4.4. Obtención de referencias a objetos con getattr

Ya sabe usted que las funciones de Python son objetos. Lo que no sabe es que se puede obtener una referencia a una función sin necesidad de saber su nombre hasta el momento de la ejecución, utilizando la función getattr.

Ejemplo 4.10. Presentación de getattr

>>> li = ["Larry", "Curly"]
>>> li.pop                       1
<built-in method pop of list object at 010DF884>
>>> getattr(li, "pop")           2
<built-in method pop of list object at 010DF884>
>>> getattr(li, "append")("Moe") 3
>>> li
["Larry", "Curly", "Moe"]
>>> getattr({}, "clear")         4
<built-in method clear of dictionary object at 00F113D4>
>>> getattr((), "pop")           5
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'pop'
1 Esto obtiene una referencia al método pop de la lista. Observe que no se invoca al método pop; la llamada sería li.pop(). Esto es el método en sí.
2 Aquí también se devuelve una referencia al método pop, pero esta vez el nombre del método se especifica como una cadena, argumento de la función getattr. getattr es una función incorporada increíblemente útil que devuelve cualquier atributo de cualquier objeto. En este caso, el objeto es una lista, y el atributo es el método pop.
3 Si no le ha impresionado aún la increíble utilidad de esta función, intente esto: el valor de retorno de getattr es el método, que puede ser invocado igual que si se hubiera puesto directamente li.append("Moe"). Pero no se ha llamado directamente a la función; en lugar de esto, se ha especificado su nombre como una cadena.
4 getattr también funciona con diccionarios.
5 En teoría, getattr debería funcionar con tuplas, pero como las tuplas no tienen métodos, getattr lanzará una excepción sea cual sea el nombre de atributo que se le pida.

4.4.1. getattr con módulos

getattr no sirve sólo para tipos de datos incorporados. También funciona con módulos.

Ejemplo 4.11. La función getattr en apihelper.py

>>> import odbchelper
>>> odbchelper.buildConnectionString             1
<function buildConnectionString at 00D18DD4>
>>> getattr(odbchelper, "buildConnectionString") 2
<function buildConnectionString at 00D18DD4>
>>> object = odbchelper
>>> method = "buildConnectionString"
>>> getattr(object, method)                      3
<function buildConnectionString at 00D18DD4>
>>> type(getattr(object, method))                4
<type 'function'>
>>> import types
>>> type(getattr(object, method)) == types.FunctionType
True
>>> callable(getattr(object, method))            5
True
1 Esto devuelve una referencia a la función buildConnectionString del módulo odbchelper, que estudiamos en el Capítulo 2, Su primer programa en Python. (La dirección hexadecimal que se ve es específica de mi máquina; la suya será diferente.)
2 Utilizando getattr, podemos obtener la misma referencia a la misma función. En general, getattr(objeto, "atributo") es equivalente a objeto.atributo. Si objeto es un módulo, entonces atributo puede ser cualquier cosa definida en el módulo: una función, una clase o una variable global.
3 Y esto es lo que realmente utilizamos en la función info. object se pasa como argumento de la función; method es una cadena que contiene el nombre de un método o una función.
4 En este caso, method es el nombre de una función, lo que podemos comprobar obteniendo su tipo con type.
5 Como method es una función, podemos invocarla.

4.4.2. getattr como dispatcher

Un patrón común de uso de getattr es como dispatcher. Por ejemplo, si tuviese un programa que pudiera mostrar datos en diferentes formatos, podría definir funciones separadas para cada formato de salida y una única función de despacho para llamar a la adecuada.

Por ejemplo, imagine un programa que imprima estadísticas de un sitio en formatos HTML, XML, y texto simple. La elección del formato de salida podría especificarse en la línea de órdenes, o almacenarse en un fichero de configuración. Un módulo statsout define tres funciones, output_html, output_xml y output_text. Entonces el programa principal define una única función de salida, así:

Ejemplo 4.12. Creación de un dispatcher con getattr


import statsout

def output(data, format="text"):                              1
    output_function = getattr(statsout, "output_%s" % format) 2
    return output_function(data)                              3
1 La función output toma un argumento obligatorio, data, y uno opcional, format. Si no se especifica format, por omisión asume text, y acabará llamando a la función de salida de texto simple.
2 Concatenamos el argumento format con "output_" para producir un nombre de función y entonces obtenemos esa función del módulo statsout. Esto nos permite extender fácilmente el programa más adelante para que admita otros formatos de salida, sin cambiar la función de despacho. Simplemente añada otra función a statsout que se llame, por ejemplo, output_pdf, y pase "pdf" como format a la función output.
3 Ahora simplemente llamamos a la función de salida de la misma manera que con cualquier otra función. La variable output_function es una referencia a la función apropiada del módulo statsout.

¿Encontró el fallo en el ejemplo anterior? Este acomplamiento entre cadenas y funciones es muy débil, y no hay comprobación de errores. ¿Qué sucede si el usuario pasa un formato que no tiene definida la función correspondiente en statsout? Bien, getattr devolverá None, que se asignará a output_function en lugar de una función válida, y la siguiente línea que intente llamar a esa función provocará una excepción. Esto es malo.

Por suerte, getattr toma un tercer argumento opcional, un valor por omisión.

Ejemplo 4.13. Valores por omisión de getattr


import statsout

def output(data, format="text"):
    output_function = getattr(statsout, "output_%s" % format,
                              statsout.output_text)
    return output_function(data) 1
1 Está garantizado que esta llamada a función tendrá éxito, porque hemos añadido un tercer argumento a la llamada a getattr. El tercer argumento es un valor por omisión que será devuelto si no se encontrase el atributo o método especificado por el segundo argumento.

Como puede ver, getattr es bastante potente. Es el corazón de la introspección y verá ejemplos aún más poderosos en los siguientes capítulos.