Decora tu código (Python decorators)

Los decoradores de Python ofrecen una forma elegante de aplicar wrappers a clases, métodos y propiedades.

La sintaxis es la siguiente:

>>> @decorador>>> def funcion_decorada():>>>     return 'resultado'>>>>>> print funcion_decorada()resultado>>>

y básicamente, significa lo mismo que el siguiente trozo de código:

>>> def funcion():>>>     return 'resultado'>>>>>> funcion_decorada = decorador(funcion)>>>>>> print funcion_decorada()resultado>>>

Un decorador es una función que:

  • – recibe una función como parámetro y
  • – devuelve otra función como resultado.

Completando nuestro primer ejemplo, el siguiente decorador:

>>> def decorador(funcion):>>>>>>    def nuevaFuncion(*args, **kwargs):>>>        print 'pamplinas'>>>        return funcion(*args, **kwargs)>>>>>>    return nuevaFuncion>>>

modifica el comportamiento de nuestra función de la siguiente manera:

>>> print funcion_decorada()pamplinasresultado>>>  

Un aplicación típica de los decoradores es en la definición de métodos de clase. Tradicionalmente se ha utilizado esta sintáxis:

>>> def metodo(self):>>>     return 'resultado'>>>>>> metodo_de_clase = classmethod(metodo)>>>

Si el cuerpo del método es muy largo, puede quedarnos poco legible el código. Los decoradores nos permiten abreviar bastante:

>>> @classmethod>>> def metodo_de_clase(self):>>>     return 'resultado'

Los decoradores tienen numerosas aplicaciones, por ejemplo, podemos implementar una política de “Dreprecation Warnings” de la siguiente manera:

>>> import warnings>>>>>> def deprecated(funcion):>>> >>>     def nuevaFuncion(*args, **kwargs):>>>         warnings.warn(funcion.__name__, DeprecationWarning, stacklevel=2)>>>         return funcion(*args, **kwargs)>>> >>>     nuevaFuncion.__name__ = funcion.__name__>>>     nuevaFuncion.__doc__ = funcion.__doc__>>>     nuevaFuncion.__dict__.update(funcion.__dict__)>>>     return nuevaFuncion>>> >>> >>> class MiClase:>>> >>>      @deprecated>>>      def metodo_anticuado(self):>>>          return 'resultado'>>> >>> miClase = MiClase()>>>>>> r = miClase.metodo_anticuado()DeprecationWarning: metodo_anticuado>>>

Referencias:

Anuncios

Python es cuentión de huevos (python eggs)

El índice de paquetes de Python, o Cheese Shop como coloquialmente se le conoce, es el repositorio central donde la comunidad de programadores de Python pueden intercambiar sus módulos.

El repositorio está repleto de huevos de Python (Python eggs), que en realidad son módulos de Python empaquetados con diversa información como los módulos de los que depende, la documentación relativa al software o la versión de la que se trata.

El enfoque es muy similar al aplicado por dpkg en Debian o el CPAN en Perl.

He estado leyendo unos cuantos sitios donde se describe en profundidad la técnica y me he animado a hacer mi pequeña aportación a la tienda de quesos.

Para construir un huevo de Python, utilizamos el módulo “setuptools”, que extiende la funcionalidad del veterano “distutils”, utilizado tradicionalmente para la distribución de paquetes Python.

Antes de convertir nuestro código fuente Python en un huevo hay que preparar convenientemente el fichero “setup.py” con el que distribuiremos nuestro paquete. Básicamente, se trata de invocar a la función “setup()” de “setuptools” en lugar de a la de “distutils”. Debemos poner especial atención en los parámetros que escribimos, pues configuran la forma en compartimos nuestro código.

import ez_setupez_setup.use_setuptools()
from setuptools import setup, find_packages
VERSION='0.2'
DESCRIPTION='Wrapper for www.nestoria.co.uk API'
LONG_DESCRIPTION='This module implements functions for harvesting data from www.nestoria.co.uk through the public API'
CLASSIFIERS = filter(None, map(str.strip,"""Intended Audience :: DevelopersProgramming Language :: PythonTopic :: Internet :: WWW/HTTP""".splitlines()))
setup(
name="nestoria",
version=VERSION,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
license="GPL",
classifiers=CLASSIFIERS,
author="Nando Quintana",
author_email="fquintana@codesyntax.com",
url="http://www.nestoria.co.uk/help/api-tech",
packages =['nestoria'],
entry_points="""[nestoria.plugins] nestoria = nestoria.nestoria:Nestoria    """,
scripts = ['ez_setup.py'],
platforms=['any'],
install_requires="simplejson")

Entre estos parámetros, podemos destacar los siguientes:

-packages: con él indicaremos la lista de paquetes que distribuiremos en este huevo. En general, será una lista de directorios del código fuente del huevo,

-entry_points: habrá que poner especial atención a este parámetro. Mediante él indicaremos qué modulos podrá importar un programador de nuestro paquete,

-scripts: listado de scripts que irán adjuntos al paquete. Notese que el script “ez_setup.py” es utilizado en nuestro “setup.py” por lo que es interesante asegurarnos de que quien instala nuestro huevo, cuenta con este módulo,

