SUPUESTO 2

OBSERVATORIO DE IZAÑA, EN EL PARQUE NACIONAL DE LAS CAÑADAS DEL TEIDE, CENTRO DE TENERIFE.


      En esta ocasión, y tras haber publicado un sencillo supuesto anterior en SUPUESTO 1 a modo de ejercicio y muestra, tanto para ver un ejemplo de código eficiente como para modificarlo nosotros mismos y "tunearlo" a nuestro antojo para ir "cogiendo práctica", vamos a intentar desarrollar un segundo supuesto que abunde en la gestión, básica, muy básica, de una empresa dedicada al alquiler y venta de coches.



Lo primero de todo es el diseño, la planificación de nuestro proyecto. Esto es FUNDAMENTAL. Para cualquier proyecto de programa que queramos realizar, antes que las teclas del keyboard están el lápiz, la goma, la regla, el papel milimetrado, un flexo con bombilla de luz cálida y una taza de buen café (o algo por el estilo). Todo comienza con una idea y su desarrollo posterior que deberán plasmarse de alguna manera, bien en ese viejo papel milimetrado que mencionábamos antes o en una hoja de dibujo en cualquier programa de ordenador que queramos utilizar. Lo ideal es un diagrama de flujo, fabricado por nosotros mismos bien sobre el papel o recurriendo a alguna herramienta online, que las hay gratuitas y muy buenas.

Para ello es básico tener claro lo que queremos hacer, desde el comienzo del mismo hasta el final que nos resulte más satisfactorio, con todas sus ramificaciones y potencialidades posibles, y el cómo lo vamos a hacer. Podemos subrayar aquéllo que queramos codificar de manera más inmediata, nuestro "producto básico", por así decirlo, lo que en marketing de programación se suele llamar una versión lite, y dejar para más adelante aquéllos añadidos (posibilidades/potencialidades) que "dejamos al margen" para incorporarlas al código más adelante, una vez hayamos comprobado que nuestra versión básica funciona de maravilla, lo que nos permitiría elaborar versiones más completas y optimizadas de nuestro proyecto original.



Una vez hecho todo esto, tras haber pensado y repensado lo que queremos hacer y cómo, podremos ponernos manos a la obra. En nuestro supuesto, no vamos a llegar tan lejos ni mucho menos: como se trata sólo de una muestra, de un ejemplo elemental, vamos a elaborar nuestro diseño de una manera simple y utilitaria. Haremos el proceso de diseño en dos partes: lo que queremos hacer y las clases que vamos a modelizar para conseguirlo (el cómo).

Vamos allá.

     ◼️ PARTE PRIMERA:

     

      1º.- Debe ayudar al agente a gestionar la flota de vehículos de la que dispone la empresa, tanto para                 su alquiler como para su venta.
      2º.- Deberá aplicarse sobre tres tipos de vehículos:
  • Turismos.
  • Monovolúmenes.
  • Todoterrenos.
      ☆ Por su parte, el gerente deberá ser capaz de:

      1º.- Crear y consultar una ficha técnica de cada vehículo.
      2º.- Conocer mediante un listado actualizado qué vehículos d alquiler están disponibles para su uso               en carretera y cuáles no (revisión en talleres).
      3º.- Lista de vehículos, tanto para alquilar como para vender.
      4º.- Consignar cada vehículo de la flota como vendido o alquilado.

Una vez tenemos en claro nuestro planteamiento debemos desarrollar una estructura de clases.

      

     ◼️ PARTE SEGUNDA:

 

      1º.- Interfaz ➡️ Cartelera de agente.
      2º.- Clase (interfaz): Cartera del agente donde se centraliza la información.
  • Clase Vehiculos (clase padre o superclase)
                Clases que heredan de la clase Vehiculos:
    • Clase Turismo
    • Clase Monovolumen
    • Clase Todoterreno
                Clases que heredan de todas las anteriores (incluida la clase padre Vehiculos):
    • Clase Venta
    • Clase Alquiler          
                Clase que hereda de la clase Alquiler:
    • Clase Taller   


ÁRBOL DE CLASES







Ahora que ya tenemos perfilado el árbol de clases y herencias, terminamos la modelización de nuestras clases estableciendo cuáles van a ser sus instancias
  • Los atributos/propiedades que van a tener los objetos que instanciemos de cada clase.
  • Los métodos que determinan qué es lo que pueden hacer nuestros objetos
Allá vamos.


A) Clase Vehiculos:

 
ATRIBUTOS/PROPIEDADES                                                   

🔘  marca/modelo
🔘  cilindrada
🔘  caballaje
🔘  tiempo_concesionarioTiempo de estancia o permanencia en concesionario. 
 
MÉTODOS

🔘  mostrar(): Muestra la ficha técnica del vehículo.
🔘  prompt_init():  Ejecución.

✱  Obviamente, podemos construir una ficha técnica mucho más completa incluyendo, por ejemplo, el tipo de motor, color de la chapa, matrícula, número del bastidor, tipo de combustible, etc. Pero lo vamos a dejar como "tarea" para aquéllos de nosotros que deseen trastear con el código resultante de este supuesto (que es de lo que se trata, al fin y al cabo).


B) Clase Cartera_del_Agente:


ATRIBUTOS/PROPIEDADES                                                   

🔘  listado_vehiculos
 
MÉTODOS

🔘  lista_vehiculos(mostrar_todo = False): Muestra una lista de los vehículos disponibles.
🔘  add_vehiculo(tipo_vehiculo, tipo_venta, tipo_alquiler): Nos permite añadir un vehículo nuevo a nuestra cartera de agente.


C) Clase Turismo:


ATRIBUTOS/PROPIEDADES                                                   

✱ Son atributos/propiedades específicos dado que los atributos/propiedades globales los heredará de la clase Vehiculos.

🔘  puertas
🔘  gasolina/diesel
🔘  sistema_eco: tipo de dato booleano.
 
MÉTODOS

🔘  mostrar(): Muestra la ficha técnica del vehículo.
🔘  prompt_init():  Ejecución.


C) Clase Monovolumen:

ATRIBUTOS/PROPIEDADES                                                   

✱ Son atributos/propiedades específicos dado que los atributos/propiedades globales los heredará de la clase Vehiculos.

🔘  espacio_m3: Espacio útil del vehículo expresado en metros cúbicos.
🔘  gasolina/diesel
🔘  sistema_eco: tipo de dato booleano.
🔘  baca: tipo de dato booleano.
 
MÉTODOS

🔘  mostrar(): Muestra la ficha técnica del vehículo.
🔘  prompt_init():  Ejecución.


D) Clase Todoterreno:

ATRIBUTOS/PROPIEDADES                                                   

✱ Son atributos/propiedades específicos dado que los atributos/propiedades globales los heredará de la clase Vehiculos.

🔘  altura
🔘  potencia_plus_motor
🔘  gasolina/diesel
🔘  sistema_eco: tipo de dato booleano.
🔘  baca: tipo de dato booleano.
 
MÉTODOS

🔘  mostrar(): Muestra la ficha técnica del vehículo.
🔘  prompt_init():  Ejecución.


E) Clase Venta:

ATRIBUTOS/PROPIEDADES                                                   

🔘  precio
🔘  impuestos

✱  Proponemos, por ejemplo, para quienes queramos trastear, que instanciemos un atributo/propiedad financiacion de cara a establecer la modalidad de compra del vehículo.

 MÉTODOS

🔘  mostrar(): Muestra la ficha técnica del vehículo.
🔘  prompt_init():  Ejecución.


F) Clase Alquiler:

ATRIBUTOS/PROPIEDADES                                                   

🔘  kilometraje
🔘  seguro_todoriesgo: tipo de dato booleano.
🔘  alquiler: Lo que se paga por el vehículo por unidad de tiempo.

MÉTODOS

🔘  mostrar(): Muestra la ficha técnica del vehículo.
🔘  prompt_init():  Ejecución.


G) Clase Taller:

ATRIBUTOS/PROPIEDADES   
                                                
🔘  ingreso: cadena. Determina si el vehículo se encuentra en el taller o no.

            ➡️ Está en el taller (no disponible):

                🔘  fecha_entrada
                🔘  itv: tipo de dato booleano.
                🔘  estimacion_tiemporev

            ➡️ No está en el taller (disponible):

                🔘  itv: tipo de dato booleano.

MÉTODOS

🔘  mostrar(): Muestra la ficha técnica del vehículo.
🔘  prompt_init():  Ejecución.

PLAYA DE ROQUE BERMEJO, COSTA DE TAGANANA, MACIZO DE ANAGA, NORESTE DE TENERIFE.


Comenzamos con la modelización de la clase padre o superclase (es lo más coherente y recomendable, que las modelizaciones de clases, cuando se establecen relaciones de dependencia (herencia) entre unas y otras, deban comenzar siempre por la clase padre o superclase, desde el punto de vista de la jerarquía, de arriba a abajo, en sentido descendente) que, en nuestro caso, se corresponde con la clase Vehiculos.




      
      A MÍ, PERSONALMENTE, ME GUSTA MÁS LA OPCIÓN QUE OS DEJO MÁS ABAJO. CON UNA ESTRUCTURA SINTÁCTICA DISTINTA. EN RESUMIDAS CUENTAS, VIENE A HACER Y DECIR LO MISMO QUE EL EJEMPLO ANTERIOR, PERO PARA MI CEREBRO CÁNIDO DE PROGRAMADOR...¿QUÉ QUERÉIS QUE OS DIGA? ME MOLA MÁS ÉSTA. DE TODAS FORMAS, QUE CADA CUAL ELIJA LA FÓRMULA QUE MEJOR SE ADAPTE A SUS ENTENDEDERAS 😌.




Como podemos ver, inicializamos con todos los datos necesarios nuestro objeto/instancia v1, aquéllos mismos que demandábamos como propiedades/atributos de nuestra clase Vehiculos, preasignados en el cuerpo de nuestro inicializador de la clase: el método especial __init__(), y además, secuenciados en el orden que describimos en nuestro esquema para esta clase, como podemos ver más arriba: primero la marca, luego el modelo, etc. Como tendremos varios objetos/instancias (v1, v2, v3,...) y nos interesa un cuerpo de propiedades/atributos (esta es la razón por la que modelizamos la clase Vehiculos, recordémoslo) común a todos ellos, en lugar de pasar datos o valores "reales" a las propiedades, por ejemplo, marca = "Toyota", marca = "Mazda", modelo = "Picasso", pasamos los tipos de datos (str, str, float, str, float) en que deben introducirse los respectivos valores que asignemos a cada propiedad.

