patron - ¿Por qué decoradores Python en lugar de cierres?
patron decorator python (4)
Todavía no me he vuelto loca por los decoradores en Python.
Ya comencé a usar muchos cierres para hacer cosas como personalizar funciones y clases en mi codificación.
P.ej.
class Node :
def __init__(self,val,children) :
self.val = val
self.children = children
def makeRunner(f) :
def run(node) :
f(node)
for x in node.children :
run(x)
return run
tree=Node(1,[Node(2,[]),Node(3,[Node(4,[]),Node(5,[])])])
def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)
printTree(tree)
Por lo que puedo ver, los decoradores son simplemente una sintaxis diferente para hacer algo similar.
En lugar de
def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)
Yo escribiría :
@makeRunner
def printTree(n) : print "%s," % n.val
¿Esto es todo lo que hay para los decoradores? ¿O hay una diferencia fundamental que me he perdido?
¿Son tus ejemplos código real, o solo ejemplos?
Si son códigos reales, creo que usas demasiado decoradores, probablemente por tu historial (es decir, que estás acostumbrado a otros lenguajes de programación)
Etapa 1: evitar decoradores
def run(rootnode, func):
def _run(node): # recursive internal function
func(node)
for x in node.children:
_run(x) # recurse
_run(rootnode) # initial run
Este método de ejecución obsoleta makeRunner. Tu ejemplo se convierte en:
def pp(n): print "%s," % n.val
run(tree, pp)
Sin embargo, esto ignora completamente los generadores, entonces ...
Etapa 2: usar generadores
class Node :
def __init__(self,val,children) :
self.val = val
self.children = children
def __iter__(self): # recursive
yield self
for child in self.children:
for item in child: # recurse
yield item
def run(rootnode, func):
for node in rootnode:
func(node)
Tu ejemplo permanece
def pp(n): print "%s," % n.val
run(tree, pp)
Tenga en cuenta que el método especial __iter__
nos permite usar el for node in rootnode:
construct. Si no te gusta, simplemente cambia el nombre del método __iter__
a, por ejemplo, walker
, y cambia el ciclo de run
en: for node in rootnode.walker():
Obviamente, la función de run
podría ser un método de class Node
de class Node
lugar.
Como ve, le sugiero que use directamente run(tree, func)
lugar de vincularlos al nombre printTree
, pero puede usarlos en un decorador, o puede hacer uso de la función functools.partial
:
printTree= functools.partial(run, func=pp)
y de ahí en más, simplemente
printTree(tree)
Si bien es cierto que sintácticamente, los decoradores son simplemente "azúcar", esa no es la mejor manera de pensar sobre ellos.
Los decoradores te permiten tejer funcionalidad en tu código existente sin modificarlo realmente. Y te permiten hacerlo de una manera declarativa.
Esto le permite usar decoradores para hacer una programación orientada a aspectos (AOP). Así que desea utilizar un decorador cuando tenga una preocupación transversal que quiera encapsular en un solo lugar.
El ejemplo por excelencia sería el registro, donde desea registrar la entrada o salida de una función, o ambas cosas. Usar un decorador es equivalente a aplicar consejos (¡log esto!) A un punto de unión (durante la entrada o salida del método).
La decoración de métodos es un concepto como OOP o listas de comprensión. Como usted señala, no siempre es apropiado, y puede ser usado en exceso. Pero en el lugar correcto, puede ser útil para hacer que el código sea más modular y desacoplado.
Siguiendo la referencia de AOP de Dutch Master, descubrirá que el uso de decoradores se vuelve especialmente útil cuando comienza a agregar parámetros para modificar el comportamiento de la función / método decorado, y leer la definición de función anterior es mucho más fácil.
En un proyecto, recuerdo, necesitábamos supervisar toneladas de tareas de apio, así que se nos ocurrió la idea de utilizar un decorador para enchufar y ajustar según fuera necesario, que era algo así como:
class tracked_with(object):
"""
Method decorator used to track the results of celery tasks.
"""
def __init__(self, model, unique=False, id_attr=''results_id'',
log_error=False, raise_error=False):
self.model = model
self.unique = unique
self.id_attr = id_attr
self.log_error = log_error
self.raise_error = raise_error
def __call__(self, fn):
def wrapped(*args, **kwargs):
# Unique passed by parameter has priority above the decorator def
unique = kwargs.get(''unique'', None)
if unique is not None:
self.unique = unique
if self.unique:
caller = args[0]
pending = self.model.objects.filter(
state=self.model.Running,
task_type=caller.__class__.__name__
)
if pending.exists():
raise AssertionError(''Another {} task is already running''
''''.format(caller.__class__.__name__))
results_id = kwargs.get(self.id_attr)
try:
result = fn(*args, **kwargs)
except Retry:
# Retry must always be raised to retry a task
raise
except Exception as e:
# Error, update stats, log/raise/return depending on values
if results_id:
self.model.update_stats(results_id, error=e)
if self.log_error:
logger.error(e)
if self.raise_error:
raise
else:
return e
else:
# No error, save results in refresh object and return
if results_id:
self.model.update_stats(results_id, **result)
return result
return wrapped
Luego, simplemente decoramos el método de run
de las tareas con los parámetros necesarios para cada caso, como por ejemplo:
class SomeTask(Task):
@tracked_with(RefreshResults, unique=True, log_error=False)
def run(self, *args, **kwargs)...
Luego, cambiar el comportamiento de la tarea (o eliminar el seguimiento por completo) significaba ajustar un parámetro o comentar la línea decorada. Súper fácil de implementar, pero más importante, muy fácil de entender en la inspección.
Los decoradores , en el sentido general, son funciones o clases que envuelven a otro objeto, que extienden o decoran el objeto. El decorador admite la misma interfaz que la función u objeto envuelto, por lo que el receptor ni siquiera sabe que el objeto ha sido decorado.
Un cierre es una función anónima que se refiere a sus parámetros u otras variables fuera de su alcance.
Entonces, básicamente, los decoradores usan cierres y no los reemplazan.
def increment(x):
return x + 1
def double_increment(func):
def wrapper(x):
print ''decorator executed''
r = func(x) # --> func is saved in __closure__
y = r * 2
return r, y
return wrapper
@double_increment
def increment(x):
return x + 1
>>> increment(2)
decorator executed
(3, 6)
>>> increment.__closure__
(<cell at 0x02C7DC50: function object at 0x02C85DB0>,)
>>> increment.__closure__[0].cell_contents
<function increment at 0x02C85DB0>
Entonces el decorador guarda la función original con cierre .
¿Cuál es la diferencia entre cierres y decoradores en Python?