The difference between __getattribute__ and __getattr__

The other day I was teaching Python meta-programming to a workmate. I think it’s a good way to learn about high order functions since meta-programming makes extensive use of closures, function builders, decorators… I was trying to make a concept probe about a very, very generic REST connector. Here is my first (and wrong) attempt:

class RESTConn(object):

  def __init__(self, entry_point):
    self.entry_point = entry_point

  def method_builder(self, method_name):
    verb, _, collection = method_name.split('_', 2)
    def do_verb(payload=None, **kwargs):
      uri = self.make_uri(collection)
      querystring = self.make_querystring(kwargs)
      print verb.upper(), self.combine(uri, querystring)
      if payload:
        print payload

    return do_verb

  def make_uri(self, collection):
    return '/'.join([self.entry_point, collection])

  def make_querystring(self, kwargs):
    return '&'.join(['='.join(pair) for pair in kwargs.iteritems()])

  def combine(self, uri, querystring):
    if querystring:
      return '&'.join([uri, querystring])

    return uri

  def __getattribute__(self, name):
    if not hasattr(self, name):
      method = self.method_builder(name)
      setattr(self, name, method)

    return super(RESTConn, self).__getattribute__(name)

Try this example by instantiating a new connector and trying to call something like:

c = RESTConn('unoyunodiez.com')
c.get_from_articles()

The program falls into an infinite recursion and do nothing before crashing. Why?

There are three problems here. First and most important is using __getattribute__(), second is using hasattr() and third is accessing self.method_builder().

The object’s method __getattribute__() is used to retrieve an attribute from an instance. It captures every attempt to access an instance attribute by using dot notation or getattr() built-in function. Unless it was overridden, the former expression is translated into object.__getattribute__(self, ‘get_from_article’). The default implementations looks into the instance’s namespace, then looks into the class namespace, then into each base’s namespace and so on. Finally, if not found, the default implementation calls the fallback __getattr__() method of the instance and it raises an AttributeError exception as default implementation.

This is not a problem by itself but if you pay attention enough you’ll notice we are trying to create the new method only if the object does not have the method yet. It is semantically the same as overriding __getattr__() because it is called only when the attribute was not found. So, even if we cannot explain the infinite recursion error yet, we can fix the class by replacing:

  def __getattribute__(self, name):
    if not hasattr(self, name):
      method = self.method_builder(name)
      setattr(self, name, method)

    return super(RESTConn, self).__getattribute__(name)

with:

  def __getattr__(self, name):
    method = self.method_builder(name)
    setattr(self, name, method)
    return getattr(self, name)

So, the difference between __getattribute__() and __getattr__() is that the first one is called unconditionally when an attribute is being retrieved from an instance while the second is called only when the attribute was not found.

But, what about the infinite recursion? Why the first example was failing?

Seguir leyendo «The difference between __getattribute__ and __getattr__»

Metaprogramación en Python

Hace unos días os contaba en la entrada sobre la Codemotion que una de las charlas más interesantes fue la propuesta por Sergio Gil sobre metaprogramación en Ruby en la que se nos introducía esta técnica de programación de forma amena y coloquial. Como os prometí, aquí tenéis la adaptación a Python y en breve tendréis también su versión enfocada en JavaScript.

La charla de Sergio en la Codemotion estaba etiquetada con nivel intermedio no por requerir conocimientos avanzados de Ruby (yo soy un absoluto novato en este lenguaje) sino por precisar de comprender conceptos avanzados de los lenguajes dinámicos. Quizá, lo más importante sea el modelo de datos.

Aunque en realidad la memoria es una secuencia de celdas direccionables que guardan datos o referencias a otras celdas (lenguages como C lo hacen patente), Ruby, JavaScript o Python no se ejecutan directamente sobre nuestra máquina sino sobre un intérprete o máquina virtual que recubre la máquina real. El intérprete ofrece una visión de la memoria «en alto nivel» al programador llena de variables, métodos, objetos, clases, módulos y otras entidades que, junto con la forma en la que tales entidades se relacionan entre sí, forman lo que se llama el modelo de datos del lenguaje. Lo interesante de los lenguajes dinámicos y por lo que se llaman así es porque en tiempo de ejecución, no se limitan a ofrecer una descripción estática del modelo sino que permiten manipularlo. Por ejemplo:

>>> class Simple(object):
...   '''Simply a class'''
...
>>> o = Simple()
>>> dir(o)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> o.__class__
<class '__main__.Simple'>
>>> o.__doc__
'Simply a class'
>>> o.__init__
<method-wrapper '__init__' of Simple object at 0x7f8a016fabd0>
>>> def f(): print 'Hello!'
...
>>> f
<function f at 0x7f8a016fc668>
>>> o.say_hello = f
>>> o.say_hello()
Hello!
>>> p = Simple()
>>> p.say_hello()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Simple' object has no attribute 'say_hello'

Fijaos en la interacción anterior, la consola de Python proporciona un entorno de ejecución. En él creamos una clase nueva, Simple, e instanciamos un objeto o, mostramos sus atributos con dir, comprobamos el tipo de algunos de ellos y a continuación creamos una nueva función que asignamos sólo al objeto o. El objeto p no tiene esta función por lo que el intérprete falla al intentar encontrarla.

Ahora que tenemos la idea intuitiva de por qué Python es un lenguaje dinámico, indaguemos en la metaprogramación al fin. Metaprogramación es una palabra que se asocia comunmente a la idea de «programas que escriben programas»; yo prefiero concebir la metaprogramación como una técnica para automatizar la descripción de modelos. ¿Suena muy técnico? Veámoslo con un ejemplo:

Supongamos que queremos una clase que nos permita obtener información de una hipotética compañía mycompany.com Tal sitio web expone información de sus proyectos, empleados, compradores, etc a traves de las páginas /projects, /employees, /customers… Una posible implementación es la siguiente:

class MyCompany(object):
    url = 'mycompany.com'

    def projects(self):
        return self._get('/projects')

    def employees(self):
        return self._get('/employees')

    def customers(self):
        return self._get('/customers')

    def _get(self, url):
        return 'GET ' + self.url + url

Evidentemente, en un entorno real, la función _get() debería hacer algo más que devolver una cadena pero, para nuestros propósitos, nos sirve tal y como está.

La clase MyCompany es bastante clara y expresiva. Si quisiera acceder ahora a /products partiría de uno de los métodos existentes y cambiaría su nombre y la ruta de la URL. Cuando la clase tenga 15 métodos, y a la compañía le de por mover sus páginas a /items/projects, /items/employees, /items/customers… pues tendremos que revisar los 15 métodos. Las desventajas son evidentes pero, ¿podemos hacerlo mejor? Por supuesto:

class MyCompany(object):
    url = 'mycompany.com'

def make_get(path):
    def actual_get(self):
        return 'GET ' + self.url + '/' + path

    return actual_get

for name in ['projects', 'employees', 'customers']:
    setattr(MyCompany, name, make_get(name))

Ahora, añadir un nuevo método es tan sencillo como añadirlo a la lista de métodos, y modificar la funcionalidad es tan fácil como cambiar el método actual_get().

Como yo no tengo una limitación de 45 minutos (como ocurría en la presentación), podemos entrar en detalle: lo único que estamos haciendo es usar setattr() para añadir atributos a un objeto (sí, en Python las clases son objetos; en Python TODO son objetos). El primer parámetro es el objeto al que queremos añadir el atributo, el segundo es el nombre del atributo y el tercero es el valor del atributo. El valor lo produce la función make_get() que devuelve otra función actual_get() con el comportamiento deseado.

Ahora podemos hacer:

>>> company = MyCompany()
>>> company.projects()
'GET mycompany.com/projects'
>>> company.employees()
'GET mycompany.com/employees'
>>> company.customers()
'GET mycompany.com/customers'

De nuevo parafraseando a Sergio en su charla de la Codemotion, al final todo se reduce a:

DRY
D
on’t Repeat Yourself

El segundo listado resulta muy compacto pero adolece de algunos problemas de diseño: demasiado acoplamiento. Si ahora quiero crear otra compañía, pongamos yourcompany.com, ¿qué hago? ¿Otro módulo con casi el mismo código? ¡No! De nuevo ¡DRY!

Vamos a mejorar el diseño:

class HTTP(object):
    @classmethod
    def get(cls, *names):
        def make_get(path):
            def actual_get(self):
                return 'GET ' + self.url + '/' + path

            return actual_get

        for name in names:
            setattr(cls, name, make_get(name))

class MyCompany(HTTP):
    url = 'mycompany.com'
MyCompany.get('projects', 'employees', 'customers')

class YourCompany(HTTP):
    url = 'yourcompany.com'
YourCompany.get('projects', 'employees')

Ahora podemos coger la clase HTTP y meterla en un módulo aparte (por ejemplo http.py). Nuestras compañías pueden estar en otro módulo (tal vez companies.py) e importar y heredar de HTTP para a continuación llamar al método de clase get() que creará los métodos indicados.