Notemos cómo al final de la secuencia de propiedades/atributos, introducimos el parámetro **kwargs. Este parámetro es, realmente, un comodín que colocamos al final, siempre al final, recordemos (v. FUNCIONES DEFINIDAS POR EL USUARIO IV), para que el método especial __init__() pueda acoger propiedades/atributos  nuevos que no conozcamos de antemano, cuya inserción no hayamos previsto en un primer momento o que tengamos intención de añadir nuevas  propiedades/atributos dentro de nuestra clase más adelante. Por razones como ésta la inclusión del parámetro **kwargs resulta más que recomendable cuando, como será el caso, trabajamos con herencias y, sobre todo, si trabajamos con herencias múltiples, pues desconocemos a priori qué argumentos podrían ser invocados desde las subclases que hereden o extiendan de la clase padre o superclase Vehiculos que estamos modelizando ahora.

El maestro D. Jesús Conde en su excelentísimo tutorial sobre Python y al que podemos acceder con sólo hacer clic sobre el libro, bajo HERRAMIENTAS Y UTILIDADES, en el ángulo superior derecho de este manual (y navegando en sus apartados) argumenta, casi literalmente, que el objeto **kwargs "permite capturar parámetros que el método __init__() no sabe qué hacer con ellos pero que los recoge para pasarlos a la siguiente clase [herencia] llamando al método super() (v. LA FUNCIÓN SUPER())".

En resumidas cuentas, **kwargs constituye una herramienta de codificación plástica, un "comodín" como la intitulábamos antes, que como programadores, usaremos con frecuencia en nuestros proyectos, y que nos permite almacenar una cantidad desconocida a priori de argumentos como si fuera un diccionario, en pares variable=valor, igual que en marca = "Mazda", caballaje = 90.5, cilindrada = 6500, etc.

Veamos un ejemplo:



Así, podríamos introducir propiedades/atributos nuevos, que no estaban presentes en nuestro proyecto original, pero que podemos usar en cualquier otra clase hija, como matricula = str o color = str.
Vamos a verlo aplicándolo a nuestro supuesto:



En esta ocasión recurrimos directamente a un módulo (p.py), como vemos arriba, a la izquierda del todo, para ganar un poquito de flexibilidad en la exposición.

La clase Vehiculos es exactamente igual a la que codificamos de manera original. Así que, la que nos interesa, es la que viene a continuación: AmpliacionFicha.

En 1. modelizamos la clase AmpliacionFicha, le proporcionamos una zona de herencias, los paréntesis, e insertamos en ella el nombre de la clase de la que va a heredar o de la que va a extender, en nuestro caso, de la clase Vehiculos, por lo que nuestra clase recién modelizada AmpliacionFicha será una clase hija que heredará de la clase padre o superclase Vehiculos.

En 2. recurrimos al inefable inicializador (también, constructor, aunque algunos preferimos guardar esa denominación para el método especial __new__(), que como ya hemos visto, opera de manera elipsada, en el backend del programa, entre bambalinas, vamos) de Python, el método especial __init__(), que de manera preceptiva llevará implícita la autorreferencia self para indicar que dicho inicializador pertenece a la propia clase AmpliacionFicha.

En 3. viene la magia. Aquí recurrimos a nuestra buena amiga, la función super(), que mediante la sintaxis o notación del punto, el dott method, que hemos mencionado en innumerables ocasiones a lo largo de este manual, llama, invoca, a aquéllo (el objeto, normalmente, un método) que pongamos por delante suyo para traerlo a su posición, en este caso, en el ámbito de la clase hija AmpliacionFicha. ¿Qué es lo que llamamos? Pues al inicializador, el método especial __init__() de la clase padre Vehiculos que, como vemos, contiene un montón de cosas: marca = str, modelo = str, cilindrada = float, tiempo_concesionario = str, caballaje = float..., y nuestro comodín: **kwargs. ¿Y cómo es posible esto? Pues porque al pasar el nombre de una clase determinada, en nuestro supuesto, Vehiculos, como argumento de la zona de herencias de la clase AmpliacionFicha, establecemos un VÍNCULO entre ambas clases de tal modo que la clase cuyo nombre pasamos como argumento de la zona de paréntesis de la clase AmpliacionFicha pasa a ser clase padre o superclase de ésta última, que pasa a ser hija de la anterior.

Por tanto, a través de la función integrada super() podemos traer a una clase hija los métodos que queramos de y desde su clase padre o superclase (o clases padre/superclases, si estamos trabajando con una herencia múltiple). Y esto es exactamente lo que hemos hecho: traer al cuerpo del método especial inicializador __init__(self) de la clase hija AmpliacionFicha el método especial inicializador de la clase padre o superclase Vehiculos __init__(selfmarca = strmodelo = strcilindrada = floattiempo_concesionario = strcaballaje = float, **kwargs). Con una diferencia: en la llamada con super() no incluimos ningún argumento en el inicializador, ni tampoco, la autorreferencia self.
En 4. construimos un método nuevo, impresion, para que nos imprima, en 5., por ejemplo, la marca del objeto que instanciemos a partir de la clase AmpliacionFicha y que, como no puede ser menos, ojo, 👀, debe llevar la autorreferencia self.

Vamos a verlo en la práctica. Para aprovechar mejor la cosa, modificamos un poco el método impresion de la manera siguiente:

                                                                    def impresion(self):
                                                                            print("Marca: ", self.marca, "\n", "Matrícula: ", self.matricula)


En 1. instanciamos un objeto a partir de la clase hija AmpliacionFicha que almacenamos en un nombre de referencia que apunta a un espacio en la memoria en la que se ha guardado el mencionado objeto: v1.

A partir de aquí, en 2., y gracias a la función integrada super() que nos permite traer al ámbito de la clase hija el método especial inicializador __init__(selfmarca = strmodelo = strcilindrada = floattiempo_concesionario = strcaballaje = float, **kwargs) de la clase padre o superclase Vehiculos, podemos establecer lindamente esas mismas propiedades/atributos con sus valores respectivos para el objeto v1

En 3. aprovechamos el comodín **kwargs, mire usted por dónde, y así vemos clarito, clarito, para qué sirve: declaramos una variable que no existe en el inicializador original, __init__(selfmarca = strmodelo = strcilindrada = floattiempo_concesionario = strcaballaje = float, **kwargs) y que llamamos matricula, y le proporcionamos un valor, "12345abcde". Del mismo modo podríamos añadir todas las propiedades/atributos que se nos ocurran con su valor respectivo, como por ejemplo, numero_bastidor = "wwx123456", fecha_fabricación = "07/10/2020", capacidad_maletero = 2.5, etc.

En 4. comprobamos para pasmo y alegría nuestra que, en efecto, al objeto v1 le "adornan" todas las propiedades/atributos que insertamos en la zona de argumentos del inicializador de la clase padre o superclase Vehiculos (que, fijémonos, no "hemos tocado" para nada) __init__(selfmarca = strmodelo = strcilindrada = floattiempo_concesionario = strcaballaje = float, **kwargs) más una propiedad/atributo más, nueva, recién creada,  matricula, que hemos declarado en el momento porque el comodín **kwargs nos faculta precisamente para poder hacerlo, con su correspondiente valor asignado, "12345abcde". ¿Vemos ahora la importancia de añadir un objeto **kwargs al final de una ristra de parámetros en un método cualquiera, aunque luego no lo usemos?

Para finalizar, fijémonos que en el cuadro de diálogo que muestra  las propiedades/atributos que ha adquirido nuestro objeto v1 por haber sido instanciado dentro de la clase hija AmpliacionFicha, se nos muestra también el método impresion, que llamaremos en la captura siguiente.



En 1. llamamos al método impresion que nos devuelve, como habíamos quedado en la recodificación, el valor asociado a la propiedad/atributo marca, que ya venía declarada en la zona de parámetros de la clase padre Vehiculos, y el valor asociado a la propiedad/atributo matricula, que no existía declarada como tal originalmente, pero que gracias al parámetro comodín **kwargs, hemos podido declarar sin problemas cuando lo hemos necesitado (esto se llama plasticidad).

En resumen hasta ahora, todo funciona.

PÍJARA, HELECHO PROPIO DE LA LAURISILVA, EL BOSQUE RELICTO DE LAURÁCEAS DE HACE MÁS DE VEINTE MILLONES DE AÑOS, QUE TODAVÍA HOY SE PUEDE VER Y DISFRUTAR EN LOS ARCHIPIÉLAGOS DE LA MACARONESIA, Y DEL QUE DESCIENDEN TODOS LOS DEMÁS BOSQUES DEL MUNDO.



      

   RESUMIENDO, COMO QUIEN DICE, TENGAMOS EN CUENTA QUE ESTO SUCEDE ASÍ PORQUE **kwargs APUNTA A UN PAR KEYNAME/DATO O VALOR. AUNQUE SI EN LUGAR DE **kwargs COLOCÁSEMOS *args (FIJÉMONOS, POR FAVOR, QUE EN ESTA OCASIÓN SÓLO TENEMOS UN ASTERISCO DELANTE Y NO DOS COMO EN **kwargs) OBTENDRÍAMOS, A NIVEL INTERNO, UN TIPO DE DATO tupla QUE ES, COMO YA HEMOS VISTO EN OTRAS OCASIONES A LO LARGO DE ESTE MANUAL, LA MANERA QUE TIENE EL LENGUAJE DE IMPEDIR QUE UN DETERMINADO DATO O VALOR PUEDA SER MODIFICADO, LO MÁS PARECIDO A UNA CONSTANTE QUE PYTHON NOS OFRECE. SIN EMBARGO, TAL COSA NO SUCEDE AQUÍ DE MODO QUE EL RECURSO A *args EN LUGAR DE **kwargs, NO TIENE INCIDENCIA PARTICULAR ALGUNA DE CARA AL FUNCIONAMIENTO CORRECTO DE NUESTRO PROGRAMA Y SON PERFECTAMENTE INTERCAMBIABLES. LADRAMOS UN EJEMPLO.





Repasemos un poquito por encima las virtudes del parámetro *args con un ejemplo sencillo y un aún más sencillo esquema, justo debajo, a continuación, para compararlo con el parámetro **kwargs:







Hechas las necesarias aclaraciones, proseguimos alegremente con nuestro supuesto.



La inclusión de la función integrada super() en nuestro inicializador/constructor __init__(...) de nuestra clase vehiculos() nos permite invocar a posteriori cualquier nuevo (o nuevos) argumento que queramos incorporar de acuerdo a los requisitos de la subclase o clase hija con la que estemos operando. En este sentido, podemos entender super() como una "pseudoinstanciación" de atributos/propiedades, o lo que es lo mismo, una instanciación in albis de atributos/propiedades futuribles..

Continuamos añadiendo a nuestra clase los métodos que le corresponden:



Y probamos a continuación que todo funcione correctamente.




PERENQUÉN (O CHERENQUE, GUACHINERO O GUACHINEGRO, PENINQUÉ, PENIQUENQUE, PRACÁN, RAÑOSA O RAÑOSITA, SALAMANCA Y SALAMANDRA) QUE
POR TODOS ESTOS NOMBRES SE CONOCE EN EL ARCHIPIÉLAGO DE LAS CANARIAS, A ESTE PEQUEÑO REPTIL ENDÉMICO DE NUESTRAS ISLAS.


Con el método prompt_init() referimos que los datos son introducimos de manera externa mediante una función input(), y que luego se mostrarán en formato típico de diccionario gracias a la llamada a la función dict(), que para eso está.

Si nos fijamos, nuestro método prompt_init() no lleva inserto en su zona de parámetros la sempiterna autorreferencia self 😱: no nos interesa traer self aquí porque tenemos la intención de convertirlo en un método estático (V. MÉTODOS DE CLASE, ESTÁTICOS Y @PROPERTY y SUPUESTO 1). ¿Y cómo hacemos esto? Incluyendo el método estático asignado al nombre del método y llevando como argumento al método mismo, fuera del ámbito del propio método prompt_init() para que pueda ser, en efecto, un método estático mondo y lirondo.

Este método nos devuelve un objeto de tipo dict (diccionario), ya que usamos, recordemos, la función dict(), en el retorno (return) de nuestro método prompt_init(), tal y como podemos comprobar a continuación:


Presentamos a continuación una forma más elegante de conseguirlo recurriendo a los decorators, los decoradores de Python (v. DECORATORS).



Para construir/instanciar un nuevo objeto, podemos recurrir a la sintaxis siguiente:


De esta  manera tan simple, convertimos al método prompt_init() en un método estático, con la función staticmethod()...


... que trabaja en segundo plano y que lleva como argumento a ejecutar nuestro método prompt_init() después de haberlo creado; con el decorador @satticmethod antes de haberlo creado, como se puede apreciar en el ejemplo anterior, y así obtener sus datos asignados de manera directa posteriormente y no desde un objeto-padre sino, al contrario, desde la misma subclase o clase hija, mejorando así la disponibilidad de la información.

Recordemos que los métodos estáticos se comportan de manera casi análoga a las variables de clase, vinculándose a la clase misma y no a los objetos instanciados dentro de la propia clase, por lo que no necesitamos utilizar ninguna autorreferencia (ni self ni cls), aunque sí debemos reseñar que con los métodos estáticos no funciona la función integrada super() y, consecuentemente, no son heredables al no apuntar a ningún método instanciado a partir de un objeto cualquiera de la clase.

RESTOS DE DIQUES ANTIGUOS, MUY EROSIONADOS, EMERGIENDO COMO NAÚFRAGOS SOBRE UN MAR DE PIEDRA PÓMEZ A MÁS DE 2500 METROS DE ALTURA, PARQUE NACIONAL DE LAS CAÑADAS DEL TEIDE, CENTRO DE TENERIFE.


DESARROLLO DE LAS CLASES HIJA TURISMO, MONOVOLUMEN Y TODOTERRENO



      Estas tres clases contendrán y compartirán una estructura de código muy similar, añadiendo o modificando tan sólo aquéllos aspectos que les fuera más particular a cada una de ellas, lo que nos proporcionará opciones que redundarán en el concepto de reusabilidad del código, tan apreciado por Python.

Nos apoyaremos en la clase turismo, que nos servirá de modelo para el resto de clases, Monovolumen y Todoterreno, y en la que observaremos los beneficios y, también, las limitaciones de las herencias en el momento de programar.

      
      TENGAMOS EN CUENTA QUE, POR UN ERROR PROPIO, POR LO CUAL PEDIMOS DISCULPAS, EL NOMBRE DE ALGUNAS DE ESTAS CLASES SE HAN ESCRITO EN MINÚSCULAS. ES UN GRAVE ERROR, INSISTIMOS: LOS NOMBRES QUE PROPORCIONAMOS A LAS CLASES DEBEN COMENZAR SIEMPRE POR MAYÚSCULAS. COMO Monovolumen Y Todoterreno Y NO POR MINÚSCULAS, COMO turismo. 😔🙏




Comencemos a modelizar la clase.



Inmediatamente debajo de la  modelización de la clase turismo, y con la preceptiva indentación o sangrado en tanto estamos dentro del bloque o ámbito de la clase, instruimos sendas tuplas por atributo/propiedad: numero_puertas, carburante y ecoconducción, con sus respectivos valores que, posteriormente, serán los que tengamos que elegir (opciones) para caracterizar nuestro turismo.

Estos atributos/propiedades no se heredan de la clase vehiculos dado que no han sido instanciados en la superclase, siendo por contra exclusivos de la nueva clase turismo que estamos construyendo.

¿Por qué recurrimos a un tipo de dato tupla y no a un tipo de dato lista, más versátil, por ejemplo, para almacenar la colección de datos que serán las opciones que se elegirían para definir las características de un objeto que instanciemos a partir de la clase turismo? Por una parte para aprovechar la condición de inmutabilidad que caracteriza y define a la tupla frente a la lista, y evitar así cambios o modificaciones indeseadas, garantizándonos así que la información se conserve y se transmita, esto es, que se preserve, vamos, intacta; y por otra porque es la forma en la que Python almacena a nivel interno por defecto cualquier colección de datos, lo que redunda en eficacia y optimización.

De todas formas, ya sabemos de los apartados anteriores en este manual que en el supuesto de que quisiéramos introducir algún tipo de modificación en nuestras tuplas, nos basta simplemente con recurrir a la función conversora list(), efectuar los cambios que consideremos oportunos apoyándonos en los métodos propios de la función list() (append(), remove(), etc..), e invertir el proceso anterior volviendo a construir una tupla, esta vez, con la función conversora tuple().

Las opciones posibles representan cada valor que puede asumir las respectivas variables: 3 ó 5, tipo de dato entero, para la variable numero_puertas; "gasolina", "diésel", "híbrido" para la variable carburante; y un tipo de dato booleano, True y False, (en este caso, se asimilará a True y no se asimilará a False) para la variable ecoconduccion.

Procedemos ahora a añadir nuestro inicializador/constructor __init__() a la clase:




1.- Integramos en el inicializador/constructor, como ya es norma, las variables que almacenarán los atributos/propiedades exclusivos aplicados a la clase turismo, más el comodín **kwargs, por si tenemos que añadir información que desconozcamos a priori, pasándose en modo diccionario. 

2.- Llamamos a la función super() que nos permite incorporar atributos/propiedades de la clase padre o superclase y traerlas al cuerpo o ámbito de nuestra clase hija turismo

Finalmente, justo debajo de esta última línea de código, instanciamos los atributos/propiedades numero_puertas, carburante y ecoconduccion.



Esta estructura de código nos proporciona una situación interesante: por una parte, tenemos a las variables numero_puertascarburante y ecoconduccion conservadas como variables de clase; y por otra, las repetimos en el inicializador/constructor donde las instanciamos como atributos/propiedades, tal y como solemos hacer.

Si instanciamos un objeto, v1, de la clase turismo, en cuanto abramos sus paréntesis, el IDLE de Python nos mostrará los requisitos del inicializador/constructor para proporcionar a nuestro recién instanciado objeto v1 sus características iniciales. Lo vemos abajo:


A modo de ensayo, en esta ocasión no vamos a introducir dato alguno y cerramos en vacío la zona de parámetros de nuestro inicializador/constructor. Así, en cuanto colocamos el punto (sintaxis del punto o dott method) delante de nuestro flamante objeto v1, automáticamente, Python abre un cuadro de diálogo donde nos muestra en columna los diferentes atributos/propiedades de los que dispone en su inicializador/constructor, __init__(), para que podamos efectuar una selección:


Seleccionamos por ejemplo la característica (atributo/propiedad) carburante y, primero, llamamos al atributo/propiedad delegado por la clase turismo sobre el propio objeto v1. Como no hemos proporcionado ningún valor en el momento en que instanciamos al objeto v1, Python nos devolverá su valor inicial que no es otro que su tipo de dato: <class 'str'>. A continuación, llamamos a la variable de clase carburante mediante la sintaxis del punto, turismo.carburante, lo que nos devolverá la tupla original: ('gasolina', 'diésel', 'híbrido'). Lo vemos a continuación.



¿Y qué podemos conseguir con esto? Pues, por ejemplo, añadiendo un condicional a nuestro código, dentro del propio inicializador/constructor __init__(), justo a continuación de la instanciación de los atributos/propiedades numero_puertas, carburante y ecoconduccion. De esta manera, si alguno de los valores que proporcionamos a las características (atributos/propiedades) no está dentro de las tuplas que conforman los valores de base opcionales (las variables de clase) de cualquier objeto que instanciemos o construyamos a partir de la clase turismo, se advierta del error y se proponga una acción concreta. En el caso de que los valores que pasemos a las características (atributos/propiedades) sí estuvieran dentro de las tuplas que conforman los valores de base opcionales (las variables de clase) de cualquier objeto que instanciemos o construyamos a partir de la clase turismo, nos muestre un mensaje que nos advierta de que todo está correcto. Vamos a ver sendos ejemplos para el caso donde proporcionaremos una estructura  muy sencilla, tan sólo la palabra reservada pass, para la clase padre o superclase vehiculos:






Interesante, ¿verdad?

CUEVA DEL VIENTO, EL TUBO VOLCÁNICO MÁS LARGO DE LA UNIÓN EUROPEA, ACONDICIONADO EN SU MAYOR PARTE PARA LAS VISITAS GUIADAS, EN EL MUNICIPIO DE ICOD DE LOS VINOS, NOROESTE DE TENERIFE.


Proseguimos añadiendo ahora los métodos correspondientes a nuestra clase hija turismo.


 En 1. invocamos de nuevo a la función integrada super() para llamar al método homónimo (mostrar) desde la clase padre vehiculos. Vamos a ver qué tal funciona:


En 1. instanciamos un objeto que llamamos v1 a partir de la clase hija turismo. a lo largo de 2. le proporcionamos a nuestro flamante objeto v1 las características (atributos/propiedades) que hemos definido en el inicializador/constructor de la clase padre vehiculo, mientras que en 3., hacemos lo propio con las características (atributos/propiedades) que son propias, exclusivas, de la clase hija o subclase turismo. Así, v1 cuenta con todas las características que se le demandan: las que le provienen heredadas o extendidas desde su clase padre vehiculos, en 1., a las que se suman, en 2. las que le son propias, las que definimos dentro de su propio cuerpo o ámbito: numero_puertas, carburante y ecoconduccion.

