Archive for the ‘Python’ Category

¿Va a desaparecer Python?

By aaloy on July 29th, 2010

Cuando en algunas empresas dices que vas a utilizar Python para desarrollar su aplicación normalmente te miran raro. No pocas veces es la primera vez que oyen hablar del lenguaje y te pregunta porqué Python y no PHP, “que es lo que utiliza todo el mundo” o Java si estás en un entorno empresarial.

Por una parte comprendo las reticencias. En un país donde se prima ante todo no correr riesgos, hacer lo que todo el mundo hace, incluso cuando signifique no ser competitivo , está bien visto e innovar está mal visto, decir que vas a hacerlo en Python porqué los costes serán menores, la mantenibilidad será mejor y además su aplicación web irá más rápida que en PHP, es un factor de menos peso que el de hacer lo que hacen los demás.

El otro día esta preocupación llegó a niveles alarmantes cuando se nos dijo que según una investigación interna creían que Python desaparecería en un par de años. Obviamente me sentí en el deber moral de tranquilizar a mi interlocutor y presentarle un pequeño inform de porqué podía estar tranquilo, Python no desaparecería en los próximos años.

Como supongo que esta situación se puede volver a repetir y tras petición popular (bueno, una o dos personas, pero soy fácil de convencer) copio mis conclusiones en este apunte, de modo que pueda servir de referencia a otros programadores y empresas que nos dedicamos a la programación en este excelente lenguaje.

El documento podría ampliarse hasta el infinito, hacer referencia y comparaciones con otros lenguajes y frameworks, pero no es ese su objetivo. Se trata de dar una respuesta argumentada al porqué creo que Python no tiene ninguna posibilidad de desaparecer como lenguaje de programación en los próximos años. De hecho tiene menos posibilidades de desaparecer como lenguaje que otros lenguajes comerciales y cerrados, y si nó que se lo pregunten a la gente de Forté tras su compra por parte de Sun, ahora propiedad de Oracle.

Objetivos de este documento

El objetivo de este documento es el de despejar cualquier duda que pueda existir sobre Python como lenguaje de programación con recorrido y futuro. En concreto se trata de establecer el porqué Python no va a desaparecer y porqué vemos que es una plataforma ideal para el desarrollo de sotware.

¿Qué es Python?

Python es un lenguaje de programación de propósito general, interpretado y orientado a objetos, que tanto puede utilizarse para realizar aplicaciones de línea de comandos, aplicaciones de escritorio como aplicaciones web.

Python fue creado por Guido Van Rossum hace más de veinte años. Actualmente Guido sigue implicado en el desarrollo de Python trabajando para Google, que lo considera un lenguaje estratégico en sus desarrollos. El desarrollo de Python es muy estable, se prima sobre todo la claridad del lenguaje y su legibilidad. Se cuida mucho la compatibilidad hacia atrás, filosofía que se ha transmitido también a frameworks como Django.

El futuro de Python está garantizado por la Python Software Fundation  y se realizan numerosas conferencias anuales en distintos paises. La de este año en Europa tuvo lugar el 22 de julio en Birmingham.

Python es un lenguaje multiplataforma, esto significa que se puede ejecutar sobre servidores Linux, Windows, Sun, etc. Incluso se está ejecutando sobre teléfono Android y Nokia.

