miércoles, 3 de marzo de 2010

Clases y objetos en Python, para entendernos.

En Python todo es un objeto, lo que facilita mucho las cosas a la hora de programar. En este post vamos a ver lo sencilo que resulta trabajar con este paradigma de programación.

No voy a hablar aquí de las ventajas de la orientanción a objetos, entre otras cosas porque me llevaría varias horas, y si has llegado hasta aquí, es que algo del tema sabes, seguro.

La mejor forma de ver un tema de estos (y todos creo yo en la informática) es experimentando, así que la teoría la vamos a ver con ejemplos, que es verdaderamente donde vemos la realidad de las cosas. Lo importante es experimentar, ¡sentir como el código fluye por nuestras retinas!.

Es más, la informática, en mi humilde opinión, hay que verla y entenderla desde un punto de vista filosófico, al más puro estilo de David Hume (tan maltratado por la historia), en donde el conocimiento humano proviene de los sentidos, de las percepciones (ideas e impresiones).

«Con el término impresión me refiero a nuestras más vívidas impresiones, cuando oímos, o vemos, o sentimos, o amamos, u odiamos, o deseamos. Y las impresiones se distinguen de las ideas, que son impresiones menos vívidas de las que somos conscientes cuando reflexionamos sobre alguna de las sensaciones anteriormente mencionadas».

«Una proposición que no parece admitir muchas disputas es que todas nuestras ideas no son nada excepto copias de nuestras impresiones, o, en otras palabras, que nos resulta imposible pensar en nada que no hayamos sentido con anterioridad, mediante nuestros sentidos externos o internos».

Una clase en Python se puede definir de la siguiente manera:

class nombre_clase(object):
pass

Esta clase hereda del objecto object, el cual es un objeto vacío. (No voy a hablar aquí de nuevos estilos y demás, ya que no quiero liar este tema). Podemos ver que es una clase que no tiene nada, es una clase vacía. Vamos a incluir un método a esta clase.

class nombre_clase(object):
def metodo_1(self):
pass

Ahora tenemos una clase con un método. Esto es lo que me fascina de Python!!! Dime en qué lenguaje se usa menos código para representar una clase!!!

Bien, ahora, vamos a incluir un constructor, o lo que más se parezca a un constructor de la clase.(Veremos más adelante que técnicamente no es un constructor, pero sí donde podemos poner código de inicialización).

class nombre_clase(object):
def __init__(self):
'''
Constructor
'''
pass

def metodo_1(self):
pass

¿Y un destructor?

class nombre_clase(object):
def __init__(self):
'''
Constructor
'''
pass

def metodo_1(self):
pass

def__del__(self):
'''
Destructor
'''
pass

Lo básico para empezar a trabajar con POO lo acabamos de ver!!! ¿Veis lo sencillo que es definir una clase? Sabemos definir una clase, hemos definido métodos, su constructor (por el momento será el constructor, aunque ténicamente no lo es) y su destructor. Seguimos incluyendo conceptos. Vamos a ver ahora una clase real, y como instanciar una clase, esto es, crear un objeto. Creo que ayer estuve en una tienda de muebles, así que hoy toca tema mueble. Vamos a escribir una clase que defina el concepto de mueble (para el hogar). Definimos la clase mueble:


# Instanciamos la clase (creamos un objeto).
silla = mueble('rojo','madera',[50,60])

Podemos observar, mediante IDLE, los atributos y métodos de nuestra clase. Los atributos y métodos que comienzan por dos guiones bajos (__) son de acceso privado. En Python no hay modificadores de acceso como en C# ó Java (public ó private). Es por ello que IDLE no lo muestra al intentar completar el código.

Sin embargo la encapsulación en Python no está realmente implementada, ya que no son realmente privados. Esto que parece un lío no lo es. Por ejemplo, si después de crear el objeto silla, ejecutamos la sentencia print silla.__precio dará el siguiente error:


Traceback (most recent call last):
File "D:/python/poo/mueble.py", line 42, in

print silla.__precio

AttributeError: 'mueble' object has no attribute '__precio'

Sin embargo, si primero le damos un valor a __precio y luego imprimimos el resultado, ya podemos acceder a él.

>>> silla.__precio = 500
>>> print silla.__precio
500

Otra forma de burlar este mecanismo es aprovecharse del name mangling. Los nombres que empiezan con __ se renombran para incluir el nombre de la clase. Podemos acceder al atributo ó método privado así:

>>> print silla._mueble__precio
500

En el método __init__ incluimos los atributos necesarios para la clase. Podemos dar directamente valores a los atributos.

silla.color = 'verde'

Podemos acceder directamente a un atributo, para obtener información.

>>> print silla.color
verde

Para acceder a un método es tan simple como:

print silla.precio_final()

ó

silla.precio_mueble(1000, 16)

¿self? ¿y esto para qué sirve? Pues para poder referenciar un atributo de la clase. En el __init__ la variable basura es una variable interna dentro del método. No podemos acceder a ella desde el exterior.

>>> print silla.basura

Traceback (most recent call last):
File "D:/python/poo/mueble.py", line 42, in
print silla.basura
AttributeError: 'mueble' object has no attribute 'basura'

Hemos creado un objeto silla a partir de una clase mueble. Pero el concepto de silla tiene quizás más atributos que lo pueden definir mejor. ¿Solución? Crear una clase silla, a partir de la clase mueble, para de esta forma heredar sus atributos y métodos y poder nosotros ampliar sus características. ¿Cómo?

class silla(mueble):
pass

De esta manera ya tenemos una clase silla, que hereda de la clase mueble, esto es, tiene todos sus atributos y métodos. Ampliamos nuestra nueva clase.

Para probar que funciona creamos un objeto y miramos los valores de los atributos:

# Instanciamos.
mueble_silla = silla(4,False,True,'verde','madera',[30,50])

# Imprimimos información.
print "Caracrerísticas del mueble:"
print "Tamaño....: ", mueble_silla.tamanyo
print "Color.....: ", mueble_silla.color
print "Material..: ", mueble_silla.material
print "Nº patas..: ", mueble_silla.patas
print "Respaldo..: ", "Si" if mueble_silla.respaldo else "No"
print "Posabrazos: ", "Si" if mueble_silla.posabrazos else "No"

Herencia múltiple en Python

¿Y si creamos una silla con un cojín? "Queremos crear algo compuesto por dos algos". Y digo "algo" por no llamarlos objetos. Evidentemente estamos hablando de herencia múltiple, esto es, una clase que hereda de varias clases. Si hay métodos ó atributos que se llaman igual, se sobreescriben, esto es, no se pueden ejecutar los dos a la vez, por lo tanto uno de los dos toma el valor. Además, en Python no está implementado el polimorfismo (en el término de sobrecarga de métodos). A ver, pongámonos serios, que esto parece que empieza a liarse. La definición de una clase a partir de otras se hace de forma natural, y los atributos y métodos se van sobreescribiendo de izquierda a derecha, y en profundidad. ¿Cómo ver esto? Veámoslo con el enésimo ejemplo. Primero definimos la clase cojín.

Una vez creada la clase cojín vamos a ver a continuación la creación de la case silla_con_cojin1. Podemos ver en el código la forma tan fácil para crear herencia múltiple. Como he dicho antes, de forma natural, ¡como Python!. Además de crear la clase, creamos un objeto silla_completa1 a partir de dicha clase.