Cuando una vez que hemos hecho esto llamamos a continuación al método mostrar (obviamente, el método mostrar que instanciamos en la clase turismo y donde instanciamos nuestro objeto v1). Como en la primera línea de código del método mostrar llamamos a través de la función integrada super() al método homónimo de la clase padre vehiculos, es el resultado de la ejecución de éste lo primero que se visualiza en los resultados. Justo a continuación se visualiza el resto del código del método que le es propio, imprimiendo el número de puertas, el tipo de carburante y se aplica o no ecoconducción.



Procedamos ahora a instanciar nuestro segundo método, promp_init(), cuya función, como recordamos de la modelización de nuestra clase padre o superclase vehiculos, será la de obtener del usuario los valores correspondientes a cada clave, key, del par clave/valor, key/value, que define la sintaxis del tipo de dato dict, diccionario, en Python) proporcionándonos de manera inmediata el acceso desde la clase padre o superclase vehiculos y, dado el caso, adjuntar valores nuevos al mismo.

Como vamos a disponer de dos diccionarios, uno procedente de la clase padre o superclase vehiculos y el segundo instanciado dentro de la propia clase hija turismo, podemos recurrir de nuevo a un método estático a través de su decorador, decorator, correspondiente: @staticmethod.

¿Que tenemos dos diccionarios? Hombre, pues podríamos fusionarlos en uno solo a través del método update() de los diccionarios (v. DICCIONARIOS Y SUS MÉTODOS) y facilitar así su accesibilidad. Pero esta solución, factible, sí, y a primera vista lógica y deseable, dado que podríamos repetir este proceso de "fusionado" con los diccionarios que creemos en clases sucesivas que respondan a la misma estructura, aumentaría en exceso la complejidad del código obligándonos a ejecutar sendos bucles for/in por entrada, inputinput() del usuario.


      ESTO ÚLTIMO QUE ACABAMOS DE DECIR NOS DEMUESTRA QUE EL CONCEPTO DE REUSABILIDAD/REUTILIZACIÓN/MINIMIZACIÓN DEL CÓDIGO QUE TANTO GUSTA EN EL IDEAL ZEN DE PYTHON, NO SIEMPRE ES LO MÁS DESEABLE SEGÚN EL CASO: HABRÁ MOMENTOS DONDE TENDREMOS QUE VALORAR SI NOS INTERESA MÁS MINIMIZAR EL CÓDIGO O MODERAR SU COMPLEJIDAD, SOBRE TODO , DE CARA A LAS REVISIONES QUE NOSOTROS MISMOS VAYAMOS A EFECTUAR A LO LARGO DEL TIEMPO, ASÍ COMO EL MOSTRAR UN CÓDIGO CLARO E INTELIGIBLE PARA TERCEROS. AL FINAL, EN PYTHON, COMO EN LA VIDA MISMA, NO TODO ES BLANCO O NEGRO.

Por contra, la mejor opción pasa por construir una función de validación que tome una cadena, string, como entrada y devuelva una lista con las respuestas válidas, que incrustaremos en la clase padre o superclase, inmediatamente después del decorador, lo que de motu proprio solventa la circunstancia de que si tuviéramos que recurrir a herencias, el método devendría en estático (staticmethod) lo que impide el acceso al mismo de ninguna variable de instancia aunque podrían acceder a él las variables de clase, obligándonos a modelizar una clase nueva.

Así pues, instanciamos un método controlador (función de validación) dentro del cuerpo o ámbito de nuestra clase padre o superclase vehiculos.



1. Para el primer argumento, entrada, de la función input_valido(entrada, opciones_validas) usamos un operador de incremento, +=, que nos permite añadir nuevas opciones. Obtendremos así una cadena o string que permitirá unir a una pregunta una serie de opciones pasadas entre paréntesis mediante el concurso del método str.join(secuencia) que, recordemos, devuelve una concatenación entre la cadena y lo que pasamos como argumento del método, una secuencia de datos que viene almacenada en la variable opciones_validas, el segundo argumento obligatorio que lleva en su zona de parámetros la función de validación input_valido(entrada, opciones_validas).

2. Creamos la variable respuesta que guardará la entrada que escriba el usuario como resultado de la ejecución de la instrucción anterior.

3. Para filtrar las respuestas recurrimos a un bucle dinámico while, de tal modo que si la entrada, respuesta, que deberá pasarse en minúsculas en virtud del método lower() de las cadenas o strings,  no se encontrara entre opciones_validas, volvería a repetirse la pregunta (respuesta=input(entrada)). En caso de que sí se encuentre entre las opciones propuestas, opciones_validas, se devuelve la respuesta

                                                                                               
TORTUGAS EN LAS COSTAS DEL FONDO ATLÁNTICO TENERIFE.

 
Toca ahora convertir nuestro script en un precioso y molón módulo de Python, con su nombre y su extensión .py, para poderlo invocar cuando queramos desde nuestro editor de código o IDE preferido. Para conseguir esto lo primero que haremos será guardarlo con un nombre que le proporcionaremos nosotros mismos, por ejemplo, Vehiculos, desde el propio IDLE de Python, el editor básico pero eficaz que proporciona la propia instalación del programa y que ya conocemos de sobra. En nuestro ejemplo, como podemos ver arriba del todo a la izquierda, para poder operar con él y mostrar los ejemplos que acompañan a este artículo, le hemos dado un nombre provisional, aa.py:



Además, como subrayamos también, hemos estado trabajando hasta ahora desde el propio escritorio, Desktop, probablemente, el directorio más socorrido de cualquier sistema operativo que se precie.

Pues lo dicho: le cambiamos el nombre por otro más chic, como Vehiculos, con su extensión .py, esto es, Vehiculos.py, mediante la ruta Files > Save As... como indicamos abajo:






Como vemos, le cambiamos su nombre original provisional aa.py por Vehiculos.py, pulsamos sobre la opción Guardar, en el ángulo inferior derecho, y el fichero se nos almacenará en Escritorio.

Ahora ya sólo nos queda mover el fichero Vehiculos.py al índice de módulos de Python. Nos basta con un simple drag and drop, arrastrar y soltar, del módulo desde el Escritorio hasta el mencionada índice. Así que hacemos doble clic sobre el icono del IDLE de Python (recomendamos tenerlo siempre a mano en nuestro Escritorio) para que se nos abra, y seguimos la ruta File > Open...:



Con nuestro índice de módulos de Python abierto, arrastramos nuestro nuevo fichero Vehiculos.py al lado derecho.



Y comprobamos que, en efecto, ya lo tenemos almacenado en nuestro índice de módulos de Python.



Ahora, para que la implementación surta efecto, tendremos que efectuar la importación correspondiente a través de una sintaxis de importación absoluta, esto es, from Vehiculos import *, dado que la función input_valido(entrada, opciones_validas) pertenece al módulo Vehiculos.py. Es una opción más interesante porque, a expensas de que vamos a modelizar varias clases más, si nos ponemos a escribirlas todas, ... Nada, mejor * y acabamos antes.



En 1. lo que hemos hecho es llamar a la clase vehiculos y, mediante la sintaxis del punto, llamar a su vez al método input_valido(entrada, opciones_validas), pasándole los argumentos que nos exige: una pregunta a modo de entrada, pasada como cadena o string, y una tupla con las posibilidades que queremos dar a elegir para la variable opciones_validas. Si nuestro input, nuestra respuesta (aquéllo que se va a almacenar en la variable respuesta) está dentro de las opciones propuestas, nos la devuelve tal cual. En caso de no ser así, el método seguirá ejecutándose (para eso está el bucle dinámico while) hasta obtener del usuario una respuesta válida.

Como sabemos que la tupla la tenemos ya creada en la clase turismo y almacenada en la variable carburante, gracias a  las posibilidades que nos deja la herencia, podemos invocarla directamente desde el propio método input_valido(entrada, opciones_validas), invocando a la variable de clase carburante por la sintaxis del punto. Lo vemos:




Obviamente, como ya deberíamos saber, toda entrada externa de datos a través de la función integrada input() de Python  lo hace como tipo de dato string, como una cadena de texto. Esto significa que si, como en el caso anterior, las opciones a elegir son strings, todo se resuelve maravillosamente bien. Pero si no lo son, Python lanzará una excepción del tipo TypeError como la copa de un pino.


¿Cómo podríamos solucionar estos casos? Entre varias soluciones posibles, quizás la más fácil pase por reescribir los valores de nuestras variables de clase en la clase turismo y pasarlas todas como strings, como cadenas de texto, con lo cual el problema se solventaría de un plumazo:

Y, posteriormente, recurrir según fuera el caso a las funciones conversoras, int() para numero_puertas y bool() para ecoconducción, respectivamente, si en algún momento a lo largo del código necesitáramos estos tipos de datos, enteros y booleanos para efectuar alguna operación.

Otra opción pasaría por crear otras tantas tuplas-espejo de las primeras, con todos sus elementos pasados como cadenas, pero conservando las anteriores. Así, cuando pasemos el argumento opciones_validas en input_valido, pasamos estas tuplas-espejo (strnumero_puertas y strecoconduccion) en lugar de las originales. En este caso, podríamos convertir el dato introducido por el usuario de manera análoga a como explicamos más arriba y, con el dato ya convertido en entero o booleano según el caso, compararlo con los datos de las tuplas originales.



Pero nosotros vamos a emplear una fórmula más plástica y elegante donde podremos apreciar mejor el potencial de la sintaxis de Python. Para ganar coherencia, primero declararemos sendas variables por entrada que vayamos a utilizar almacenando cada una de ellas la pregunta correspondiente. A continuación usaremos una combinación entre la función integrada map() y la función lambda (v. T2: LAS FUNCIONES LAMBDA: ELOGIO DE LA SENCILLEZ).

Con la función map() podemos aplicar una función cualquiera, en nuestro caso, la función lambda, cuya mayor virtud  y razón de ser es que no lleva nombre, es una función anónima, por lo que nos viene de perlas para el caso, sobre una secuencia de datos. Y una tupla, mire usted por dónde, es precisamente éso: una secuencia de datos. ¿Y qué es lo que queremos hacer con cada dato de esa secuencia? Pues convertirlos directamente, "sobre la marcha", a cadenas, a strings, para que el método input_valido nos funcione correctamente. La fórmula sería, pues, como sigue:




Con la función map() aplicamos una función, en este caso, una función anónima lambda y que constituye el primer argumento de la misma, donde en 1. hacemos referencia a cada elemento (item) de la secuencia (la tupla, en 3.,) de manera que transformaremos cada elemento en una cadena (esto es lo que hemos decidido que debe hacer nuestra función lambda) como mostramos en 2.. ¿Y dónde aplicaremos ésto? Pues en 3., como ya hemos dicho, la secuencia, la tupla, que colocamos tras la coma preceptiva y que constituye el segundo argumento de nuestra función map().

