python scope closures decorator python-decorators

python - ¿Cómo inyectar la variable en el alcance con un decorador?



scope closures (7)

[Descargo de responsabilidad: puede haber formas más pitónicas de hacer lo que quiero hacer, pero quiero saber cómo funciona el alcance de Python aquí]

Estoy tratando de encontrar una forma de hacer que un decorador haga algo como inyectar un nombre en el alcance de otra función (de manera que el nombre no se filtre fuera del alcance del decorador). Por ejemplo, si tengo una función que dice que debo imprimir una variable llamada var que no se ha definido, me gustaría definirla dentro de un decorador donde se llama. Aquí hay un ejemplo que rompe:

c = ''Message'' def decorator_factory(value): def msg_decorator(f): def inner_dec(*args, **kwargs): var = value res = f(*args, **kwargs) return res return inner_dec return msg_decorator @decorator_factory(c) def msg_printer(): print var msg_printer()

Me gustaría imprimir " Message ", pero da:

NameError: global name ''var'' is not defined

El traceback incluso apunta a wher var se define:

<ipython-input-25-34b84bee70dc> in inner_dec(*args, **kwargs) 8 def inner_dec(*args, **kwargs): 9 var = value ---> 10 res = f(*args, **kwargs) 11 return res 12 return inner_dec

Entonces no entiendo por qué no puede encontrar var .

¿Hay alguna manera de hacer algo como esto?


Aquí hay una demostración simple de usar un decorador para agregar una variable al alcance de una función.

>>> def add_name(name): ... def inner(func): ... # Same as defining name within wrapped ... # function. ... func.func_globals[''name''] = name ... ... # Simply returns wrapped function reference. ... return func ... ... return inner ... >>> @add_name("Bobby") ... def say_hello(): ... print "Hello %s!" % name ... >>> print say_hello() Hello Bobby! >>>


Aquí hay una forma de inyectar múltiples variables en el alcance de una función de una manera algo similar a lo que hace @Martijn Pieters en su respuesta. Lo publico principalmente porque es una solución más general que aplicar el decorador varias veces, ya que sería necesario hacer lo mismo con el código en las respuestas de Martijn (y muchas de las demás).

from functools import wraps def inject_variables(context): """ Decorator factory. """ def variable_injector(func): @wraps(func) def decorator(*args, **kwargs): try: func_globals = func.__globals__ # Python 2.6+ except AttributeError: func_globals = func.func_globals # Earlier versions. saved_values = func_globals.copy() # Shallow copy of dict. func_globals.update(context) try: result = func(*args, **kwargs) finally: func_globals = saved_values # Undo changes. return result return decorator return variable_injector if __name__ == ''__main__'': namespace = {''a'': 5, ''b'': 3} @inject_variables(namespace) def test(): print(''a:'', a) print(''b:'', b) test()


Hay una manera limpia de hacer lo que quiera sin usar la variable global. Si desea ser apátrida y los hilos seguros, realmente no tiene la opción.

Usa la variable "kwargs":

c = ''Message'' def decorator_factory(value): def msg_decorator(f): def inner_dec(*args, **kwargs): kwargs["var"] = value res = f(*args, **kwargs) return res return inner_dec return msg_decorator @decorator_factory(c) def msg_printer(*args, **kwargs): print kwargs["var"] msg_printer()


No puedes. Los nombres de ámbito (cierres) se determinan en tiempo de compilación, no puede agregar más en tiempo de ejecución.

Lo mejor que puede lograr es agregar nombres globales , utilizando el espacio de nombres global propio de la función:

def decorator_factory(value): def msg_decorator(f): def inner_dec(*args, **kwargs): g = f.__globals__ # use f.func_globals for py < 2.6 sentinel = object() oldvalue = g.get(''var'', sentinel) g[''var''] = value try: res = f(*args, **kwargs) finally: if oldvalue is sentinel: del g[''var''] else: g[''var''] = oldvalue return res return inner_dec return msg_decorator

f.__globals__ es el espacio de nombre global para la función envuelta, por lo que funciona incluso si el decorador vive en un módulo diferente. Si var se ha definido como global, se reemplaza con el nuevo valor y, después de llamar a la función, se restauran los globales.

Esto funciona porque cualquier nombre en una función que no está asignada a, y no se encuentra en un ámbito circundante, se marca como un global en su lugar.

Manifestación:

>>> c = ''Message'' >>> @decorator_factory(c) ... def msg_printer(): ... print var ... >>> msg_printer() Message >>> ''var'' in globals() False

Pero en lugar de decorar, igual podría haber definido var en el ámbito global directamente .

Tenga en cuenta que alterar los globales no es seguro para subprocesos, y cualquier llamada transitoria a otras funciones en el mismo módulo también seguirá viendo este mismo global.


No puedes. Python tiene alcance léxico . Eso significa que el significado de un identificador se determina únicamente en función de los ámbitos que lo rodean físicamente cuando se mira el código fuente.


Python tiene un ámbito léxico, por lo que me temo que no hay una forma clara de hacer lo que quieras sin algunos efectos secundarios potencialmente desagradables. Recomiendo simplemente pasar var a la función a través del decorador.

c = ''Message'' def decorator_factory(value): def msg_decorator(f): def inner_dec(*args, **kwargs): res = f(value, *args, **kwargs) return res inner_dec.__name__ = f.__name__ inner_dec.__doc__ = f.__doc__ return inner_dec return msg_decorator @decorator_factory(c) def msg_printer(var): print var msg_printer() # prints ''Message''


Suponiendo que en Python las funciones son objetos, puedes hacer ...

#!/usr/bin/python3 class DecorClass(object): def __init__(self, arg1, arg2): self.a1 = arg1 self.a2 = arg2 def __call__(self, function): def wrapped(*args): print(''inside class decorator >>'') print(''class members: {0}, {1}''.format(self.a1, self.a2)) print(''wrapped function: {}''.format(args)) function(*args, self.a1, self.a2) return wrapped @DecorClass(1, 2) def my_function(f1, f2, *args): print(''inside decorated function >>'') print(''decorated function arguments: {0}, {1}''.format(f1, f2)) print(''decorator class args: {}''.format(args)) if __name__ == ''__main__'': my_function(3, 4)

y el resultado es:

inside class decorator >> class members: 1, 2 wrapped function: (3, 4) inside decorated function >> decorated function arguments: 3, 4 decorator class args: (1, 2)

más explicación aquí http://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html