Herencia, relación entre dos o más clases
Y ya hemos llegado al Capítulo VI de la Guía Python! En el capítulo de hoy, agregaremos nueva funcionalidad a nuestro programa generador de presupuestos, introduciendo con ello, un nuevo concepto de la programación orientada a objetos: herencia.
Y ya hemos llegado al Capítulo VI de la Guía Python!
En el capítulo de hoy, agregaremos nueva funcionalidad a nuestro programa generador de presupuestos, introduciendo con ello, un nuevo concepto de la programación orientada a objetos: herencia.
Herencia
En Programación Orientada a Objetos, la herencia es la relación existente entre dos o más clases. La herencia marca una relación de jerarquía entre objetos, en la cual, una clase principal (madre) puede ser heredada por otras
secundarias (hijas), las cuales adquieren “por herencia” los métodos y atributos de la primera (clase principal).
- El presupuesto tradicional que venimos generando.
- Un presupuesto extendido, que emita los recibos de pagos correspondientes, calculando el monto de adelanto y su resto.
Archivos Necesarios
Utilizaremos todos los archivos disponibles en la carpeta “capitulo6“. En esta carpeta, encontraremos las siguientes novedades:
Un nuevo sub-directorio llamado recibos
En caso que decidamos ejecutar el generador de presupuestos en modo avanzado, éste, automáticamente creará y guardará los nuevos recibos en esta carpeta.
Nuevo template recibo.txt
Este archivo es un nuevo template, el cual emplearemos como plantilla para generar los recibos.
================================================================================ $titulo Nº $numero/1 ================================================================================ En $ciudad a los ___ días del mes de ___________ de 20__ RECIBÍ de $cliente La cantidad de $moneda $pago_1 En concepto de pago adelanto presupuesto Nº $numero.- ________________________ $nombre $web ================================================================================ -------------------------- >>>> cortar aquí <<<< ------------------------------ ================================================================================ $titulo Nº $numero/2 ================================================================================ En $ciudad a los ___ días del mes de ___________ de 20__ RECIBÍ de $cliente La cantidad de $moneda $pago_2 En concepto de pago finalización de obra según presupuesto Nº $numero.- ________________________ $nombre $web ================================================================================
Un nuevo módulo: recibo.py
Este será el archivo en el que más nos concentraremos hoy. Un modelo que hereda de Presupuesto, generando un presupuesto extendido que incluye formulario de recibo de pago.
# -*- coding: utf-8 *-* from string import Template from presupuesto import Presupuesto from constantes import TEMPLATE_RECIBO, CIUDAD, MONEDA_DENOMINACION from helpers import leer_archivo class PresupuestoConRecibo(Presupuesto): def __init__(self): Presupuesto.__init__(self) self.adelanto = 40 self.titulo = "RECIBO" self.generar_recibo() def calcular_pagos(self): """Calcula el monto correspondiente al adelanto y resto del trabajo""" total = self.neto self.pago_1 = total * self.adelanto / 100 self.pago_2 = total - self.pago_1 def generar_recibo(self): """Genera los recibos para entregar al cliente""" self.calcular_pagos() txt = leer_archivo(TEMPLATE_RECIBO) diccionario = dict(titulo=self.titulo, numero=self.numero_presupuesto, ciudad=CIUDAD, cliente=self.cliente, moneda=MONEDA_DENOMINACION, pago_1=self.pago_1, nombre=Presupuesto.encabezado_nombre, web=Presupuesto.encabezado_web, pago_2=self.pago_2) txt = Template(txt).safe_substitute(diccionario) self.guardar_recibo(txt) def guardar_recibo(self, contenido): """Guarda el recibo generado en la carpeta recibos Argumentos: contenido -- template renderizado """ filename = 'recibos/' + str(self.numero_presupuesto) + '.txt' recibo = open(filename, 'w') recibo.write(contenido) recibo.close()
run.py
Este archivo, será el que desde ahora en más ejecutemos de la línea de comandos en lugar de presupuesto.py. Veremos como, pasándole un parámetro determinado por línea de comando, se encargará de ejecutar uno u otro módulo (Presupuesto o PresupuestoConRecibo).
# -*- coding: utf-8 *-* import sys from presupuesto import Presupuesto from recibo import PresupuestoConRecibo modelo = sys.argv[1] if modelo == 'basico': presupuesto = Presupuesto() elif modelo == 'recibo': presupuesto = PresupuestoConRecibo() else: print "Argumentos no válidos"
Ese extraño archivo con extensión .nja
El Capítulo VI de la Guía Python, lo he desarrollado con el IDE Open Source, NINJA-IDE.
El archivo Guia_Python.nja será opcionalmente necesario, si deseas utilizar Ninja-IDE para esta etapa del proyecto. El archivo .nja te evitará configurar el proyecto, pero es opcional su descarga.
Archivos modificados
presupuesto.py
– Código estandarizado según PEP 8
– Se elimina además, la instancia a Presupuesto incorporándola en run.py
– Se limpia el método __init__()
– Ahora, Presupuesto hereda de object (ver explicación más adelante)
constantes.py
– Incorpora nuevas constantes
Razonamiento lógico de la herencia
Como bien se comentó al principio, la herencia es una de las características que define al paradigma de la Programación Orientada a Objetos (POO), estableciendo la forma en la cual, dos o más clases se relacionan entre sí.
Cuando una clase hereda de otra, ésta, adquiere de forma automática, los métodos y atributos de la clase de la cual hereda.
Existe una lógica relacional en la herencia de clases. Una clase no debe heredar al azar de otra, sino que debe existir una relación real.
Llevado a un ejemplo de la vida diaria, podríamos tener una clase principal llamada Persona, que sea heredada por la clase Hombre y por la clase Mujer. Hombre y Mujer, tendrían los mismos atributos que Persona (extremidades superiores, inferiores, órganos vitales), pero cada una tendría atributos propios que las distinguen entre sí y a la vez extienden a Persona (órganos reproductores, genes). De la misma manera, compartirían los mismas métodos que Persona (caminar, correr, comer), pero cada una, tendrían sus propios métodos distintivos (no, no me pidan ejemplos, usen la imaginación!!!!).
Sin embargo, no sería relacionalmente lógico, que Perro herede de persona. Si bien puede tener atributos y métodos que a simple vista resultan similares (correr, comer, caminar) no es una clase de Persona sino de Animal.
La Herencia en Python
En Python, para indicar que una clase hereda de otra, se utiliza la siguiente sintaxis:
class NombreDeLaClaseHija(NombreDeLaClaseMadre):
Cuando una clase es principal (una clase madre), debe heredar de object
:
class Presupuesto(object):
Nuestro nuevo módulo recibo.py, hereda todos los atributos y métodos de presupuesto:
class PresupuestoConRecibo(Presupuesto):
Además, de poder definir métodos y atributos propios que extenderán las características de Presupuesto.
Accediendo a métodos y atributos
Para acceder a las propiedades de clase, es decir, aquellos atributos definidos en la propia clase ANTES de ser instanciada, se utiliza:
NombreDeLaClase.nombre_del_atributo
Sin embargo, si se desea acceder a propiedades del objeto, es decir, a aquellos atributos definidos LUEGO de crear una instancia de la clase, se utiliza:
self.nombre_del_atributo
Es decir, que dado el siguiente código:
class ClaseMadre(object): atributo_de_clase = 'valor' def __init__(self): self.metodo() def metodo(self): self.atributo_del_objeto = 'otro valor'
Si heredo esta clase, por otra:
class ClaseHija(ClaseMadre):
Para acceder a “atributo_de_la_clase” dentro de ClaseHija, tendré que hacerlo mediante:
print ClaseMadre.atributo_de_la_clase
Aunque también es posible, acceder mediante self:
print self.atributo_de_la_clase
Mientras que para acceder a “atributo_del_objeto”, primero se debe haber ejecutado el método que define dicha propiedad, es decir metodo():
class ClaseHija(ClaseMadre): def __init__(self): ClaseMadre.__init__(self)
Para luego acceder a dicho atributo, mediante self:
print self.atributo_del_objeto
Sin embargo, podré acceder a cualquier método heredado, utilizando self directamente:
class ClaseMadre(object): atributo_de_clase = 'valor' def __init__(self): self.metodo() def metodo(self): self.atributo_del_objeto = 'otro valor' def segundo_metodo(self): print 'Hola Mundo' class ClaseHija(ClaseMadre): def __init__(self): ClaseMadre.__init__(self) self.otro_metodo() def otro_metodo(self): print ClaseMadre.atributo_de_clase print self.atributo_del_objeto self.segundo_metodo()
Pasando parámetros a un archivo .py por línea de comandos
Desde ahora, para ejecutar nuestro programa, ya no tendremos que hacer python presupuesto.py, sino:
python run.py argumento
Donde argumento podrá ser: basico, quien ejecutará el módulo Presupuesto de la misma forma que en el Capítulo V o recibo, el cual ejecutará el Presupuesto extendido con la generación de recibos de pago.
Si ejecutas por línea de comandos:
presupuesto run.py recibo
Al finalizar, en la nueva carpeta “recibos” se habrá generado un TXT con el mismo número que el presupuesto creado, conteniendo dos recibos de pago para imprimir.
En cambios, si ejecutas:
presupuesto run.py basico
No habrá diferencia con lo que hemos hecho hasta el capítulo anterior.
Capturando argumentos enviados por línea de comandos (en run.py)
import sys modelo = sys.argv[1]
sys es un módulo estándar de Python que provee de funciones específicas del sistema (ampliar información).
argv recoge una lista de parámetros que son pasados por línea de comandos cuanso se ejecuta mediante python archivo.py argumentos.
El primer elemento de la lista argv, es decir argv[0] es el nombre del archivo. En nuestro código, accedemos directamente al segundo elemento de la lista: sys.argv[1] el cuál nos dirá qué opción hemos elegido. Si optamos por básico, crearemos una instancia de Presupuesto()
if modelo == 'basico': presupuesto = Presupuesto()
En cambio, si hemos indicado “recibo” obtendremos una instancia de PresupuestoConRecibo()
elif modelo == 'recibo': presupuesto = PresupuestoConRecibo()
De lo contrario, se imprimirá un mensaje de error:
else: print "Argumentos no válidos"
Ver más información sobre paso y captura de argumentos por línea de comando.
El desafío de hoy
Nos estamos poniendo cada vez más exigentes con nuestro código. En el capítulo anterior, nos tocó hacer un refactoring para estandarizar el código con la normativa de la PEP 8.
En el capítulo de hoy, el reto es doble.
Desafío #1:
Prueba a ejecutar el módulo run.py sin pasar ningún argumento:
python run.py
¿Te animas a solucionar el inconveniente con la ayuda del tutorial oficial de Python?
Desafío #2
El nuevo módulo recibo.py posee un método para guardar el recibo generado, muy similar al método que utiliza el módulo Presupuesto para guardar el presupuesto en un archivo HTML. Muchas líneas de código se repiten, lo cual, incurre en una redundancia innecesaria que puede ser evitada. ¿Qué ideas se te ocurren para solucionar este código redundante? No es necesario que escribas código. El objetivo es que entre todos razonemos y hagamos una lluvia de ideas que nos ponga en práctica.
Xelente….En cuanto salga de la escuela me pongo a escudriñar lo nuevo de este capitulo….Gracias a STHEPHANIEFALLA por atender el llamado en el twitter. Y a Eugenia por el tiempo de las 14 horas dedicado a este capitulo….
Lastima que ahora no lo pueda leer entero, pero igualmente esta re bueno!!
reee graciassss :D, llego tarde.. pero mereció la pena la espera 😛
Un Saludo!
vi la guia duarante clases y es ta muy interesante tanto asi que no prese atencion al profesor jeje… al == que Gume saliendo de clases practicare para no perder la costumbre. Gracias Eugenia.
umm pq no lo puedes leer entero???
muy excelente el tutorial de ahora, sigue asi Eugenia, y exito en todo.
Saludos.
Se ve bonito el codigo, me dare un tiempo este fin de semana para meterle mano al codigo awwwnnnn 😛 …. De igual manera gracias a las señorita Eugenia por el tiempo dedicado, que por cierto esta muy guapa jejeje 😀 … Saludos !!!
Hola buenas tardes, vengo siguiente este tutorial desde hace un par de días y me parece muy bien el esfuerzo que esta realizando Eugenia. Felicidades por eso.
Soy nuevo en python y he aprendido muchisimo en estas 6 entregas, aunque en esta ultima aun no la he estudiado a fondo como con las otras 5.
Con respecto al Primer desafio creo que se podria solucionar preguntando primero la longitud de ARGV ya siempre tendrá como valor minimo 1 elemento.
Para el Segundo Desafio, propongo realizar una clase externa en donde se pueda procesar todo lo relacionado a lectura, escritura de archivos, así se puede usar también para el contador y para guardar los datos.
Como comente esta entrega aun no la he estudiado del todo bien, por si algo que comento ya esta implementado, una disculpa.
Saludos
[…] de algunos martes ausentes ya tenemos la sexta entrega de la excelente guía que nos regala Maestrosdelweb para aprender a programar en Python. En esta […]
Hola Eugenia, que gusto saludarte y claro a todos mis compañeros de este seminario de Python.. cada vez se pone más emocionante esto. Te dejo el link de Run.py ya corregido para no mande el error al correr sin ningun parámetro. (–espero este bien….)-:
run.py
http://pastebin.com/aJcun50E
Con el segundo reto se me ocurre crear un modulo (“o procedimiento”) por aparte y llamarlo cada vez que sea necesario — así no repetiremos las líneas de codigo cada vez que queramos hacer la misma tarea. – – Bueno lo voy a poner prueba a ver que tal me va..
Gracias Christian!
Hola lfjaimesb!
Desafío #1: genial! ¿Te animás a codearlo?
Desafío #2: sobre la propuesta de realizar una clase…
Teniendo en cuenta que una clase es la abstracción de un objeto (un “molde” para crear objetos con los mismos atributos y funciones):
¿La abstracción de que objetos sería esa clase?
¿Cuáles serían sus atributos y sus métodos?
[…] capítulo: Herencia, relación entre dos o más clases → […]
Hola drick!!
Desafío #1: excelente!
Aprovecho esto para comentar una pequeña cuestión técnica:
Si bien sabemos que sys.argv traerá consigo al menos 1 parámetro, en la programación, todo razonamiento requiere de abstracción. Al momento de razonar la forma de solucionar un enunciado, debemos lograr “abstraernos” por completo de aquello que es tangible.
Debemos olvidarnos que CONOCEMOS a sys.argv y solo debemos concentrarnos, en su tipo: una lista.
Si quiero acceder al segundo elemento de una lista (index 1) ¿que debo validar realmente?
Imaginemos la siguiente lista:
mi_lista = []
Supongamos que esa lista es sys.argv
Al validar que len(mi_lista) sea igual a uno, no estoy considerando la posibilidad de que len(mi_lista) sea igual a cero (o que también sea igual a 7!)
Es entonces cuando hay que llevar la abstracción a un nivel más abajo, pues las posibilidades de len(mi_lista) serán infinitas. So… si tengo que acceder al índice 1 (segundo elemento de la lista), la única posibilidad cierta que puedo validar, es que len(mi_lista) sea igual a 2 o distinta. Y con eso, estaré contemplando el infinito de posibilidades:
si len(mi_lista) == 2: ejecuto el código
sino: contemplo las infinitas posibilidades anunciando que “no es lo que se esperaba”.
Si bien preguntar en este caso, si len(sys.argv) es igual a 1, funciona, es más propicio formularlo de manera abstracta olvidando que conocemos su contenido. Bajar un nivel de abstracción, hasta llegar a su tipo. Y luego, bajar otro nivel de abstracción, calculando la cantidad de posibilidades sobre lo que necesito.
¿Se entendió o te dejé mareado? Reconozco que es más simple explicarlo con una pizarra!!
Desafío #2: ¿módulo o procedimiento? Y ya que a vos “te tengo visto” 😀 de los otros capítulos, te pregunto ¿descartarías convertir el método en un helper?
PD: si algo de lo que dije sobre el desafío #1 no se entendió, a postear las dudas, ya que me parece sumamente importante que se comprenda
Saludetes!!
no entendí la explicación hasta que vi la solución que ha dado drick y pensé que podia fallar esa solución.
la mia es que he creado unas lineas con try y except para que en caso de que no sea ninguna de las dos opciones “basico” o “recibo” salte el error y except se ocupe de darle un valor vacío para que pueda continuar el codigo sin saltarme un error.
http://pastebin.com/vZfnv2RC
Buen día Eugenia, felicidades por tu curso y experiencia con la herramienta alternativa a los inflados Java y Net.
Me puedes orientar acerca de que herramientas tendria que usar para desarrollar un software de escritorio (ventanas) que sea funcional tanto en windows como en linux ?
La base de datos que tengo que usar desde ya es MySql.
Gracias.
[…] las cuales adquieren “por herencia” los métodos y atributos de la primera (clase principal). Descargar este archivo [!] Enlace roto Procesando el […]
Hola buen dia Eugenia, muchas gracias por tomar un poco de tu tiempo para compartrnos un tus conocimientos apartir de este cuso 😀 del cual he estado aprendiedo mucho, ademas de que lo haz hecho muy entretenido, asi que intente responder el reto
Para le primer reto tomando en cuenta tu sugerencia de la ejecucion de modulos hice lo siguiente:
http://pastebin.com/dQLkrLrX
Para el segundo reto la idea que se me ocurre es generar un metodo a la clase madre el cual se llame guardar_archivo(_self, nombre_archivo, extension)
Hola Eugenia.. gracias por contestarme, tienes razón con el desafío # 1, practicamente deje un vacío total que podría hacer que el el sistema fuera algo inestable pues si el usuairo ingresa más de 1 argumento sistema siempre lo tomará como bueno, es decir si ingresa 3 argumentos estaría bien para el sistema, pero para nosotros no…..(jijij.. que error…jijij).. ahora con lo del desafío dos.. disculpa confundi los lenguales de programación tienes razón al hablar de modulo o procedimiento quería referirme a un helper para luego llamarlo .. aunque creo que me falto explicación.. (disculpa por esto)…. GRACIAS POR TODO he aprendido bastante…!!!
Hola Benito.. disculpa.. fijate que estuve probando tu código.. y creo que le diste una solución muy buena al programa.. °°° te aplaudo..°°°.. pero fijate que creo que cometiste el mismo error que tu servidor.. pues el programa solo valida si el argumento 1 no es vació, y por lo tanto si el usuario ingresa 3 0 más argumentos el programa lo tomará como válido (cosa que no es)….
Te dejo el link del run… ya corregido… (espero este bien..jijij)
http://pastebin.com/N4qsp6uH ….
Hola Eugenia, para el reto #2 opino que se podría implementar un método en la colección de métodos helpers.py e importarlo en la clase recibo.
[…] Bahit para Maestros del Web.Agrega tu comentario | Enlace permanente al […]
[…] HERENCIA, RELACIÓN ENTRE DOS O MÁS CLASES http://www.maestrosdelweb.com/editorial/guia-python-herencia-relacion-clases/ […]