Sin embargo, para acabar, para que todo esto funcione debemos envolver la línea de código anterior entre los paréntesis, la zona de parámetros, de la función conversora tuple(). Sólo entonces obtendremos la modificación que necesitamos. Comprobamos ahora qué tal nos funciona:


Funciona. 

Sin embargo, este código es una buena solución para tipos de datos como enteros o decimales, float, pero no nos va a funcionar así, a bote pronto, con los booleanos. ¡Mecachis! Lo vemos:


Y así seguiría demandándonos una respuesta correcta que nunca podríamos darle. 

Pero si, efectivamente, el código nos convierte los elementos True y False de la secuencia original ecoconduccion en cadenas, "True" y "False" y nuestro input, a pesar de que por defecto de coloreado del IDLE que nos lo deja en naranja, no nos confundamos, es una cadena o string como una catedral... ¿Dónde está el problema? ... ⏰...

Sí en efecto,  el problema está aquí, dentro del cuerpo o ámbito de la clase vehiculos, en nuestro método input_valido:



Si eliminamos, comentamos o modificamos del modo siguiente el código:

 

... entonces sí nos valdría, pero generaría un problema para el resto de opciones:


El filtrado sobre opciones-validas ya no nos sirve para nada.

Entonces, ...¿Cuál sería la solución idónea para terminar de una vez por todas con este embrollo? Pues muy sencillo: sustituir los booleanos True y False por "sí" y "no". Además, modificamos el código del método input_valido extrayendo el método str.lower() del bucle while, donde también nos daría problemas, y lo colocamos una línea de código por encima, de tal modo que el método final nos quedaría así:


Si volvemos a probar el código nuevamente, comprobaremos que todo funciona correctamente.





¿Qué enseñanzas podemos extraer de todo ésto? Lo primero, que es muy recomendable no utilizar booleanos alegremente y procurar emplearlos sólo y en la medida de lo posible para las bifurcaciones (condicionales): nos evitaremos entrar en secuencias de códigos-parche para ir resolviendo problema tras problema hasta, en muchas ocasiones, como en el ejemplo anterior, tan sólo llegar a un callejón sin salida. También podemos concluir que, en la medida de lo posible, ya que la entrada de datos por input Python la transforma por defecto en una cadena o string, procuremos siempre que tengamos que filtrar esa entrada a partir de una secuencia de datos como en nuestro caso, que todos esos datos, sea cual sea la secuencia, sean todos cadenas:


Además, en el caso de los números, si tuviéramos que operar con ellos efectuando cálculos aritméticos, siempre tendríamos a nuestra disposición a la función integrada eval(), para cálculos sencillos, o aún mejor, según el caso, las funciones conversoras int(), float(), etc.


EL EXTRAÑO CASO DE LA DOCTORA TUPLA Y MÍSTER LAMBDA


      
      Para quienes han seguido este tutorial con cierto grado de interés, observando el resultado de la aplicación de la instrucción map(lambda item: str(item), turismo.numero_puertas), se habrán percatado de que... ¡hemos conseguido modificar una tupla sobre la marcha!

Sin embargo, sabemos desde que estudiamos a este tipo concreto de secuencia de datos (v. TUPLAS) que las tuplas no se pueden modificar. Es más: para eso están.

Ninguna secuencia puede modificar el tipo de dato de sus elementos si no es a través de la declaración de otra secuencia accesoria que los almacene y, después, igualar la variable que almacena la secuencia original a esta secuencia accesoria. Y en el caso de las tuplas, una vez hecho lo anterior, emplear la función conversora tuple() para rematar la faena:



Sin embargo, si recurrimos a mister lambda, la cosa cambia y la transformación de los tipos de datos se realiza de manera casi automática. Y eso a pesar de que una tupla, como vemos, por  ser inmutable, sólo cuenta con dos modestos métodos: count(), para saber el número de veces que un determinado ítem o elemento se repite dentro de la secuencia; e index(), que nos devuelve la posición que ocupa un determinado ítem o elemento dentro de la secuencia.



Es cierto que la variable x que hemos declarado para almacenar el resultado de la ejecución de la instrucción será una tupla. Pero es la función anónima lambda la que, de manera interna, genera una secuencia accesoria sobre la tupla a y ejecuta por sí misma, sin que nosotros tengamos que hacer nada, el proceso que mostramos en la captura inmediatamente superior. Sí, amigos, la función lambda esconde  bajo su escueto nombre un potencial muy elevado que podemos aprovechar.

OCASO SOBRE LOS PINARES DE TENERIFE.


Finalmente, construimos nuestro método prompt_init de forma muy parecida a como lo hicimos dentro de la clase vehiculos, como método estático, @staticmethod, y donde incluiremos una llamada al diccionario de la clase padre o superclase, y que generaba el método homónimo prompt_init, uniendo posteriormente ambos diccionarios a través del método dict.update(dict) para englobar en una única variable, que llamaremos parent_init, toda la información disponible hasta el momento del objeto que hemos instanciado.



En 1. declaramos una variable que llamamos parent_init (lo de "parent" en la primera parte del nombre incide en su descendencia) donde se almacenará el resultado de la ejecución del método homónimo prompt_init de la clase padre vehiculos.

En 2. tenemos nuestras tres variables que almacenarán las respuestas (respuesta) que demos a las cuestiones que se nos plantean. En 3. mantenemos el código que desarrollamos más arriba, con la función anónima lambda, porque ya que la hicimos... ¿no? De todas formas, si queremos prescindir de élla podemos tranquilamente modificar el contenido de la tupla numero_puertas y, en vez de tener como elementos números enteros, pasamos cadenas de texto: en vez de 3 y 5 pasamos "3" y "5".

En 4. unimos los dos diccionarios: el que tenemos desarrollado en la clase padre o superclase vehiculos y que almacenamos en la variable parent_init, tal y como explicamos en 1., con el que desarrollamos a continuación para las variables que declaramos en 2., y que pasamos como argumento en la zona de parámetros del método update() de los diccionarios.

En 5. ya podemos solicitar un retorno, una devolución, return, de los resultados de la ejecución del método.

Comprobamos su funcionamiento.



Vemos que sí. Ahí va la cosa.

Mostramos a continuación una imagen de nuestro código hasta el momento actual:





VISTA PARCIAL DE LAS CAÑADAS DEL TEIDE, CORAZÓN DE TENERIFE.


MODELIZACIÓN DE LAS CLASES MONOVOLUMEN Y TODOTERRENO



A) MONOVOLUMEN:









B) TODOTERRENO:





MODELIZACIÓN DE LAS CLASES VENTA Y ALQUILER


      

VENTA:






      Comenzamos por modelizar la clase venta (sí, sí, error nuestro: recordemos un vez más que los nombres de las clases en Python, por convención (PEP8), deben escribirse con la letra inicial en mayúsculas. De nuevo, pedimos disculpas) cuya estructura será similar a las clases que ya tenemos modelizadas, obviamente, con los atributos/propiedades que le corresponden pero manteniendo los mismos métodos. Vamos a verlo:


      Como podemos ver, tan sólo hemos declarado dos variables (atributos/propiedades) como argumentos del inicializador, tras la autorreferencia obligatoria self: precio e impuestos, en ambos casos, variables que almacenarán una cadena de texto, una str, como dato o valor, y que instanciamos justo por debajo a la llamada a la función super().