Gracias a IDLE podemos hacer completitud de código. Vemos que aparecen atributos y métodos de ambas clases de las cuales se hereda. Los métodos están todos, a saber, fabricante_mueble, precio_final, precio_mueble y referencia). ¿Y qué pasa con los atributos? Vemos que los únicos que aparecen son el color, material y textura, que aparentemente son de la clase cojin. ¿Es que no se heredan los atributos de la clase silla? La cuestión es que hay que mirar un poco en la creación de la clase silla_con_cojin1. Se define como silla_con_conjin1(cojin, silla), de modo que si lo primero que se ejecuta es lo de más a la izquierda, el constructor que se ejecuta es el de la clase cojin, y es por ello que nada más que vemos atributos de la clase cojin. Si definimos una clase silla_con_cojin2(silla, cojin) tendremos que los atributos que aparecen son los que se inicializan, y como el constructor que se ejecuta es el de la clase silla, pues tendremos sus atributos. Eso es:

¿Cómo solucionamos este dilema? Porque yo quiero que haya atributos de las dos clases. Pues solamente tengo que inicializar los dos constructores de las 2 clases que heredo. ¿Dónde? Pues en la nueva clase que estoy definiendo a partir de las clases de las que quiero heredar. Vamos a verlo ahora mismo:

Y como resultado tenemos, evidentemente:

Darse cuenta de los valores de los atributos color y material. Son los que están por defecto en la creación de la clase cojin. Los demás valores de los atributos son los que están por defecto en la clase silla. Esto es así porque se ha ido de izquierda a derecha, buscando métodos y de abajo hacia arriba en busca de atributos (eso explica que sa haga override en el color del cojin sobre el color de la silla). ¿No me crees? Vale, cambiamos el orden de los __init__ dentro del constructor de la clase silla_con_cojin1. Ya verás que pasa.

Y ahora el resultado es el siguiente:

Se han cargado los atributos (que coinciden en nombre) de la clase silla en vez de los atributos de la clase cojin.

CONCLUSIONES

Como hemos visto trabajar con POO no es complicado, y ahorra mucho tiempo. Si he hecho ejemplos más largos ha sido para que el lector pueda ver que se puede hacer mucho más de lo que se imagina. La herencia múltiple es una herramienta muy poderosa, pero extraordinariamente peligrosa si no se utiliza con propiedad. Esto es, hay que tener ciertas convenciones a la hora de nombrar métodos (para que no se solapen, ¡no hay polimorfismo!) y atributos, por supuesto. Cuando quieras controlar todos los atributos de todas las clases de las cuales se heredan, una solución (yo conozco esta) es inicializar los constructores de las clases deseadas.

8 comentarios:

  1. Esta muy bueno el tuto gracias por compartir !

    ResponderEliminar
  2. . Crea una clase Hora con atributos para las horas, los minutos y los segundos de la hora.
    Incluye, al menos, los siguientes métodos:
    Constructor predeterminado con el 00:00:00 como hora por defecto.
    Constructor parametrizado con horas, minutos y segundos.
    leer(): pedirá al usuario las horas, los minutos y los segundos.
    valida(): comprobará si la hora es correcta; si no lo es la ajustará. Será un método
    auxiliar (privado) que se llamará en el constructor parametrizado y en leer().
    Accedentes y mutadores.
    print(): mostrará la hora (07:03:21).
    aSegundos(): devolverá el número de segundos transcurridos desde la medianoche.
    deSegundos(int): hará que la hora sea la correspondiente a haber transcurrido
    desde la medianoche los segundos que se indiquen.
    segundosDesde(Hora): devolverá el número de segundos entre la hora y la
    proporcionada.
    siguiente(): pasará al segundo siguiente.
    anterior(): pasará al segundo anterior.
    copia(): devolverá un clon de la hora.
    igualQue(Hora): indica si la hora es la misma que la proporcionada.
    menorQue(Hora): indica si la hora es anterior a la proporcionada.
    mayorQue(Hora): indica si la hora es posterior a la proporcionada
    en python

    ResponderEliminar
  3. si alguno tiene el ejercicio me lo puede pasar lo agradesco gracia

    ResponderEliminar
  4. si alguien realizo el ejercicio de carlos me puede hacerme el favor de enviarlo a maesal1997@gmail.com de antemano muchas gracias

    ResponderEliminar