Posts Tagged ‘Python’

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