Posteriormente, instanciamos al método mostrar, cuya primera línea de código es también una llamada a la función integrada super() y, para acabar, nuestro decorador @staticmethod antecediendo al método prompt_init, para transformarlo en un método estático.


      DEBEMOS DARNOS CUENTA DE QUE TAL Y COMO ESTÁ CONSTRUIDO NUESTRO CÓDIGO, NUESTRA LLAMADA A LA FUNCIÓN INTEGRADA super() NO TIENE SENTIDO HABIDA CUENTA DE QUE NO HEMOS ESTABLECIDO HERENCIA ALGUNA EN SU ZONA DE HERENCIAS: NUESTRA CLASE ventas NO ES CLASE HIJA DE NINGUNA CLASE PADRE O SUPERCLASE PORQUE NO HEREDA O EXTIENDE DE NINGUNA. ENTONCES, ¿POR QUÉ LAS HEMOS INCLUIDO? PORQUE COMO PROGRAMADORES DEBEMOS PREVER QUE EN ALGÍN MOMENTO, EN ALGÚN INSTANTE A LO LARGO DEL TIEMPO, SI RECODIFICAMOS NUESTRO CÓDIGO, PODRÍAMOS QUERER CONVERTIR NUESTRA CLASE ventas EN CLASE HIJA DE ALGUNA (O ALGUNAS) CLASE PADRE. LO IDEAL SERÍA ADJUNTAR UN COMENTARIO JUSTO AL LADO ADVIRTIENDO DE ESTA SITUACIÓN (POR EJEMPLO, # super().mostrar() Llamada incidental a expensas de establecer una o más clase/s padre/s) A OTROS PROGRAMADORES. REITERAMOS QUE SE TRATA DE UNA OPCIÓN QUE PODEMOS TOMAR O NO, Y QUE EN ÚLTIMA INSTANCIA DEPENDE DE LA INTENCIÓN DEL PROGRAMADOR.

Vamos con algunos testeos para comprobar qué tal se comporta nuestro código. Evidentemente, para poder llamar al método mostrar hemos eliminado la función super() de su cuerpo dado que, el mantenerla sin haber establecido una relación de herencia, como ya hemos dicho, devendría en una excepción de Python. Tal y como nos sugiere nuestra peluda amiga, otra posibilidad sería la de comentar la línea de código, tal y como se muestra en el ejemplo que nos ladra tan divinamente.




Vemos ahora una opción diferente donde, en lugar de utilizar cadenas (str) como tipo de dato base para nuestros atributos/propiedades, como habíamos propuesto originalmente, utilizamos decimales (float). Comprobaremos que, con los arreglos adecuados y sin despeinarnos un pelo, seguimos obteniendo los resultados deseados, añadiendo incluso algún método más para redondear la eficacia de nuestra clase:









      ALQUILER:






      Procedamos a continuación a modelizar la clase alquiler de nuestro supuesto:



Como vemos, seguimos reproduciendo la estructura que hemos tomado como base para todas las clases que hemos modelizado. Aquí introducimos como atributos/propiedades propias de la clase alquiler kilometraje, seguro y alquiler, todos estos pasados como cadenas.



Podemos ver que ninguna de nuestras dos clases, venta y alquiler, hereda de superclase alguna salvo de la elíptica object de la que heredan todas las clases de Python, tanto las predefinidas por el propio lenguaje como las que nosotros mismos podamos modelizar en nuestros códigos, "universal" para todas las clases, actuando siempre en segundo plano (de hecho, los métodos especiales a los que ya hemos dedicado un artículo (v. MÉTODOS ESPECIALES) y que podemos aplicar en nuestras clases, provienen precisamente de aquí). Es importante destacar también que uno de los aspectos más interesantes a la hora de programar es que la interrelación entre las clases mejora, incrementa la funcionalidad y se cumple de facto con el principio de abstracción cuando sus estructuras internas son muy parecidas entre sí, y fijémonos, como hemos repetido ya varias veces, que la estructura se repite en todas las clases, lo que a la postre no sólo redunda en su eficacia sino que, además, facilita el mantenimiento del código, lo hace más accesible a otros programadores y nos procura una lógica combinatoria interna: ahora podemos crear una clase turismo-alquiler, por ejemplo, aprovechando una herencia múltiple (que, ojo, siempre hay que controlar bien) desde las clases turismo y alquiler.

Veamos las combinaciones posibles de acuerdo a nuestras clases turismo y alquiler ya modelizadas y que pasamos como clases padre o superclases en su zona de herencias.



Como podemos ver, la razón de ser de este código (y de sus "clones" posteriores) es la de construir un diccionario único que resulte de la suma, y el método por excelencia de los diccionarios que suma es  el método update(), de los datos del diccionario prompt_init de alquiler y turismo.

No se necesita ningún inicializador/constructor __init__() porque ya lo hereda de sus clases padre. Por otra parte, y como sigue siendo habitual, recurrimos a un método estático para construir el diccionario que queremos obtener.

Para conseguir esto debemos apoyarnos en una herencia múltiple, 1., llamando y pasando como argumentos de su zona de herencias a las clases alquiler y turismo. Y hacerlo, además, en este orden y no al revés (el MRO). Ya veremos por qué.

En 2. construimos un método estático, prompt_init, igual que hemos estado haciendo hasta ahora, para que nos genere el diccionario que mencionábamos antes, con sus correspondientes pares clave/valor.

Para esto declaramos, en 3., una variable a la que llamamos init donde poder almacenarlo: primero, en esta instrucción, le asignamos como dato el diccionario heredado de la clase turismo. El proceso será el mismo cuando modelicemos las clases monovolumen y todoterreno.

Ahora podemos aplicarle, en 4., el método update() de los diccionarios a nuestra variable init y sumarle al diccionario heredado desde la clase turismo el creado a su vez para la clase alquiler y que heredamos, obviamente, de esta misma clase alquiler.

Ya en 5. nos devolverá el nuevo diccionario init con la fusión de los datos de los diccionarios procedentes de las clases turismo y alquiler.

Mostramos, a continuación, las clases MonoAlquiler y TodoAlquiler.




Toca comprobar qué tal nos funciona. Para no extendernos más de la cuenta, lo haremos sólo para la opción de alquiler de turismos, cuya clase representativa es TurAlquiler:



Parece que hasta ahora, todo funciona correctamente.


MODELIZACIÓN DE LA CLASE TALLER






      Nos ocupa desde ahora modelizar la clase Taller

Sin embargo, para empezar, lo mejor sería hacer alguna modificación en nuestras clases anteriores de tal modo que todos los valores que instanciemos para nuestros atributos/propiedades se almacenen como cadenas, strings, y sea éste tipo de dato el genérico para todos los valores que obtengamos para los respectivos atributos/propiedades de nuestras clases. Al hacer esto conseguiríamos adaptar nuestro código al Principio de Coherencia, que insta a emplear fórmulas de codificación similares, tanto como fuera posible, en nuestros programas. Por ejemplo, si la mayoría de datos que se introducen en un código lo hacen como cadenas de texto, como strings, y no lo modificamos en el momento de la entrada envolviendo nuestro input en una función conversora, pongamos por caso, int(input("Escribe un número: ")), lo ideal es que TODOS los datos, incluido el resultante de "responder" a la solicitud "Escribe un número: ", se introduzcan como cadenas. Y si se necesitara modificar el tipo de dato posteriormente para operar aritméticamente con él, por ejemplo, para hacer  sumas, aplicar algoritmos, etc., pues propiciar la necesaria conversión en el momento oportuno y no antes.

Así pues, actuamos sobre nuestra clase turismo, que era la que "adolecía de incoherencia", si se nos permite la expresión, y la modificamos para "hacerla más natural", en el sentido de que todos los datos que entran desde el exterior proporcionados por el usuario vía input se almacenan como cadenas, tal y como por defecto tipifica cada dato que recibe la función input() de Python. Lo vemos.



Aquí vemos cómo todos los datos susceptibles de selección de las tuplas numero_puertas, carburante y ecoconducción son ya cadenas de texto, strings, por lo que a la hora de concatenar esos mismos datos ya no es necesario modificar previamente su tipado. ¡Ah! ¡Qué alivio! ¡Qué interesante esto de la coherencia!

Es fundamental conservar los datos que obtenemos, según instanciemos un objeto perteneciente a la clase turismo, monovolumen o todoterreno. Como no estamos trabajando con Bases de Datos tendremos que recurrir a una forma sencilla de conservar los datos. No usaremos los módulos shelve o pickle (v. T2.SERIALIZACIÓN: PONGA UN BINARIO EN SU DISCO DURO), ambos de la librería estándar de Python, para conservarlos más allá de un cierre de sesión para no complicar en exceso nuestro programa: recurriremos únicamente a una lista que construiremos como una variable de clase. En realidad, sendas listas por cada clase TurAlquiler, MonoAlquiler y TodoAlquiler. Tres listas en total:
              • TurAlquiler ⇾ listaturismoalquiler
              • MonoAlquiler ⇾ listamonovolumenalquiler
              • TodoAlquiler ⇾ listatodoterrenoalquiler

Veamos cómo hacerlo:


Así, cada vez que instanciemos un objeto, según sea la clase donde lo hagamos, éste se almacenará en la lista correspondiente almacenando el dato mientras mantengamos abierta nuestra sesión.


Toca comprobar ahora si este remedio de andar por casa, a falta de una buena Base de Datos, y sin recurrir a los módulos shelve y pickle, como ya hemos dicho, nos funciona. Nos basaremos en la clase TurAlquiler e instanciaremos aquí dos objetos que se almacenarán como sendos diccionarios en la lista listaturismoalquiler, que es una variable de clase, repetimos, de la clase TurAlquiler.



Lamentablemente no se aprecia tan bien como quisiéramos. Sin embargo, hay dos partes en la devolución que quisiéramos resaltar: en 1. tenemos una suerte de docstring que se muestra aquí porque ya lo hemos introducido previamente en la modelización de la clase Taller:


Y cuyo resultado se ve así:




      FIJÉMOSNOS QUE EL docstring, A PESAR DE QUE PERTENECE A LA clase Taller, SE MUESTRA EN TODO SU LÉXICO ESPLENDOR NADA MÁS EFECTUAR LA LLAMADA AL MÓDULO CON LA IMPORTACIÓN ABSOLUTA from Vehiculos import *. ESTO OCURRE ASÍ PORQUE CUANDO PYTHON COMIENZA LA LECTURA DE NUESTRO PROGRAMA, LO HACE DE MANERA JERÁRQUICA, DE ARRIBA A ABAJO. ASÍ, LO PRIMERO QUE HACE ES LEER Y EVALUAR LAS clases. SI ÉSTAS SE HAN MODELIZADO CORRECTAMENTE Y ANTES DE SU INICIALIZACIÓN, SI EXISTE ALGÚN docstring O ALGUNA variables de clase, LA "ACTIVA". COMO, EN EFECTO, TENEMOS UNA docstring (Y TAMBIÉN VARIAS variables de clase), LAS "ACTIVA", COMO YA HEMOS DICHO: LA docstring LA VEMOS PORQUE VIENE ENCAPSULADA DENTRO DE UNA FUNCIÓN print(); LAS variables de clase NO PORQUE NO LAS HEMOS PASADO POR ESA MISMA FUNCIÓN print(), Y AUNQUE PERMANEZCAN INVISIBVLES A NUESTROS OJOS... ESTÁN AHÍ, CREEDNOS, ESTÁN AHÍ.


En 2. lo que hacemos es llamar a la variable de clase listaturismoalquiler que, como pertenece a la clase TurAlquiler, debemos invocar de la forma siguiente mediante la sintaxis del punto:

                                                                        TurAlquiler.listaturismoalquiler


Esta llamada nos devolverá la lista que contendrá en formato diccionario todos los objetos turismo que hayamos instanciado a lo largo de una sesión desde la clase TurAlquiler, ojo, mientras dicha sesión continúe abierta.

FORMAS CAPRICHOSAS DE BASALTO, PIEDRA PÓMEZ Y ARENISCA EN EL PARQUE NACIONAL DE LAS CAÑADAS DEL TEIDE, CORAZÓN DE TENERIFE.


Vamos a ver ahora nuestra clase Taller completa.

Primero, nuestra clase Taller se modelizará recibiendo en su zona de herencias una herencia múltiple de las clases TurAlquiler, MonoAlquiler y TodoAlquiler, lo que le permitirá acceder a sus listas respectivas: listaturismoalquiler, listamonovolumenalquiler y listatodoterrenoalquiler, recordemos, todas ellas variables de clase. A continuación procedemos a  la instanciación de los métodos __init__() y mostrar() de nuestra clase Taller. Esta vez, nuestro inicializador __init__() y de manera provisional, tan sólo contendrá la autorreferencia self  y la keyword pass como cuerpo del mismo.


Ahora instanciamos el método estático prompt_init() que contendrá varias modificaciones.




En 1. declaramos un diccionario que almacenamos en la variable seleccion donde a cada clave (key) numérica pasada como cadena o string le asignamos como valor (value) la lista que corresponda según pertenezca a la clase TurAlquiler, a la clase MonoAlquiler o a la clase TodoAlquiler.

En 2., declaramos una lista que asignamos a la variable tipovehiculos y que almacenará precisamente éso: los tipos de vehículos cuyas clases modelizamos en nuestro programa.

En 3. efectuamos un uso básico de la función integrada enumerate() para generar una columna doble que empareja cada término con el índice que ocupa en la lista tipovehiculos con esa misma palabra. Las columnas se imprimen en pantalla para que el usuario pueda efectuar una selección: si quiere consultar los turismos, los monovolúmenes o los todoterrenos.

En 4. declaramos la variable opcion para que el usuario, a través de la función integrada input() introduzca su selección y se almacene correctamente su valor.

A partir de aquí introducimos tres bloques de código, muy similares entre sí, cada uno de los cuales está dedicado a un tipo de vehículo concreto. Todos ellos vienen encabezados por un comentario: # Turismos, # Monovolúmenes y # Todoterrenos, y se "activarán" en función de cuál haya sido nuestra opción ("Turismo", "Monovolumen", "Todoterreno") según el dato almacenado en la variable opcion.

En 5., y tomamos como ejemplo nuestro primer bloque # Turismos, establecemos el condicional (bifurcación de la lectura del código) que se activará si el contenido de la variable opcion es "0" que es la clave (key) que en 1. apuntaba a la lista de turismos que tenemos creada como variable de clase en la clase TurAlquiler. A partir de ahora tendremos acceso a cualquier objeto turismo instanciado en la clase TurAlquiler y cómodamente almacenado en la lista listaturismoalquiler.

En 6. declaramos la variable len_listaturismoalquiler_provisional que generará una lista con los números que van de principio a fin y de uno en uno según sea el tamaño (len()) de la lista listaturismoalquiler, es decir, que si nuestra lista listaturismoalquiler tuviera 5 elementos (instancias u objetos turismo), la lista generada len_listaturismoalquiler_provisional sería así: len_listaturismoalquiler_provisional = (0, 1, 2, 3, 4).

En 7. declaramos una segunda variable a la que denominaremos len_listaturismoalquiler donde, aprovechando la instrucción que aprendimos más arriba, transformamos cada elemento de la lista len_listaturismoalquiler_provisional que, originalmente está formada por enteros, en cadenas:

                                                            len_listaturismoalquiler_provisional = (0, 1, 2, 3, 4)

                                                                                                    ⬇️                                                                                                                                                
                                                                len_listaturismoalquiler = ("0", "1", "2", "3", "4")


En 8. declaramos una nueva variable que llamamos z y que será el resultado de crear un diccionario a través de la función zip() de orden superior, la cual mediante un efecto "cremallera", de aquí el nombre zip, de la función, generamos el diccionario en base a dos iterables, donde el primero proporcionará las claves (len_listaturismoalquiler) mientras que el segundo (TurAlquiler.listaturismoalquiler) serán sus valores respectivos. Así a cada dígito (ítem) de len_listaturismoalquiler le corresponde una instancia u objeto de la clase TurAlquiler.

En 9. volvemos a recurrir a la función integrada enumerate() para que el usuario pueda seleccionar, en esta ocasión, a un objeto concreto, particular, específico de nuestra lista de turismos en alquiler.

En 10. declaramos la variable opcion1 que será la encargada de almacenar precisamente esa selección del usuario.

En 11. declaramos a su vez una nueva variable, que llamamos init_final y que nos permitirá extraer las características (atributos/propiedades) del elemento de la lista listaturismoalquiler que hemos seleccionado a través del método dict.get() de los diccionarios que nos permite extraer el valor asignado a una clave concreta que le pasemos como argumento.

En 12. y bajo la variable d, declaramos un nuevo diccionario donde asignamos valores a sendas variables que declaramos ex profeso: fecha_entrada, itv y estimacion_tiemporev. Todos los valores que se le asignen serán cadenas o strings y, además, deberán cumplir los criterios que señalamos en la docstring para enunciarlos.

Finalmente, en 13. unimos en uno sólo ambos diccionarios, init_final y d, a través del método dict.update() y mostramos el resultado.

La forma de proceder para los dos bloques restantes, # Monovolúmenes y # Todoterrenos, será la misma sólo que cambiando la lista por la que corresponda a cada clase: MonoAlquiler y TodoAlquiler.



Llega el momento de comprobar nuestro código 😨, Instanciaremos dos objetos desde la clase TurAlquiler y mostraremos la lista TurAlquiler.listaturismoalquiler que deberá contener ambos objetos: sendos diccionarios con las características (atributos/propiedades) de cada uno de ellos. A continuación, llamaremos al método prompt_init de la clase Taller y ver qué sucede.


En 1. tenemos nuestra lista TurAlquiler.listaturismoalquiler con las características de los dos objetos que instanciamos previamente.



En efecto, cuando llamamos al método prompt_init de la clase Taller, se muestra un primer cuadro de opciones donde seleccionamos el tipo de vehículo (elegimos, 0, turismos), y una vez efectuamos la selección se muestran los diccionarios que almacenan las características (atributos/propiedades) que definen a cada objeto de la lista TurAlquiler.listaturismoalquiler. En esta ocasión elegimos el primer objeto por el número de su izquierda, 0. Inmediatamente después de efectuar la selección se configura el diccionario d exclusivo de la clase Taller y a cada clave se le asigna su valor. Finalmente se nos muestra el resultado concluyente de la unión (dict.update()) de ambos diccionarios: el seleccionado de la lista TurAlquiler.listaturismoalquiler y d.

Probamos ahora con un objeto instanciado en la clase MonoAlquiler.


Ídem al procedimiento anterior  con la lista MonoAlquiler.listamonovolumenalquiler al final, en 1..


Afortunadamente, 👏👏👏👌👌👌, el resultado es el esperado.


ADVERTENCIA



Hay una cuestión que es importante remarcar. En ninguno de nuestros ejemplos hemos recurrido a instanciar un objeto derivado de las clases, sino que hemos aplicado los métodos directamente, incluido el método especial inicializador  o constructor __init__(). Por este motivo, si llamáramos al método mostrar() nos daría error ya que no existe objeto "real", alguno, vamos a decirlo así, que tenga esas características (atributos/propiedades) que hemos proporcionado.

Si queremos que la cosa funcione de manera correcta, nos basta tan sólo con instanciar un objeto de la clase que seleccionemos y, sobre el mismo, aplicar los distintos métodos que queramos, desde el obligatorio inicializador __init__() hasta el mismo método mostrar(). Vamos a verlo:

A) SIN INSTANCIAR OBJETO ALGUNO DESDE LA CLASE:




B) INSTANCIANDO UN OBJETO A PARTIR DE LA CLASE:




Como podemos comprobar, bastó con instanciar un objeto cualquiera al que denominamos a desde la clase de nuestra elección, en este caso, desde la clase vehiculos, para que el método mostrar() funcione como un reloj.

Aún nos quedaría por modelizar la clase Cartera_Del_Agente, con la que daríamos por finalizado nuestro supuesto 2... Pero no lo vamos a hacer aquí. ¿¡Por quééééé!? Muy sencillo: para que pueda hacer lo que dice poder hacer necesitaríamos una Base de Datos y manejar, dentro de ella, al menos, una tabla de datos con sus registros correspondientes. Entonces, a partir de élla podríamos efectuar un CRUD (Create [crear] Read [leer] Update [actualizar] Delete [borrar]) que son las operaciones básicas que podemos ejecutar con una tabla de registros. 

Como nuestra intención es comenzar a partir del 2022, y si Dios quiere, un nuevo blog dedicado a desarrollar algunos de los módulos de la librería estándar de Python más útiles, entre ellos, y nos comprometemos a que sea el primero, el módulo (paquete, en realidad) squlite3, allí aprovecharemos para completar este pequeño supuesto 2 que, esperamos, os haya servido de muestra sobre cómo podemos manejarnos con lo que hemos aprendido hasta ahora y que, a su vez, aprovechemos nosotros mismos para trastear con él y probar alternativas y cosas nuevas.

A continuación os dejamos el código completo de supuesto 2:

class vehiculos():
    def __init__(self, marca = str, modelo = str, cilindrada = float,
                 tiempo_concesionario = str, caballaje = float, **kwargs):
        super().__init__(**kwargs)
        self.marca = marca
        self.modelo = modelo
        self.cilindrada = cilindrada
        self.tiempo_concesionario = tiempo_concesionario
        self.caballaje = caballaje
        
                     
    def mostrar(self):
        print("CARACTERÍSTICAS DEL VEHICULO")
        print("···············································")
        print("Marca {0}, Modelo {1}".format(self.marca, self.modelo))
        print("Cilindrada {0}, Caballaje {1}".format(self.cilindrada, self.caballaje))
        print("Antigüedad {0}".format(self.tiempo_concesionario))

    @staticmethod
    def prompt_init():
        init = dict(marca=input("MARCA: "),
                    modelo=input("MODELO: "),
                    cilindrada=input("CILINDRADA: "),
                    tiempo_concesionario=input("TIEMPO EN EL CONCESIONARIO: "),
                    caballaje=input("CABALLAJE: "))
        return init


    def input_valido(entrada, opciones_validas):
        entrada += "({})".format((", ".join(opciones_validas)))
        respuesta =input(entrada)
        respuesta.lower()
        while respuesta not in opciones_validas:
            respuesta = input(entrada)
        return respuesta



class turismo(vehiculos):
    numero_puertas = ("3", "5")
    carburante = ("gasolina", "diésel", "híbrido")
    ecoconduccion = ("sí", "no")
    def __init__(self, numero_puertas = str,
                 carburante = str,
                 ecoconduccion = str,
                 **kwargs):
        super().__init__(**kwargs)
        self.numero_puertas = numero_puertas
        self.carburante = carburante
        self.ecoconduccion = ecoconduccion
        
    def mostrar(self):
        super().mostrar()
        print("CARACTERÍSTICAS DEL TURISMO")
        print("···············································")
        print("Número de puertas: {}".format(self.numero_puertas))
        print("Tipo de carburante: {}".format(self.carburante))
        print("Ecoconducción: {}".format(self.ecoconduccion))

    @staticmethod
    def prompt_init():
        parent_init = vehiculos.prompt_init()
        puertas = turismo.input_valido("¿Cuántas puertas tiene el turismo? ", turismo.numero_puertas)
        carburante = turismo.input_valido("¿Qué tipo de carburante lleva el turismo? ", turismo.carburante)
        ecoconduccion = turismo.input_valido("¿Utiliza sistemas de ecoconducción? ", turismo.ecoconduccion)
        parent_init.update({
            "Nº de puertas": puertas,
            "Carburación": carburante,
            "Sistemas de coconduccion" : ecoconduccion
            })
        return parent_init
    