-install_requires: con él se indican los módulos (incluso las versiones de ellos) de los que depende nuestro huevo.
El ejemplo que he preparado es un wrapper del API pública de el sitio web www.nestoria.co.uk. Como podemos ver en el código del huevo, tenemos un directorio llamado “nestoria” donde aparecen los ficheros “__init__.py” y “nestoria.py”. Este último contiene la lógica de negocio y en contreto la clase “Nestoria” que implementa las llamadas al API.



 -- nestoria
------ __init__.py
------ nestoria.py
-- setup.py
-- ez_setup.py

Para construir el huevo bastará con ejecutar el script “setup.py” con los parámetros adecuados:

# /usr/local/bin/python2.4 setup.py sdist bdist_egg

Si todo ha ido bien, tendremos un nuevo directorio llamado “dist”, con los ficheros “nestoria-0.2.tar.gz” y “nestoria-0.2-py2.4.egg”. El primero contiene el código fuente empaquetado al estilo “distutils” aunque mejorado y ha sido generado gracias al parámetro “sdist”. El segundo, contiene el huevo python.

-- build
-- dist
----- nestoria-0.2.tar.gz
----- nestoria-0.2-py2.4.egg
-- nestoria
-- nestoria.egg-info
-- ez_setup.py
-- setup.py  

Ya tenemos nuestro huevo recién hecho y estamos preparados para subirlo a la tienda de quesos. Recuerda que debes estar suscrito para subir paquetes al repositorio. Entre otros datos tendrás que dar el ID de tu clave gpg pública. Si no tienes una, createla, subela a un servidor público de claves e introduce los 8 dígitos del ID.

Si quieres saber cómo se utiliza este huevo, puedes hechar un vistazo a la página del Índice de Paquetes de Python

Screen-scraping en python (lectores de barrapunto y meneame)

¿Cómo se leen los blog?

Me gustaría leer como me gustaría que me leyesen. O dicho de otra manera, escribo sin pensar en cómo me leen si es que leen como yo leo.

¿Leen barrapunto

todos los lectores de meneame

?

Más de una vez he leido noticias interesantes en barrapunto y me han dan ganas de publicarlas en meneame, aunque al final siempre pienso: “va!, ya la habrán leido en barrapunto…”

Hoy me ha dado por echar un vistazo a bloglines, el famoso agregador de feeds, donde se puede consultar los lectores que tienen determinado feed. Hay que tomar estos datos con recelo, ya que hay un nutrido número de lectores que han decidido que su identidad no aparezca en ninguna lista de subscriptores.

He escrito un pequeño programa python para comprobar si los lectores subscritos al feed de meneame, están subscritos también al feed de barrapunto.

 lectores de meneame:    42 (48.8372093023%) lectores de barrapunto: 44 (51.1627906977%) lectores comunes:       0 (0.0%)

La conclusión es que, con los datos públicos de bloglines, no hay ningún subscriptor de meneame que esté subscrito en barrapunto.

Recuerdo lo que decía un profesor nuestro: “Hay tres tipos de mentiras: las mentiras, las grandes mentiras y las estadísticas” ;-D

#!/usr/bin/pythonimport re, urllib2HOSTNAME = ''REALM = ''USER = ""PASS = ""LECTORES_FEED_MENEAME_URL = "http://www.bloglines.com/userdir?siteid=4312848"LECTORES_FEED_BARRAPUNTO_URL = "http://www.bloglines.com/userdir?siteid=38276"EXP_REG = "
[s]*
[s]*<"EXP_REG += "a href="/public/(?P[^"]*)""exp_reg = re.compile(EXP_REG, re.IGNORECASE)def main():    lectores_meneame = []    lectores_barrapunto = []    lectores_comunes = []    authinfo = urllib2.HTTPBasicAuthHandler()    authinfo.add_password(REALM, HOSTNAME, USER, PASS)    opener = urllib2.build_opener(authinfo, urllib2.HTTPDefaultErrorHandler())    request = urllib2.Request(LECTORES_FEED_MENEAME_URL)    response = opener.open(request).read()    for data in re.findall(exp_reg, response):        lectores_meneame.append(data)    request = urllib2.Request(LECTORES_FEED_BARRAPUNTO_URL)    response = opener.open(request).read()    for data in re.findall(exp_reg, response):        lectores_barrapunto.append(data)       numero_lectores_meneame = len(lectores_meneame)    numero_lectores_barrapunto = len(lectores_barrapunto)    total_lectores = numero_lectores_meneame + numero_lectores_barrapunto    porcentaje_meneame = float( numero_lectores_meneame * 100 ) / float(total_lectores)    porcentaje_barrapunto = float( numero_lectores_barrapunto * 100 ) / float(total_lectores)    if numero_lectores_meneame > numero_lectores_barrapunto:       for lector in lectores_meneame:           if lector in lectores_barrapunto:              lectores_comunes.append(lector)    else:       for lector in lectores_barrapunto:           if lector in lectores_meneame:              lectores_comunes.append(lector)    numero_lectores_comunes = len(lectores_comunes)    porcentaje_comunes = float( numero_lectores_comunes * 100 ) / float(total_lectores)    msg =  "
 lectores de meneame:    " + str( numero_lectores_meneame )    + " (" + str( porcentaje_meneame )    + "%)"    msg += "
 lectores de barrapunto: " + str( numero_lectores_barrapunto ) + " (" + str( porcentaje_barrapunto ) + "%)"    msg += "
 lectores comunes:       " + str( numero_lectores_comunes )    + " (" + str( porcentaje_comunes )    + "%)"      msg += "
"    return msgif __name__ == "__main__":   print main()