Clases en Python

Las clases en programación son un mecanismo diseñado para asemejarnos a los objetos de la vida real. P.e. un Televisor o TV, tiene atributos como color, alto, ancho y puede realizar tareas como encenderse, apagarse, programarse, dañarse, entre otros.

En Python podemos decir que sirve para definir tipos por el usuario. Python define sus clases con la palabra reservada “class” seguida de su nombre, P.e “class Imp: “. Python no cuenta con constructores y destructores explícitos, pero sí con un método reservado para simular al constructor “__init__“.

Inicialización de una clase:

Para inicializar una clase se usa el método especial __init__.

>>> class Imp:
... def __init__(self, msg):
... self.mensaje = msg
... 
>>>

Instanciando una clase:

Para instanciar una clase, simplemente invoque a la clase como si fuera una función, pasando los argumentos que defina el método __init__. El valor de retorno será el objeto recién creado.

>>> # Instanciamos la clase
... c = Imp('bueno')
>>> c
<__main__.Imp instance at 0xb74b28cc>
>>>

Cada instancia de una clase contiene un atributo __class__, que es el objeto de su clase. Además de __class___ existen otros atributos incorporados para los objetos en python como: __name__, __bases__. (Mirar: http://docs.python.org/library/stdtypes.html#specialattrs)

>>> c.__class__
<class __main__.Imp at 0xb734026c>
>>>

Destrucción de Instancias:

En Python la destrucción de un objeto se realiza automáticamente cuando las variables a las que se le asigna la instancia salen del ámbito de ejecución.

Atributos.

Los atributos los podemos definir como en el ejemplo de la TV, como los valores por defecto de nuestra clase u objeto y que son imprescindibles para poder crearlo. En Python existen dos tipos de atributos para los objetos definidos por los usuarios (class).

Atributos de Datos:

Son las variables que serán creadas para cada instancia de una clase. Para nuestra clase anteriormente creada hemos definido un atributo de dato “mensaje”; que cada instancia de “Imp” tendrá como “atributo de dato”. Por convención todos los atributos de datos se inicializan en el método __init__. Sin embargo, esto no es un requisito, ya que los atributos de datos como las variables locales solo comienzan a existir cuando se les asigna su primer valor.

Es una buena práctica asignar un valor inicial a todos los “atributos de datos” de una instancia en el método __init__. Le quitará horas de depuración más adelante, en busca de excepciones AttributeError debido a que está haciendo referencia a atributos sin inicializar (y por tanto inexistentes).

Referenciar un Atributo de Dato:

Para hacer referencia a un “atributo de dato” dentro de la clase, se debe usar self como calificador, “self.mensaje”, y para hacer referencia desde fuera de la clase a un atributo de dato se usa el nombre de la instancia como calificado, “instancia.mensaje”.

self:

Es el primer argumento de cada método de una clase (incluido __init__), siempre es una referencia a la instancia actual de la clase. En el método __init__, self hace referencia al objeto recién creado; y en otros métodos de la clase, se refiere a la instancia cuyo método ha sido llamado.

Aunque necesita especificar “self” de forma explícita cuando define un método, no se especifica al invocar el método; Python lo añadirá de forma automática.

 >>> # Invocamos el argumento 'mensaje'
 ... c.mensaje
 'bueno'
 >>>

Métodos.

Los métodos los podemos definir como las tareas que puede realizar nuestro objeto. Según nuestro ejemplo de la TV, un método podría ser “apagar”. El cual nos podría servir para programar la apagada de nuestro TV.

Método __init__:

El método __init__ puede tomar cualquier cantidad de argumentos (e igual que las funciones, éstos pueden definirse con valores por defecto) y se llama inmediatamente y se crea una instancia de la clase. __init__ se tiende a confundir con el constructor por ser la primera parte de código que se ejecuta en una instancia de la clase recién creada. Pero no se debe confundir con el constructor ya que el objeto ya ha sido construido para cuando se llama a __init__. y ya tiene una referencia válida a la nueva instancia de la clase. Pero __init__ es lo más parecido a un constructor que va a encontrar en Python, y cumple el mismo papel. Como observación a tener en cuenta sobre el método __init__ es que éste nunca devuelve un valor.

Creando Una clase funcional:

 >>>
 >>> class Udict:
 ... def __init__(self, dict=None):
 ... self.data = {}
 ... if dict is not None: self.update(dict)
 ... def update(self, dict=None):
 ... self.data.update(dict)
 ... print self.data
 ...
 >>>
 >>> d = {1:'uno', 2:'dos'}
 >>> g = Udict(d)
 {1: 'uno', 2: 'dos'}
 >>>
 >>> g.data
 {1: 'uno', 2: 'dos'}
 >>>
 >>> c = Udict()
 >>> c.data
 {}
 >>>

Atributos de Clases.

Python también admite los atributos de clase, que son variables que pertenecen a la clase en sí. La diferencia más importante entre los atributos de clase y los atributos de datos, es que, y ya que, los “atributos de clase” pertenecen a la clase, existen o están disponibles antes de crear cualquier instancia de la clase, y los “atributos de datos” solo están disponibles en el momento que se instancia la clase.

Los “atributos de clase” sólo se pueden declarar inmediatamente en la definición de la clase, y los “atributos de datos”, como ya se mencionó se definen en el método __init__.

 >>>
 >>> class Udict:
 ... template = [("nombre", None), ("nit", None)]
 ... def __init__(self, dict=None):
 ... self.data = {}
 ... if dict is not None: self.update(dict)
 ... def update(self, dict=None): self.data.update(dict)
 ...
 >>> Udict
 <class __main__.Udict at 0xb74a132c>
 >>> Udict.template
 [('nombre', None), ('nit', None)]
 >>> Udict.data
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 AttributeError: class Udict has no attribute 'data'
 >>> u = Udict({1:'uno'})
 >>> u.template
 [('nombre', None), ('nit', None)]
 >>> u.data
 {1: 'uno'}
 >>>
 >>> Udict.data
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 AttributeError: class Udict has no attribute 'data'
 >>>

Otra característica importante de los “atributos de clase”, es que estos están disponibles haciendo referencia directa a la clase, como a cualquiera de sus instancias.

Los atributos de clase se pueden usar como constantes de la clase, pero no son constantes realmente. También puede cambiarlas.

Jugando con los atributos de clase:

 >>>
 >>> class udict:
 ... template = [("nombre", None), ("nit", None)]
 ... def __init__(self, d=None):
 ... self.data = {}
 ... if d is not None:
 ... self.update(d)
 ... else:
 ... self.update(dict(self.__class__.template))
 ... def update(self, d=None):
 ... self.data.update(d)
 ... # Podemos modificar a "self.template", más no a "self.__class__.template"
 ... self.template = dict(self.__class__.template)
 ... self.template.update(d)
 ...
 >>>
 >>> udict
 <class __main__.udict at 0xb74fc2cc>
 >>> udict.template
 [('nombre', None), ('nit', None)]
 >>> u = udict()
 >>> u
 <__main__.udict instance at 0xb74fbb0c>
 >>> u.template
 {'nombre': None, 'nit': None}
 >>> u.data
 {'nombre': None, 'nit': None}
 >>> udict
 <class __main__.udict at 0xb74fc2cc>
 >>> u.__class__
 <class __main__.udict at 0xb74fc2cc>
 >>> u.__class__.template
 [('nombre', None), ('nit', None)]
 >>> u.__class__.data
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 AttributeError: class udict has no attribute 'data'
 >>>
 >>> j = udict({'telefono':None})
 >>> j.data
 {'telefono': None}
 >>> j.template
 {'nombre': None, 'telefono': None, 'nit': None}
 >>> j.__class__.template # Funciona como una constante de la clase
 [('nombre', None), ('nit', None)]
 >>> udict.template
 [('nombre', None), ('nit', None)]
 >>>

Como podemos notar, crear una instancia de la clase llama al método __init__, que modifica el atributo de la clase. Esto afecta a la clase en sí, no sólo a la instancia recién creada.

__class__ es un atributo incorporado de cada instancia de la clase (de toda clase). Es una referencia a la clase de la que es instancia self (en este caso, la clase Udict).

Sigamos jugando:

Veamos que los “atributos de clase” no son constantes.

 >>>
 >>> class counter:
 ... count = 0
 ... def __init__(self):
 ... self.__class__.count += 1
 ...
 >>>
 >>> counter
 <class __main__.counter at 0xb73a026c>
 >>> counter.count
 0
 >>> c = counter()
 >>> c.count
 1
 >>> d = counter()
 >>> d.count
 2
 >>> d.__class__.count
 2
 >>> counter.count
 2
 >>> counter.count + 1
 3
 >>> counter.count += 1
 >>> counter.count
 3
 >>>
 >>> f = counter()
 >>> f.count
 4
 >>> f.__class__.count
 4
 >>>

Crear una segunda instancia incrementará el atributo de clase count de nuevo. Advierta cómo la clase y sus instancias comparten el atributo de clase.

Funciones, Métodos y Argumentos Privados:

Como otros lenguaje de programación, Python tiene el concepto de elementos privados.

Funciones privadas, que no se pueden invocar desde fuera de su módulo.
Métodos privados de clase, que no se pueden invocar desde fuera de su clase.
Atributos probados, que no se pueden acceder desde fuera de su clase.

Para poder definir funciones, métodos o atributos privados en Python, se debe anteponer su nombre con “dos guiones bajos” (__).

 >>> class Imp:
 ... def __init__(self, msg):
 ... self.mensaje = msg
 ... def __imp(self):
 ... return self.mensaje
 ...
 >>>
 >>> i = Imp()
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 TypeError: __init__() takes exactly 2 arguments (1 given)
 >>> i = Imp('bien')
 >>> i
 <__main__.Imp instance at 0xb74566ec>
 >>> i.imp()
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 AttributeError: Imp instance has no attribute 'imp'
 >>> i.__imp()
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 AttributeError: Imp instance has no attribute '__imp'

Pero en Python no es totalmente cierto que no podamos acceder a ellos desde fuera de su ámbito.

 >>> dir(i)
 ['_Imp__imp', '__doc__', '__init__', '__module__', 'mensaje']
 >>>
 >>> i._Imp__imp()
 'bien'
 >>>

Entienda esto como detalle interesante, pero prometa que nunca, nunca, lo hará en código de verdad. Los métodos privados lo son por alguna razón, pero como muchas otras cosas en Python, su privacidad es cuestión de convención, no forzada.

 

Autor: Jolth

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s