class monovolumen(vehiculos):
    espacio_mcubicos = ("-7m3", "+7m3")
    carburante = ("gasolina", "diésel", "híbrido")
    ecoconduccion = ("sí", "no")
    baca = ("sí", "no")
    def __init__(self, espacio_mcubicos = str,
                 carburante = str,
                 ecoconduccion = str,
                 baca = str,
                 **kwargs):
        super().__init__(**kwargs)
        self.espacio_mcubicos = espacio_mcubicos
        self.carburante = carburante
        self.ecoconduccion = ecoconduccion
        self.baca = baca
        
    def mostrar(self):
        super().mostrar()
        print("CARACTERÍSTICAS DEL MONOVOLUMEN")
        print("·······················································")
        print("Espacio en metros cúbicos del habitáculo: {}".format(self.espacio_mcubicos))
        print("Tipo de carburante: {}".format(self.carburante))
        print("Ecoconducción: {}".format(self.ecoconduccion))
        print("Baca: {}".format(self.baca))

    @staticmethod
    def prompt_init():
        parent_init = vehiculos.prompt_init()
        espacio = monovolumen.input_valido("¿De cuánto espacio dispone el monovolumen? ",  monovolumen.espacio_mcubicos)
        carburante = monovolumen.input_valido("¿Qué tipo de carburante lleva el monovolumen? ", monovolumen.carburante)
        ecoconduccion = monovolumen.input_valido("¿Utiliza sistemas de ecoconducción? ", monovolumen.ecoconduccion)
        baca = monovolumen.input_valido("¿Lleva baca? ", monovolumen.baca)
        parent_init.update({
            "Espacio en metros cúbicos": espacio,
            "Carburación": carburante,
            "Sistemas de coconduccion" : ecoconduccion,
            "Baca": baca
            })
        return parent_init

class todoterreno(vehiculos):
    altura = ("-1.75mts", "(de 1.76 a 1.80mts)", "+1.81mts")
    potencia_plus_motor = ("200cv", "250cv", "300cv")
    carburante = ("gasolina", "diésel", "híbrido")
    ecoconduccion = ("sí", "no")
    baca = ("sí", "no")
    def __init__(self, altura = str,
                 potencia_plus_motor = str,
                 carburante = str,
                 ecoconduccion = str,
                 baca = str,
                 **kwargs):
        super().__init__(**kwargs)
        self.altura = altura
        self.potencia_plus_motor = potencia_plus_motor
        self.carburante = carburante
        self.ecoconduccion = ecoconduccion
        self.baca = baca
        
    def mostrar(self):
        super().mostrar()
        print("CARACTERÍSTICAS DEL TODOTERRENO")
        print("·······················································")
        print("Altura del todoterreno: {}".format(self.altura))
        print("Potencia plus del motor: {}".format(self.potencia_plus_motor))
        print("Tipo de carburante: {}".format(self.carburante))
        print("Ecoconducción: {}".format(self.ecoconduccion))
        print("Baca: {}".format(self.baca))

    @staticmethod
    def prompt_init():
        parent_init = vehiculos.prompt_init()
        altura = todoterreno.input_valido("¿Cuál es la altura del todoterreno? ",  todoterreno.altura)
        potenciaplus = todoterreno.input_valido("¿Cuánta es la potencia plus del todoterreno? ",  todoterreno.potencia_plus_motor)
        carburante = todoterreno.input_valido("¿Qué tipo de carburante lleva el todoterreno? ", todoterreno.carburante)
        ecoconduccion = todoterreno.input_valido("¿Utiliza sistemas de ecoconducción? ", todoterreno.ecoconduccion)
        baca = todoterreno.input_valido("¿Lleva baca? ", monovolumen.baca)
        parent_init.update({
            "Altura": altura,
            "Potencia plus del todoterreno": potenciaplus,
            "Carburación": carburante,
            "Sistemas de coconduccion" : ecoconduccion,
            "Baca": baca
            })
        return parent_init
    
# MODELIZACIÓN DE LAS CLASES venta y alquiler
class venta:
    def __init__(self, precio=float, impuestos=float, **kwargs):
        super().__init__(**kwargs)
        self.precio = precio
        self.impuestos = impuestos
    def mostrar(self):
        print("DETALLES VENTA")
        print("························")
        print("Precio base de venta: {}".format(self.precio))
        print("Impuestos aplicables: {}".format(self.impuestos))
    def calculo(self):
        a = float(self.precio)
        b = float(self.impuestos)
        return a + b
    @staticmethod
    def prompt_init():
        x = dict(
            precio = float(input("Precio de venta: ")),
            impuestos = float(input("Impuestos aplicables: ")))
        print(x)
        a = x.get("precio")
        b = x.get("impuestos")
        return a + b

class alquiler:
    def __init__(self, kilometraje=str, seguro=str, alquiler=str, **kwargs):
        super().__init__(**kwargs)
        self.kilometraje = kilometraje
        self.seguro = seguro
        self.alquiler = alquiler
    def mostrar(self):
        print("DETALLES ALQUILER")
        print("·····························")
        print("Kilometraje: {}".format(self.kilometraje))
        print("Seguro a todo riesgo: {}".format(self.seguro))
        print("Alquiler por día: {}".format(self.alquiler))
    @staticmethod
    def prompt_init():
        return dict(
            kilometraje = input("¿Cuántos kilómetros lleva ya recorridos?: "),
            seguro = input("¿Tiene seguro a todo riesgo?: "),
            alquiler = input("¿A cuánto asciende el alquiler por día?: "))


# CLASES DERIVADAS DE ALQUILER          
class TurAlquiler(alquiler, turismo):
    listaturismoalquiler = []
    @staticmethod
    def prompt_init():
        init = turismo.prompt_init()
        init.update(alquiler.prompt_init())
        TurAlquiler.listaturismoalquiler.append(init)
        return init         

class MonoAlquiler(alquiler, monovolumen):
    listamonovolumenalquiler = []
    @staticmethod
    def prompt_init():
        init = monovolumen.prompt_init()
        init.update(alquiler.prompt_init())
        MonoAlquiler.listamonovolumenalquiler.append(init)
        return init

class TodoAlquiler(alquiler, todoterreno):
    listatodoterrenoalquiler = []
    @staticmethod
    def prompt_init():
        init = todoterreno.prompt_init()
        init.update(alquiler.prompt_init())
        TodoAlquiler.listatodoterrenoalquiler.append(init)
        return init
            
# Modelización de la clase Taller
class Taller(TurAlquiler, MonoAlquiler, TodoAlquiler):
    print("* Si el vehículo está en el taller: \n", 
                   "- fecha_entrada: formato (dd/mm/aaa, por ejemplo, 31/07/2020)\n", 
                   "- itv: sí o no, dependiendo de que la haya pasado durante el año en curso\n", 
                   "- estimacion_tiemporev: número de días que se espera permanezca en el taller\n", 
          "* Si el vehículo no está en el taller:\n",  
                   "- fecha_entrada: escriba 'No se considera'\n", 
                   "- itv: sí o no, dependiendo de que la haya pasado durante el año en curso\n", 
                   "- estimacion_tiemporev: pulse 0")
    
    def __init__(self):
        pass
            
    def mostrar(self):
        print("SITUACIÓN TALLER")
        print("···························")
        print("Fecha de entrada: {}".format(self.fecha_entrada))
        print("ITV: {}".format(self.itv))
        print("Estimación tiempo en el taller: {}".format(str(self.estimacion_tiemporev) + " días"))

    @staticmethod
    def prompt_init():
        seleccion = {
            "0":TurAlquiler.listaturismoalquiler,
            "1":MonoAlquiler.listamonovolumenalquiler,
            "2":TodoAlquiler.listatodoterrenoalquiler}
        tipovehiculos = ["Turismo", "Monovolumen", "Todoterreno"]
        for item, ele in enumerate(tipovehiculos):
            print(item, ele)
            print()
        opcion = input("Seleccione el tipo de vehículo por el número de la izquierda: ")
        # Turismos:
        if opcion == "0":
            len_listaturismoalquiler_provisional =list(range(len(TurAlquiler.listaturismoalquiler)))
            len_listaturismoalquiler = list(map(lambda x: str(x), len_listaturismoalquiler_provisional))
            z = dict(zip(len_listaturismoalquiler, TurAlquiler.listaturismoalquiler))
            for item, ele in enumerate(TurAlquiler.listaturismoalquiler):
                print(item, ele)
                print()
            opcion1 = input("Seleccione el turismo por el número de la izquierda: ")
            init_final = z.get(opcion1)
            d = dict(
            fecha_entrada = input("¿Cuál es la fecha de entrada al taller? "),
            itv = input("¿Ha pasado ya la Inspección Técnica de Vehículos? "),
            estimacion_tiemporev = input("¿Cuánto tiempo se estima  que permanecerá el vehículo en el taller? "))
            init_final.update(d)
        # Monovolúmenes:
        if opcion == "1":
            len_listamonovolumenalquiler_provisional =list(range(len(MonoAlquiler.listamonovolumenalquiler)))
            len_listamonovolumenalquiler = list(map(lambda x: str(x), len_listamonovolumenalquiler_provisional))
            z = dict(zip(len_listamonovolumenalquiler, MonoAlquiler.listamonovolumenalquiler))
            for item, ele in enumerate(MonoAlquiler.listamonovolumenalquiler):
                print(item, ele)
                print()
            opcion1 = input("Seleccione el monovolumen por el número de la izquierda: ")
            init_final = z.get(opcion1)
            d = dict(
            fecha_entrada = input("¿Cuál es la fecha de entrada al taller? "),
            itv = input("¿Ha pasado ya la Inspección Técnica de Vehículos? "),
            estimacion_tiemporev = input("¿Cuánto tiempo se estima  que permanecerá el vehículo en el taller? "))
            init_final.update(d)
        # Todoterrenos:
        if opcion == "2":
            len_listatodoterrenoalquiler_provisional =list(range(len(TodoAlquiler.listatodoterrenoalquiler)))
            len_listatodoterrenoalquiler = list(map(lambda x: str(x), len_listatodoterrenoalquiler_provisional))
            z = dict(zip(len_listatodoterrenoalquiler, TodoAlquiler.listatodoterrenoalquiler))
            for item, ele in enumerate(TodoAlquiler.listatodoterrenoalquiler):
                print(item, ele)
                print()
            opcion1 = input("Seleccione el todoterreno por el número de la izquierda: ")
            init_final = z.get(opcion1)
            d = dict(
            fecha_entrada = input("¿Cuál es la fecha de entrada al taller? "),
            itv = input("¿Ha pasado ya la Inspección Técnica de Vehículos? "),
            estimacion_tiemporev = input("¿Cuánto tiempo se estima  que permanecerá el vehículo en el taller? "))
            init_final.update(d)
        return init_final



TEIDE.





2 comentarios:

  1. No me aburro de revisar lo nuevo y por aprender.
    Saludos GAUBER..
    Saludos desde Chile.
    Laguna Verde.
    Valparaíso.

    ResponderEliminar
    Respuestas
    1. Hola de nuevo, PIPE. Encantado de encontrarlo de nuevo por aquí. Un placer saludarlo. Por cierto, veo que vive en una localidad de ese precioso país hermano, Chile, que comparte nombre con la ciudad donde resido: San Cristóbal de La Laguna.

      Eliminar