El método de clase get() tiene algo de chicha: está decorado con el decorador classmethod que hace que, al ser llamado, reciba como primer parámetro la clase donde ha sido definido. Luego recibe una lista con los argumentos pasados a la función, en este caso, los nombres de los métodos. Por cada uno de ellos, crea una nueva función de la forma que vimos anteriormente.

El siguiente listado prueba las clases MyCompany y YourCompany:

>>> mine = MyCompany()
>>> mine.projects()
'GET mycompany.com/projects'
>>> mine.customers()
'GET mycompany.com/customers'
>>> yours = YourCompany()
>>> yours.projects()
'GET yourcompany.com/projects'
>>> yours.customers()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'YourCompany' object has no attribute 'customers'

Comprender el modelo de datos de Python resulta de vital importancia para explotar la metaprogramación, baste por el momento comprender cómo Python recupera un attributo de un objeto. Cuando en Python escribimos objeto.atributo, internamente se llama a la función __getattribute__ con el objeto y el nombre del atributo como parámetros. La implementación por defecto mira (no preguntéis cómo, que no acabamos nunca) en el objeto por un atributo con ese nombre y si no lo encuentra trata de encontrarlo en la clase del objeto. Si ahí tampoco está, mira en las superclases y de ahí hacia arriba por la jerarquía de clases. En caso de no encontrarlo se llama al método __getattr__ de la instancia el cual, por defecto, lanza una excepción del tipo AttributeError.

¿Y qué queremos decir con esto? Pues que podemos explotar más la metaprogramación hasta el punto de ni siquiera tener que crear los métodos de la clase explícitamente. La generación en sí puede ser dinámica, creados conforme se necesitan. Veamos cómo:

class HTTP(object):
    @classmethod
    def get(cls, *names):
        def make_get(path):
            def actual_get(self):
                return 'GET ' + self.url + '/' + path

            return actual_get

        for name in names:
            setattr(cls, name, make_get(name))

    def __getattr__(self, name):
        HTTP.get(name)
        return getattr(self, name)

class MyCompany(HTTP):
    url = 'mycompany.com'
MyCompany.get('projects', 'employees', 'customers')

class YourCompany(HTTP):
    url = 'yourcompany.com'
YourCompany.get('projects', 'employees')

Hemos reescrito __getattr__() para que en caso de que el método no exista, le pidamos a la clase que lo genere por nosotros. Luego lo recuperamos y devolvemos.

La misma interacción que antes fallaba con la excepción AttributeError ahora no fracasará:

>>> mine = MyCompany()
>>> mine.projects()
'GET mycompany.com/projects'
>>> mine.customers()
'GET mycompany.com/customers'
>>> yours = YourCompany()
>>> yours.projects()
'GET yourcompany.com/projects'
>>> yours.customers()
'GET yourcompany.com/customers'

¿Bastante impresionante, no? Ahora bien, ¿cuándo debemos usar metaprogramación? La respuesta no es sencilla pero yo tiraría por «siempre que sea expresivo y la alternativa sea peor» o quizá «siempre que se ajuste a la naturaleza del problema» La metaprogramación tiene mucho que ver con el diseño de interfaces, la interfaz de una utilidad tiene que ser agradable para sus usuarios pero mantenerla no puede resultar en una pesadilla para el programador: debemos llegar a un acuerdo entre usabilidad y mantenimiento.

La metaprogramación no es más que programación así que no pierdas las buenas costumbres. Documenta tus métodos y explícate qué estás haciendo en todo momento. Así, cuando recuperes tu proyecto 3 meses más tarde no invertiras horas y horas tratando de ver qué escribiste.

Vale la pena terminar con una comparación con Ruby. ¿Es cómodo metaprogramar en Python? En mi opinión: no tanto como en Ruby. Ruby está más orientado a manipular fragmentos de código, el hecho de que lo que existe entre un bloque do – end sea un dato simplifica considerablemente las cosas. En Python, el modelo de datos es poco regular y difícil con muchos casos y excepciones. Además, los decoradores, el anidamientos de funciones, los nombres especiales de función y el modelo de acceso a los atributos contribuyen a disminuir la claridad y limpieza de la implementación.

En la próxima entrada, exploraremos exactamente el mismo ejemplo pero en JavaScript. Será una entrada mucho más corta dado que no será necesaria la introducción ni las explicaciones previas, pero os aseguro que volverá a ser útil e interesante.

Tenéis todo el código de los ejemplos en:
https://github.com/lodr/metaprogramming