Python se utiliza desde la programación de sistemas (Linux es un buen ejemplo de ello), cálculo numérico (http://www.enthought.com/) y la programación web, con una gran variedad de librerías y frameworks. Existen ports para Java, .Net que permiten ejecutar el intérprete Python dentro de estas máquinas virtuales.

Por la gran cantidad de librerías que acompañan al lenguaje, se suele decir que Python viene con las baterías incluidas.

La información general sobre Python, origenes y filosofía del lenguaje se puede encontrar en:

http://www.python.org/doc/faq/es/general/

Casos de éxito:

http://www.python.org/about/success/

De entre ellos destacaría:

Google App Engine http://code.google.com/intl/ca-ES/appengine/ que es el entorno de Cloud Computing de Google. En estos momentos soport Java y Python y se lanzó inicialmente en Python. Soporta el framework Django.

Zope http://zope.org Un sompleto servidor de aplicaciones al estilo de Tomcat.

Plone: http://plone.org Un cms escrito sobre la base de Zope que da soporte a multitud de sitos web, desde los de Ubuntu (Canonical) hasta sitios web de NASA, en ambos casos con millones de visitas.

OpenERP: http://www.openerp.com/ Uno de los ERPs más galardonados. Compitiendo de tu a tu con ERPs comerciales en potencia y características.

BitBucket Es un servicio de hosting que da soporte a multitud de programadores y webs. Está escrito en Python y Django. http://code.djangoproject.com/wiki/DjangoSuccessStoryBitbucket

The Washington Post http://projects.washingtonpost.com/congress/ Periódico de gran tirada con una buena parte de sus desarrollos en Django.

The Washington Times http://www.washingtontimes.com desarrolla sobre un stack opensource en Python y Django y ha publicado varios proyectos abiertos en http://opensource.washingtontimes.com/.

EveryBlock http://www.everyblock.com/ Gestor de noticias integrable en un blog desarrollado en Django.

Facebook Aunque la estrucutra principal de Facebook esté hecha en PHP, toda la parte de gestión de mensajes e informcación en tiempo real está programada en Python. Facebook mantiene varios proyectos Python, entre ellos uno altamente estratégico llamado Tornado, que es el servidor web asíncrono que mueve la programación en tiempo real. Más información en:

http://developers.facebook.com/blog/post/301

http://github.com/facebook/tornado

El módulo wsgi de Tornado se integra muy bien con Django y tanto se puede utilizar el sistema de plantillas de Tornado como el de Django.

Quora http://www.quora.com Ha elegido Python y Django sobre otras alternativas. El fundador de Quora, antiguo empleado de Facebook eligió Python sobre PHP por los problemas que tenía que tratar diariamente trabajando con PHP. La entrevista está en http://www.readwriteweb.com/start/2010/07/picking-the-right-programming-language-for-your-startup.php

Mark Lutz en la última edición de su libro Programming Python cita algunas más: Industrial Light & Magic, Pixar, BitTorrent, U.S. National Weather Service, NASA, NSA, Fermi, Corel, Red Hat, Lockheed Martin, … por citar algunas de las más conocidas.

Un ejercicio que resulta de lo más interesante es hacer una búsqueda de la cadena python en una distribución Linux para darse cuenta del papel que juega Python como lenguaje para el desarrollo de funcionalidades y herramientas.

¿Va a desaparecer Python?

Rotundamente no. Python se utiliza en gran cantidad de empresas que lo consideran un lenguaje estratégico para su evolución. Google esponsoriza el desarrollo del lenguaje y utilidades en su Google Summer of code, por ejemplo http://wiki.python.org/moin/SummerOfCode/2010, pero además forma pare del día a día de empresas como IBM, que le ha dedicado numerosos artículos en el developerworks, ActiveState, Aptana, que recientemente contrató a tiempo completo al desarrollador del plugin para Python de Eclipse, Netbeans (Oracle) que cuenta con una rama de Netbeans orientada a Python, Oracle con documentación sobre cómo integrar Python en el acceso a base de datos. Etc.

Una de las comunidades más activas, sin contar la de la propia Python es la de Django. Actualmente la lista de usuarios de Django, alojada en Google Groups cuenta con más de 17.800 subscriptores, lo que da una idea del alcance del framework y por ende del lenguaje.

Otras comunidades de usuarios se centran también en otros aspectos de Python y en distintas librerías. Para las librerías Qt de Nokia http://qt.nokia.com/ ha lanzado un port para Python de dichas librerías llamado PySide http://www.pyside.org/, lo que da un impulso oficial a Python como lenguaje de desarrollo para Qt además de las librerías pyQt que ya existían de manera extraoficial.

Otra comunidad especialmente activa es la de wxPython y las comunidades de Turbogears, Pylons, pyNumeric, etc. etc.

Un lenguaje de programación es tan potente como las librerías que lo acompañan. Python tiene un buen número de librerías y utilidades que se pueden utilizar justo después de instalarlo (de ahí el batteries included del sobrenombre de Python), pero como se puede ver, hay una gran cantidad de librerías y utilidades que abarcan practicamente todos los ámbitos de la programación.

La opción de PHP

PHP es un lenguaje que nos permite desplegar nuestras aplicaciones en servidores de bajo coste. El éxito del PHP se debe no tanto a la potencia del lenguaje sinó a que los requisitos técnicos y de máquina que se necesitan para poner en marcha una aplicación en PHP los hacen ideales para su explotación masiva por parte de los ISPs.

Cuando se trata de una empresa y con los costes acutales del Hosting, lo que debe primar en el desarrollo de nuevas aplicaciones es la mantenibilidad. Salvo que se utilice un framework com Cake http://cakephp.org/ o Symfony http://www.symfony-project.org/ que nos premite separar la programación en distintas capa, los programas en PHP tienden a ser una amalgama de código de negocio embebido en páginas HTML junto con páginas PHP y librerías.

Esto no quiere decir que no haya muy buenos programas escritos en PHP y muy buenos programadores en PHP, el problema es que es muy difícil separar el grano de la paja y PHP no favorece en nada esta separación. Python utilizado junto al framework Django nos propone una separación clara entre capas e impica tener que pensar qué se está haciendo. Esta reflexión nos lleva normalmente a programas más mantenibles.

Python desde siempre ha primado la mantenibilidad y la claridad del lenguaje y frameworks como Django han hecho suyo este lema.

La elección de un framework PHP nos da la separación en capas y la mantenibilidad que buscamos en los proyectos web que deben perdurar en el tiempo, pero con unos costes importantes: un rendimiento mucho menor, pasando de las más de 300 peticiones por segundo que aguanta un proyecto mínimo de Django a las 34 peticiones por segundo del mismo proyecto desarrollado en uno de estos frameworks PHP.

El otro precio a pagar es la cantidad de líneas de código. El ratio tipico de un programa escrito en PHP contra el mismo programa escrito en Python es de 3 a 5 veces menos líneas de codigo. Más líneas de codigo implican más tiempo (y por tanto normalmente un mayor coste para la empresa) pero sobre todo mucho más código para depurar y mantener. Esto se acentúa incluso más si utilizamos un framework y la programación orientada a objetos en PHP, ya que las construcciones y el código tiende a hacerse muy farragosos.

¿Qué significa la adopción de Python?

Apostar por Python y Django para una empresa que quiere comercializar sus productos on-line es apostar por el futuro y la mantenibilidad. Es una decisión estratégica que puede diferenciarnos de los competidores que estén utilizando otras tecnologías.Incluso cuando los tiempos de desarrollo sean comparables (caso de un desarrollo en PHP sin utilizar un framework y Python+Django) la arquitectura que nos proporciona Django hace que las aplicaciones sean más legibles y mantenibles a largo plazo. La reducción en el número de líneas de código respecto PHP y la legibilidad del lenguage hacen que podamos hacer sin mayores dificultades modificaciones a código que hemos desarrollados meses, incluso años atrás.

Python está preparado para la web: librerías de plantillas, librerías soap, xml, conexión con sistemas de mensajería como RabbitMQ o Beanstalk, librerías Rest como django-piston. Al ser un lenguaje de propósito general no estamos limitados a lo que podemos hacer en un servidor web, podemos utilizar Python en nuestros scripts de sistemas, utilizarlo para generar documentación y estadísticas off-line, las posibilidades sólo están limitadas por nuestra imaginación. Optar por Python significa tener al alcance de la mano librerías y aplicaciones de última generación para los propósitos más dispares: desde cálculo numérico al envío masivo de e-mails, desde la monitorización de sistemas hasta el screen scrapping.

La capacidad que tiene Python para lanzar un depurador integrado o remoto, de desplegar aplicaciones remotamente a través de ssh con aplicaciones como fabric o de crear sencillos servicios web con servidores como web.py o Tornado lo hacen una herramienta de productividad sin competencia en estos momentos. Significa, por tnato un factor competitivo importante: reduce los tempos de desarrollo, pero reduce aún más los tiempos de mantenimiento correctivo y evolutivo.

 

Referencias

Python at Google

http://panela.blog-city.com/python_at_google_greg_stein__sdforum.htm

Curso de Python en Google

http://code.google.com/intl/ca-ES/edu/languages/google-python-class/

Google+Python = world domination?

http://techreport.com/discussions.x/16713

Selling Django to your superiors: Success Stories Panel

http://python.mirocommunity.org/video/1205/selling-django-to-your-superio

Entrevista al fundador de Quora

http://www.readwriteweb.com/start/2010/07/picking-the-right-programming-language-for-your-startup.php

Comparando PHP y Python

http://enbeeone3.com/php-vs-python-analysis

Tutorial de Python en IBM Networks para programadores PHP

http://www.ibm.com/developerworks/opensource/library/os-php-pythonbasics/index.html?ca=dgr-twtrPython4PHPdth-OS

Comparación de Django con diversos frameworks PHP

http://trespams.com/2009/05/10/django-vs-php-framewors/

Switching from PHP to Python

http://www.slideshare.net/aezell/switching-from-php-to-python

Comparando Python y PHP

http://wiki.python.org/moin/PythonVsPhp

The Growth of Dynamic Languages 

http://www.activestate.com/blog/2010/07/growth-dynamic-languages-pythonists-pythonistas-and-pythoneers

Comparación de sintaxis entre php, perl, python y ruby

http://hyperpolyglot.wikidot.com/scripting

TIOBE Programming Community Index for July 2010

http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html 

 

Posted in

Creant bits amb Python i Django

By aaloy on December 7th, 2009

El pasado viernes 4 de diciembre, entre las 4 i más de las 9 de la noche tuvo lugar la reunió “fent bits”, dedicada a Python y Django. Nos reunimos más de 25 programadores interesados en Python y Django, en la tecnología y en lo que los lenguajes dinámicos nos pueden ofrecer.

El workshow surgió de un twitt, a partir de ahí generé un apunte en mi blog personal y en poco tiempo había más participantes que disponibilidad de sitio.

La experiencia ha sido fantástica, tanto que por poco que podamos vamos a repetir.

Algunos enlaces del evento:

Se pueden obtener el código de ejemplo a través del repositorio appfusedjango de Google Code y hemos creado un site para la documentación de los ejemplos realizada en Sphinx.

Como incubados en el Parc Bit, el Incubit permite a APSL utilizar (previa reserva) las salas de formación y conferencias. Gracias a esto este evento ha sido posible, ya que el Parc Bit nos ha cedido tanto la sala como el proyector y la valiosa colaboración de Guillem que nos ayudó a con las conexiones de la sala.

Posted in

Sql y Python

By aaloy on September 5th, 2009

Es frecuente en los foros de Python consultas del tipo,

quiero hacer una consulta a una base de datos utilizando Python, lo que hago és:

1
sql = "select * from table where name =" + myvariable
2
cur.execute(sql)

donde myvariable es joe’s 

cómo lo hago?

La consulta en sí es sencilla y las soluciones que normalmente se dan pasan por aconsejar que se escape la variable para no tener problemas con las comillas y soluciones por el estilo.  Sin embargo, constestaciones de este tipo esconden el problema de base: el sql no debe montarse así, o nos encontraremos con situaciones dignas de xkcd.

Vayamos por partes:

Python tiene un interfaz común de bajo nivel, la DB-API que es el mínimo denominador común para el acceso a distintas bases de datos. Una de las funciones de la DB-API es evitar en lo posible los problemas derivados de la inyección de código, de modo que la sentencia execute permite el paso de parámetros y éstos serán escapados antes de la generación del código sql que se enviará a la base de datos. La especificación de la DB-API, permite a los desarrolladores de las librerías utilizar diversos medios para el paso de parámetros, debemos conocer cuál utiliza nuestra librería y emplearlo. Por ejemplo:

1
sql = "select * from table where name ?"
2
cur.execute(sql, myvariable)
3
# otro método
4
sql = "select * from table where name=%(name)s"
5
cur.execute(sql, {'name':myvariable})

Una vez que obtengamos los resultados con fetch(one/many/all) todavía nos quedará tratar con los resultados y pasarlos a objetos Python y un problema añadido, que con el * no controlamos en qué orden nos aparecerán los campos.

Primera cosa entonces:  Los parámetros no se añaden directamente a la cadena sql, se deja su posición y se pasan dentro de la sentencia execute. Hay pocas razones para violar esta regla y ninguna de ellas es válidas si no somos conscientes de los problemas. La razón de “no va a pasar nada”, “a mi me es más fácil así”, no sirven, lo siento.

Por otro lado también debemos preguntarnos si realmente hay una necesidad de escribr el sql “a pelo”. Actualmente existen gran cantidad de libererías que nos permiten olvidarnos del sql y que pasan los resultados directamente a objetos Python, son lo ORM. Los ORM:

  • Nos independizan de la base de datos y de la implementación de la DB-API de nuestra librería.
  • Nos permiten trabajar con objetos en lugar de con sentencias sql, aunque si es necesario nos permiten bajar al nivel de sql que queramos.
  • Escapan los parámetros, evitando ataques de inyección de código.
  • Devuelven objetos Python, ahorrándonos la tediosa conversión de tuplas sql a objetos.

Hay una gran cantidad de ORM para todos los gustos: El ORM de Django, Storm, Autum, SqlRelay, SqlAlchemy, Elixir.  Son algunos de los más populares. Si queremos algo sencillo Autum o Storm nos pueden servir, si estamos trabajando con Django, ya tenemos acceso directamente al ORM y si queremos el ORM más completo y potente del panorama Python SqlAlchemy, complementado o no con Elixir.

Los ORM nos fuerzan a definir primero nuestra estructura de objetos que mapearan contra la BD, pero normalmente la sobrecarga que supone esto es mínima comparada con la mantenibilidad y potencia que nos dan. Por ejemplo, la misma sentencia en Django:

1
reg = Table.objects.get(name=myvariable)

Donde Table representa el objeto que hemos mapeado contra la estructura de la tabla. El orm ya se encargaría de escapar el contenido de la variable y de generar el sql válido para nuestra base de datos. De regalo en reg tenemos un objeto cuyas propiedades son los campos de la base de datos.

Así pues, la recomendación principal es la ir eliminando posibilidades hasta encontrar una que encaje en nuestro problema:

  1. Utilizar un ORM
  2. Utilizar el ORM a bajo nivel para acceder a la API de la libería de base de datos
  3. Utilizar el execute con parámetros
  4. Montar nosotros la sentencia sql escapando siempre los parámetros.
Posted in

Decoradores en Python

By aaloy on August 31st, 2009

¿Qué es un decorador?

Un decorador es el nombre de un patrón de diseño. Los decoradores alteran de manera dinámica la funcionalidad de una función, método o clase sin tener que hacer subclases o cambiar el código fuente de la clase decorada. En el sentido de Python un decorador es algo más, incluye el patrón de diseño, pero va más allá, Bruce Eckel los asimila a las macros de Lisp.

Los decoradores y su utilización en nuestros programas nos ayudan a hacer nuestro código más limpio, a autodocumentarlo y, a diferencia otros lenguajes, no requieren que nos aprendamos otro lenguaje de programación distingo (cómo pasa con las anotaciones de Java por ejemplo). En su utilización podemos simular la programación orientada a aspectos (AOP) o utilizarlos para añadir sistemas de control a nuestras funciones, de log, caché, … Las posibilidades son infinitas.

Los decoradores forman parte de Python desde la versión 2.4 y cómo dice Michele Simionato nos aportan lo siguiente:

  • Reducen el código común y repetitivo (el llamado código boilerplate).
  • Favorecen la separación de responsabilidades del código
  • Aumentan la legibilidad y la mantenibilidad
  • Los decoradores son explícitos.

Clasificación de los decoradores

Podemos dividir los decoradores en grupos:

  • Según los parámetros que admiten:
    • No admiten parámetros
    • Sí admiten parámetros
  •  Según si preservan la firma o signatura del método al que decoran:
    • Decoradores no que preservan la firma
    • Decoradores que sí la preservan

Los decoradores más sencillos son aquellos que no admiten parámetros y no preservan la firma

Un decorador que no hace nada

Para empezar crearemos un decorador que el que hará se convertir cualquier función en un /dev/null, es decir, no devolverá nada y no hará nada con la función. Llamaremos a nuestro decorador forat_negre, agujero negro.

1
def forat_negre(f):     
2
    def none():
3
         pass     
4
    return none 
5
@forat_negre 
6
 
7
def di_hola():
8
     return "hola"

Si ejecutamos di_hola() no tendremos resultado alguno, mejor dicho, tendremos `None` como resultado de la función que estamos decorando.
La sintaxis @ del decorador de Python es lo que se denomina syntactic sugar, es decir, una manera de escribir las cosas que aumenta la legibilitat del código. Sin embargo, debemos tener presente que el decorador se podría haber escrito como:

1
di_hola = forat_negre(di_hola)
2
    di_hola()

y tendríamos el mismo decorador. Recordemos que las funciones son objetos de primera clase en PYthon y que se pueden asignar y pasar como parámetros.

Aunque el ejemplo es muy sencillo nos sirve para ver lo siguiente:
Un decorador no es más que un envoltorio de una función y por lo tanto tiene que devolver una función, más concretamente un _callable_, para entendernos, cualquier cosa que poniendo un doble paréntesis al lado () no pete.

01
def retorna_objecte(f):
02
   ....:     def obj():
03
   ....:         return object()
04
   ....:     return obj
05
   ....:
06
 
07
In [17]: def di_hola():
08
   ....:     return "Hola"
09
   ....:
10
 
11
In [18]: di_hola = retorna_objecte(di_hola)
12
 
13
In [19]: di_hola()
14
Out[19]: <object object at 0xf7f745e8>
15

A nuestro decorardor `forat_negre` le hemos pasado una función sin parámetros. Si intentamos pasarle una función con parámetros nos encontraremos con una pequeña sorpresa…

1
@forat_negre
2
def suma(a,b):
3
return a,b
4
 
5
suma(2,3)
6
 
7
TypeError Traceback (most recent call last)
8
TypeError: none() takes no arguments (2 given)
9

que por otro lado es del todo normal, hemos definido el `agujero_negro` de tal manera que devuelve una función sin parámetros, así que si le intentamos pasar los parámetros que tenía la función decorada sencillamente se queja y peta.

Vamos a definir un poco mejor nuestro decorador para que esto no nos pase y que pueda admitir al menos tantos parámetros como la función que decora.

01
def forat_negre(f):
02
    "d'aquí no surt res"
03
    def none(*args, **kw_args):
04
        pass
05
    return none
06
 
07
@forat_negre
08
def suma(a,b):
09
    "suma dos parametres qualsevols si pot"
10
    return a+b
11
 
12
suma(2,2)
13

Ahora ya no da error. Así pues tenemos *otra conclusión*: además de devolver una función, tenemos que procurar que la definición de las función que devolvemos admita al menos el mismo número de parámetros que la función que queremos decorar. Si no sabemos cuántos son estos nos curamos en salud con *args* y *kw_args*.

Fijémonos que no hemos mantenido la firma de la función. Si como experimento intentáis hacer un `help(suma)` no obtenemos la ayuda de la función original. Volveremos sobre esto un poco más adelante. Por ahora ya sabemos como crear decoradores simples a partir de una función.

Haciendo decoradores no intrusivos

Si habéis hecho un `help(suma)` o un `suma.__name__` quizás alguno se habrá sorprendido al ver que el nombre de la función es `_none_` en lugar de la esperada `suma`. Si repasáis lo que hemos hecho tampoco es de extrañar: hemos sustituido la función original por otra. Recordamos que el decorador f aplicado sobre la función g es equivalente a hacer g = f(g).

Lo aconsejable sería que el decorador fuera capaz de mantener la documentación y el nombre de la función que decora, ya que de este modo se simplificaría el uso de la función y los autocompletadores de código no se vuelverían tan locos.

Esto lo podemos hacer de dos maneras: la larga y la corta

La manera larga

1
def forat_negre(f):
2
    def none(*args, **kw_args):
3
        pass
4
    none.__doc__= f.__doc__
5
    none.__dict__= f.__dict__
6
    none.__name__= f.__name__
7
    return none
8

Con las tres instrucciones adicionales que hemos puesto volvemos a recuperar los metadatos de la función original que pasamos al decorador. Si ahora hacemos un `help` veremos que èste devuelve el nombre de la función correcta __suma__ y que la ayuda también es la suya.

1
Help on function suma in module __main__:
2
 
3
suma(*args, **kw_args)
4
    Suma dos parametres qualsevols si pot
5

La manera corta

Preservar los metadatos es bastante útil y común, de hecho en el módulo `functools` encontramos la función `wraps` que es en sí misma un decorador y que hace precisamente ésto. De este modo el código anterior quedaría:

1
from functools import wraps
2
 
3
def forat_negre(f):
4
    @wraps(f)
5
    def none(*args, **kw_args):
6
        pass
7
    return none
8

Observemos que hemos utilizado un decorador para crear otro decorador. Veremos más utilización de decoradores un poco más adelante.

Un decorador con argumentos

El decorador que hemos programado en el apartado anterior era bastante simple, hacía muy poca cosa y no tenía parámetros. Si queremos crear decoradores tenemos que hacer primero de todo que sean útiles, y también nos encontraremos con la necesidad de que estos decoradores admitan parámetros.

En Django, por ejemplo, podéis encontrar que el decorador de cache admite parámetros que nos permiten decirle durante cuánto tiempo tiene que cachear los resultados, o el decorador `vary_on_headers`, que nos permite modificar el contenido de la respuesta de las vistas añadiendo las cabeceras que indicamos.

Vamos a ver como lo podemos conseguir nosotros. También hay dos maneras de hacerlo, la clara y la compleja. La manera clara es la que recomendamos y utiliza una clase para hacer el decorador, la compleja requiere más esfuerza para entender qué está haciendo el decorador, es más corta, pero personalmente prefiero un código más legible.

Los decoradores que hemos programado como funciones se pueden crear también como clases, pero en este caso, creo que la definición en forma de funciones es más sencilla de seguir, y nos permitirá distinguir claramente entre los dos tipos de decoradores: los que no admiten parámetros que se construyen preferentemente mediante funciones y los que admiten parámetros, que se construyen preferentemente usando clases.

Para seguir con el agujero negro, ahora en nuestro ejemplo haremos que se muestre el resultado de la funcion que decoramos o no de manera aleatoria. Pora ello pasaremos al decorador una función como parámetro que al ser ejecutada determinará si se tiene que mostrar el resultado de la función decorada o no

El método claro de hacer decoradores con argumentos

01
#!/usr/bin/env python
02
# -*- coding: UTF-8 -*-
03
import random
04
 
05
class forat_negre_sonat(object):
06
    "Un decorador amb fam"
07
    def __init__(self, mostrar):
08
        self.mostrar = mostrar
09
 
10
    def __call__(self, f):
11
        def none(*args, **kw_args):
12
            if self.mostrar():
13
                return f(*args, **kw_args)
14
            else:
15
                return "Nop"
16
        return none
17
 
18
@forat_negre_sonat(mostrar = lambda :random.choice((True, False)))
19
def suma(a, b):
20
    "Suma dos elements que li passam com a paràmetre"
21
    return a+b
22
 
23
if __name__=="__main__":
24
    print suma(2,3)
25
    print suma(5,6)
26
    print suma(9,5)
27

Observemos el código:

1. Hemos creado una clase Python que a su constructor (el `__init__`) puede tomar el parámetro o parámetros que queramos. Es un constructor normal, así que admite parámetros por defecto.

2. Recordemos que hemos dicho que el decorador tiene que ser un objeto llamable (callable), en una clase, la _llamabilidad_ la da el método `__call__`. Esta clase la definiremos de forma que obtenga la función a decorar com un parámetro. De este modo tenemos acceso tanto a los parámetros del decorador, que hemos pasado al constructor, como a la función decorada, que hemos pasado como parámetro al call.

Después de esto ya sólo queda encapsular la llamada como lo hacíamos al caso anterior, devolviendo el decorador en lugar de la función a decorar.

En el ejemplo además, he intentado mostrar que el parámetro puede ser el que nosotros queramos, en concreto he pasado una función anónima, creada con `lambda` que es la que se encarga de establecer la aleatoriedad del resultado.

Si queréis podemos hacer este decorador un poco más completo, haciendo que admita además de funciones valores y que preserve el nombre y documentación de la función decorada.

01
#!/usr/bin/env python
02
# -*- coding: UTF-8 -*-
03
import random
04
 
05
class forat_negre_sonat(object):
06
    "Un decorador amb fam"
07
    def __init__(self, mostrar=None):
08
        self.mostrar = mostrar
09
 
10
    def __call__(self, f):
11
        def none(*args, **kw_args):
12
            if callable(self.mostrar):
13
                opcion = self.mostrar()
14
            else:
15
                opcion = self.mostrar
16
            if opcion:
17
                return f(*args, **kw_args)
18
            else:
19
                return "Nop"
20
        none.__name__ = f.__name__
21
        none.__doc__ = f.__doc__
22
        return none
23
 
24
@forat_negre_sonat(mostrar = lambda :random.choice((True, False)))
25
def suma(a, b):
26
    "Suma dos elements que li passam com a paràmetre"
27
    return a+b
28
 
29
@forat_negre_sonat(mostrar=True)
30
def resta(a,b):
31
    return a-b
32
 
33
if __name__=="__main__":
34
    print "Exemple amb %s " % suma.__name__
35
    print suma(2,3)
36
    print suma(5,6)
37
    print suma(9,5)
38
    print "Exemple amb %s " % resta.__name__
39
    print resta(2,3)
40
    print resta(5,6)
41

El método complejo de crear decoradores con parámetros

01
def forat_negre_dos(mostrar):
02
    def wrap(f):
03
        @wraps(f)
04
        def wrapped_function(*args, **kw_args):
05
            if callable(mostrar):
06
                opcion = mostrar()
07
            else:
08
                opcion = mostrar
09
            if opcion:
10
                return f(*args, **kw_args)
11
            else:
12
                return "Nop"
13
        return wrapped_function
14
    return wrap
15

Bien, enrevesado, lo que se dice enrevesado no lo es, ya que una cosa tan simple no tiene demasiada historia, pero fijaos que el codigo se sigue bastante peor.

El primero que hemos hecho es definir nuestra función, donde hemos puesto los parámetros que admite. Esta función devuelve otra función que admite un argumento, que es la función a decorar, que a su vez admite un número indeterminado de argumentos (recordemos que esto lo estamos forzando nosotros).

Cómo la segunda función, `wrapped_function` está definida dentro de `wrap`, tiene acceso al parámetro del decorador y puede utilizarlo.

Encadenando decoradores

Los decoradores se pueden encadenar, es decir, una función puede tener tantos decoradores como haga falta y necesitemos, sólo limitados por nuestro sentido común y la legibilidad del programa. Dos decoradores son habituales, tres no se ven mucho, cuatro o más son para pensárselo.

Para el ejemplo tomaré prestado Python Decorator Library uno de los decoradors más útiles, el memoize, que nos permite cachear una función a partir de sus parámetros. La implementación que hace Python Decorator Library del patrón memoize bastante es sencilla de seguir con lo que ahora sabemos y además nos servirá para completar la construcción de decoradores sin parámetros usando una clase.

01
class memoized(object):
02
   """Decorator that caches a function's return value each time it is called.
03
   If called later with the same arguments, the cached value is returned, and
04
   not re-evaluated.
05
   """
06
   def __init__(self, func):
07
      self.func = func
08
      self.cache = {}
09
   def __call__(self, *args):
10
      try:
11
         return self.cache[args]
12
      except KeyError:
13
         self.cache[args] = value = self.func(*args)
14
         return value
15
      except TypeError:
16
         # uncachable -- for instance, passing a list as an argument.
17
         # Better to not cache than to blow up entirely.
18
         return self.func(*args)
19
   def __repr__(self):
20
      """Return the function's docstring."""
21
      return self.func.__doc__
22

A diferencia de la construcción con parámetros, en el constructor de la clase memoized se pone como parámetro la función a decorar, y al método __call__ los parámetros de la función, en lugar de la función a decorar como se hacía al otro método.

¿Por qué se ha usado esta manera si la otra es más sencilla? Pues porqué necesitamos mantener en memoria la caché y lo que se hace es mantenerla en un diccionario dentro de la misma clase. Si la caché fuera externa (con memcached por ejemplo), se habría podido hacer perfectamente en forma de función.

Además definiremos un decorador que nos servirar para indicar cuando entramos a la función y comprobar el decorador memoized.

01
def log(f):
02
    "Registra l'execució de la funció"
03
    def wrap(*args):
04
        print "Excutant %s, args: %s" % \\
05
           (f.__name__, ",".join(str(x) for x in args))
06
        return f(*args)
07
    return wrap
08
 
09
@memoized
10
@log
11
def fibonacci(n):
12
    "Return the nth fibonacci number."
13
    if n in (0, 1):
14
        return n
15
    return fibonacci(n-1) + fibonacci(n-2)
16
 
17
print fibonacci(12)
18

Probad de ejecutar este código con y sin la función memoized. Con los dos decoradors activos veréis que cada decorador toma como entrada la función ya decorada que sale del decorador que tiene más abajo. Así el memoized coge como entrada la función fibonacci ya decorada con el log.

Podéis hacer la prueba con un ejemplo más simple:

01
#!/usr/bin/env python
02
# -*- coding: UTF-8 -*-
03
 
04
def uppercase(f):
05
    "Dada una función f que devuelve un string lo pasa todo a mayúsculas"
06
    def wrap():
07
        return f().upper()
08
    return wrap
09
 
10
def make_bold(f):
11
    "Dada una función f que devuelve un string le añade los tags de bold"
12
    def wrap():
13
        return "<strong>%s</strong>" % f()
14
    return wrap
15
 
16
@make_bold
17
@uppercase
18
def say_hello():
19
    return "Hello world"
20
 
21
print say_hello()
22

Probad cambiando la orden de los decoradores y veréis perfectamente como se van aplicando éstos desde la función hacia arriba. En el ejemplo primero se convierte el “Hello word” a mayúsculas y después se le aplican los tags de negrita.

La signatura pendiente

Antes de acabar nos queda un tema pendiente: la firma. Los decoradors que hemos creado pueden preservar el nombre y la documentación de la función que decoran, pero no preservan la firma, es decir, el número de parámetros que le pasamos.

Michele Simionato ha escrito un módulo excelente llamado *decorator* que extiende la utilizació de los decoradores, mateniendo la firma de la función, el nombre y la documentación, y además nos da la posibilidad de crear factorías de decoradors. Una herramienta para tener siempre a mano. Con este módulo podríamos escribir el código del ejemplo anterior cómo:

01
from decorator import decorator
02
@decorator
03
def uppercase(f, *args):
04
    "Donada una funció f que retorna un string ho passa a majúscules"
05
    return f(*args).upper()
06
 
07
@decorator
08
def make_bold(f, *args):
09
    "Afegeix el tag strong a la sortida de la funció"
10
    return "<strong>%s</strong>" % f(*args)
11
 
12
@uppercase
13
@make_bold
14
def say_hello(nom):
15
    "Di hola, home!"
16
    return "Hello world %s" % nom
17
 
18
if __name__=="__main__":
19
    from inspect import getargspec
20
    print say_hello('World')
21
    print say_hello.func_name
22
    print say_hello.__doc__
23
    print getargspec(say_hello)
24

Si ejecutáis el código podréis ver que no nos ha hecho falta recurrir a wraps o a reasignar el nombre, la propia librería de Simionato lo ha hecho. Además, si nos fijamos en la salida del ejemplo:

HELLO WORLD WORLD
say_hello
Di hola, home!
ArgSpec(args=['nom'], varargs=None, keywords=None, defaults=None)

La primera línea corresponde a la salida de la función que hemos decorado. La segunda es el nombre de dicha función. Vemos el nombre de la función original y no la del decorador. La documentación también se ha mantenido y para acabar, podemos ver que la firma de la función es correcta, nos dice que tiene un argumento obligatorio llamado `nom`.

Conclusión

Espero haber dejado un poco más claro el tema de los decoradores. Crearlos no es difícil, utilizarlos es todavía más simple, sólo tenemos que tener claro qué son y cuando usarlos. Son una herramienta potente que nos permite hacer nuestro código más legible y cohesionado. Sin miedo y a disfrutar con los decoradores!.

Cómo todo en esta vida, usadlos con sentido común y moderación.

Referencias

Para escribir este artículo me he basado en distintas fuentes, las más interesantes las cito a continuación:

PEP 318
Decorators I : Introduction to Python Decorators
Decorators II: Decorator Arguments
Python Decorators
Understanding decorators
Charming Python: Decorators make magic easy
Decorator 3.1.2
Decorator Pattern
Python decorator Library

Posted in

Hello world!

By admin on August 9th, 2009

from itertools import cycle
greeting = ['Hello', 'World!', '\n']
for word in cycle(greeting):
print word,

Posted in