Decoradores Python agradables
Pregunta
¿Cómo escribo bien un decorador?
En temas particulares incluyen: compatibilidad con otros decoradores, conservación de firmas, etc.
Me gustaría evitar la dependencia del módulo decorador si es posible, pero si hubiera suficientes ventajas, entonces lo consideraría.
Relacionados
- Preservar firmas de funciones decoradas - una pregunta mucho más específica. La respuesta aquí es utilizar el módulo de decorador de terceros que anota el decorador con @ decorator.decorator
Solución
Escribir un buen decorador no es diferente a escribir una buena función. Lo que significa, idealmente, usar cadenas de documentación y asegurarse de que el decorador esté incluido en su marco de prueba.
Definitivamente, debe usar la biblioteca decorator
o, mejor, el functools.wraps ()
en la biblioteca estándar (desde la versión 2.5).
Más allá de eso, es mejor mantener a sus decoradores concentrados y bien diseñados. No utilice * args
o ** kw
si su decorador espera argumentos específicos. Y do complete los argumentos que espera, así que en lugar de:
def keep_none(func):
def _exec(*args, **kw):
return None if args[0] is None else func(*args, **kw)
return _exec
... usar ...
def keep_none(func):
"""Wraps a function which expects a value as the first argument, and
ensures the function won't get called with *None*. If it is, this
will return *None*.
>>> def f(x):
... return x + 5
>>> f(1)
6
>>> f(None) is None
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
>>> f = keep_none(f)
>>> f(1)
6
>>> f(None) is None
True"""
@wraps(func)
def _exec(value, *args, **kw):
return None if value is None else func(value, *args, **kw)
return _exec
Otros consejos
Utilice functools para conservar el nombre y el documento. La firma no se conservará.
Directamente desde el doc .
>>> from functools import wraps
>>> def my_decorator(f):
... @wraps(f)
... def wrapper(*args, **kwds):
... print 'Calling decorated function'
... return f(*args, **kwds)
... return wrapper
...
>>> @my_decorator
... def example():
... """Docstring"""
... print 'Called example function'
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'