Python avanzado
Tras el post de Python básico viene el de Python avanzado. Sin embargo, la diferencia entre uno y otro es bastante grande. Advierto a los novatos que esta parte es mucho más difícil. Me he planteado hacer un “Python intermedio”, pero creo que esa parte la dará la experiencia. Recomiendo practicar haciendo pequeños programas en Python antes de intentar abordar esta parte.
Si alguien tiene dudas, siempre puede volver a consultar el artículo de Python básico.
Espero haber cubierto suficiente materia como para que queden claros los conceptos principales.
Métodos mágicos
Existen algunos métodos que nos permiten alterar el comportamiento habitual de nuestras clases. Por ejemplo, está el método __getattr__
, que se ejecutará cuando tratemos de acceder a un atributo que no exista y, por lo tanto, podremos alterar el comportamiento habitual que consiste en lanzar una excepción:
|
|
Algunos de estos métodos mágicos son:
__getattr__
se ejecuta al acceder a un atributo inexistente.__setattr__
se ejecuta al intentar modificar un atributo inexistente.__delattr__
se ejecuta al eliminar el atributo.__getattribute__
se ejecuta antes de intentar acceder a cualquier atributo.__new__
se llama antes de crear un objeto de la clase, cuando el objeto aún no existe.__init__
se llama justo después de crear el objeto. Es el equivalente a un constructor.__del__
se llama justo antes de destruir el objeto. Sin embargo, la destrucción de un objeto puede no realizarse al invocar al métododel
.__repr__
debe devolver una representación del objeto, ya que se llamará porrepr()
.__str__
es una representación “informal” del objeto, y se llamará constr()
o conprint()
__lt__
,__le__
,__eq__
,__ne__
,__gt__
,__ge__
permiten sobreescribir las comparaciones, aunque también se puede utilizar:__cmp__
, para realizar comparaciones__hash__
debe devolver un identificador único para el objeto
Otros métodos mágicos nos sirven para permitir que nuestras clases se comporten como listas o diccionarios:
__len__
se consulta al ejecutarlen()
__getitem__
llamado al evaluarself[key]
__setitem__
llamado al asignar aself[key]
__delitem__
llamado condel(self[key])
__iter__
solicita un iterador.__reversed__
iterador inverso.
Hay muchos, MUCHOS métodos mágicos. Algunos son para secuencias, otros para contenedores, y otros para operaciones matemáticas. Podéis consultar la lista completa en el Python Data Model
Veamos ahora el uso práctico que pueden tener algunos de ellos:
Atributos bajo demanda
En python todo funciona como si fuera una tabla Hash o, en nomenclatura más pythónica, un diccionario. Existe un método mágico llamado __dict__
, que es un atributo de sólo lectura que contine los valores del resto de atributos de un objeto.
Y podemos aprovecharnos de esta propiedad para hacer objetos que “mutan” de acuerdo a su inicialización:
|
|
El ejemplo actual crea una clase cuyos objetos contendrán como atributos cualquier cosa que se haya pasado como atributos nombrados:
|
|
Diccionarios que se comportan como objetos
Otra característica que puede sernos de utilidad es tener un diccionario que se comporta como un objeto, es decir, que permite acceder a sus elementos como atributos. Esta característica puede hacer nuestro código más fácil de leer:
|
|
Ahora podemos acceder al objeto como diccionario u objeto:
|
|
Esto tiene una limitación: como los nombres de variables no pueden comenzar por números, contener espacios ni otros signos de puntuación, es posible que nunca podamos acceder a algunos valores:
|
|
Objetos iguales
La igualdad entre objetos es relativa. En una ocasión necesité (haciendo un pequeño entorno de pruebas) un objeto que me permitiera insertar cualquier cosa. La solución habitual es ésta:
|
|
Sin embargo hay otra forma más bonita de hacer las cosas:
|
|
En este segundo caso no es necesario comprobar si vale un valor concreto, ya que la propia comparación resolverá el problema. Limpio y sencillo.
Esta solución permite crear otro tipo de objetos interesantes, como StringContaining
, PositiveNumber
y otros objetos curiosos:
|
|
Singleton
Estos métodos mágicos también pueden utilizarse para implementar el patrón Singleton. Este patrón es bastante controvertido, ya que las probabilidades de usarlo bien son más bien reducidas.
Es precisamente por esa razón que no lo explicaré aquí.
Decoradores
Existe la posibilidad de crear funciones que se ejecutarán con otras funciones. A esto se le denomina decorators. Tanto en Python como en Java comienzan por una arroba (@
).
Hay distintas maneras de crear un decorator. La más sencilla es crear una función que devuelve una función:
|
|
En este pequeño programa se crea un decorator llamado timed
. Posteriormente se crea un método example
empleando el decorator. La salida de la llamada final será:
|
|
Conviene fijarse en ciertos aspectos:
- No confío en el número de argumentos que puedan llegar al decorator. Habitualmente se procederá así.
- No confío en que el método no lance excepciones, y las dejo pasar.
- No interfiero en el resultado del método.
Los decoradores son muy potentes, pero hay que manejarlos con cabeza, ya que pueden tener demasiada “magia”. Un ejemplo de lo que NO DEBE HACERSE BAJO NINGÚN CONCEPTO:
|
|
Estoy convencido que, si habéis llegado hasta aquí, sabéis por qué no debe hacerse. A este error se le suele denominar “catch pokémon”, y cometerlo en un decorador puede ser terriblemente difícil de encontrar. Ya sabéis: un gran poder conlleva una gran responsabilidad.
@staticmethod y @classmethod
Hay algunos decoradores definidos en la librería estándar, como son @staticmethod
y @classmethod
. ¿Cuándo se debe usar cada uno de ellos?
El primero, @staticmethod
, se utiliza para crear métodos que no requieren nada de la clase principal.
El segundo, @classmethod
, recibirá la clase como primer argumento, por si se necesita utilizar para algo. Se suele llamar cls
por convención.
Como norma general, no los utilicéis. He aquí una regla con los métodos estáticos: si tienes un método estático que llama a otro método estático, plantéate que puedes estar haciéndolo mal (habrá casos justificados, claro está).
Ambos tienen un uso muy interesante a la hora de crear Factories:
|
|
A veces también nos pueden valer para crear Builders, pero no debería ser lo habitual:
|
|
Propiedades
Hay lenguajes que animan a construir modelos anémicos, es decir, clases que sólo contienen getters y setters. En Python esto está completamente desaconsejado, ya que tenemos as properties.
Básicamente, una property consiste en un getter y/o setter que se maneja como un atributo. Esto permite transformar un atributo en una property en el momento en que es necesario, de manera que el uso siga siendo el mismo.
Veamos un ejemplo:
|
|
Supongamos que tenemos la clase Example
con el atributo var
. Por requisitos del programa, queremos que var
devuelva siempre el doble de su valor. Eso es un problema, ya que es un atributo… o no:
|
|
Aquí han ocurrido varias cosas: lo primero hemos renombrado la variable var
para que sea diferente. A menudo se empleará un underscore para indicar que es una variable privada (aunque en Python no hay nada privado realmente, es sólo notación). A continuación se crea la función con el getter y, finalmente, se crea la property con el nombre que tenía antes la variable.
Desde ese momento se ejecutará el getter cuando se intente acceder a la propiedad.
La función builtin property
tiene los siguientes argumentos:
|
|
De manera que nos permite sobreescribir el getter, setter, el destructor o la documentación.
Properties y decorators
Existe otra manera más sencilla de hacer lo mismo pero con decorators. Así, éste es el mismo ejemplo:
|
|
Los métodos del decorador serían setter
, getter
y deleter
, por lo que podemos hacer lo mismo que con property
.
Generadores
Finalmente, y como concepto avanzado, aquí están los generadores. Consisten en funciones que producen elementos, pero la función queda en memoria para poder producir más elementos. Es el caso de la función range
(en versiones anteriores a la 3.0 era xrange
), que podría implementarse así:
|
|
Veamos un ejemplo más sencillo aún: lo mismo, pero sin enrevesar los argumentos:
|
|
Como puede observarse, se utiliza la cláusula yield
. Ésta es como un return
, pero la función no termina. De esta manera se pueden recorrer todos los elementos. Puede haber elementos infinitos.
Esto, junto con algunos principios más, da lugar a la programación funcional.
With
Existe un elemento más muy curioso: with
. A menudo resulta difícil de entender cuando es muy sencillo: simplemente invoca a los métodos __enter__
y __exit__
al comienzo y final del bloque, respectivamente. Esto da lugar a situaciones muy divertidas.
Por ejemplo, el método open
abre un archivo, pero el objeto que devuelve soporta estas operaciones. Por eso podemos abrir un archivo de la siguiente manera, con la seguridad de que siempre se cerrará:
|
|
El equivalente (más o menos) sin el with
sería algo así:
|
|
Como puede observarse, mucho más complejo.
Partials
A menudo es necesario escribir el mismo método varias veces cambiando únicamente un parámetro. En estos casos, podemos sobreescribirlo de una manera mucho más sencilla, creando una función igual pero con uno de los parámetros fijos. A esto es a lo que se conoce como partial method.
Esta utilidad se encuentra en el módulo functool. Veamos un ejemplo:
|
|
Como se ve en el ejemplo, b
es una función parcial de f
, en la que tenemos algunos métodos predefinidos. Es decir, sería equivalente a:
|
|
Archivos autoejecutables
Python dispone de un fuerte soporte para archivos comprimidos Zip. Podemos hacer la siguiente prueba: Escribid en un archivo llamado __main__.py la línea print "hello world"
. Ahora podemos comprimirlo con zip
y tratamos de ejecutarlo:
|
|
¡Magia! El archivo se ha ejecutado como si fuera un único archivo python.
Diversión
Python se desarrolla por la comunidad, y a la comunidad le gusta divertirse… Por eso hay escondidos algunos “Easter Eggs”.
El primero de ellos sirve para mostrar “El Zen de Python”. Basta con ejecutar:
|
|
El segundo surgió de una broma de XKCD, donde se mencionaba Python. Si veis la broma entenderéis por qué se lanza así:
|
|
Más información
El mejor sitio para obtener información básica de este lenguaje sigue siendo la propia documentación de Python.
Y vuelvo a recomendar los libros Dive into Python, de Mark Pilgrim, que es gratuíto y se puede descargar de esa misma dirección y también Pro Python, de Marty Alchin.