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).

  1. El presupuesto tradicional que venimos generando.
  2. 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.