How to inject variable into scope with a decorator?
[ disclaimer:有可能是更多的语言方式(做我想做的,但我想知道Python的范围厂在这里]
在我想找到一个办法让A公司之类的东西,那是一个名称为"注入另一个功能(范围),这样的名字不泄漏公司的外部的范围)。例如,如果有一个打印功能,A说"这是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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() |
我喜欢两个打印"
1 | NameError: global name 'var' is not defined |
《追踪两个平衡点wher
1 2 3 4 5 6 | <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 |
我不明白为什么它不能找到
有任何的方式做一些这样的吗?
不能。作用域名称(闭包)是在编译时确定的,不能在运行时添加更多。
最好的方法是使用函数自己的全局命名空间添加全局名称:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
这是因为在一个函数中,没有被分配给并且在周围的作用域中找不到的任何名称都被标记为全局名称。
演示:
1 2 3 4 5 6 7 8 9 | >>> c = 'Message' >>> @decorator_factory(c) ... def msg_printer(): ... print var ... >>> msg_printer() Message >>> 'var' in globals() False |
但我可以直接在全球范围内定义
请注意,更改全局变量并不是线程安全的,对同一模块中其他函数的任何临时调用也将看到相同的全局变量。
不能。python有词汇范围。这意味着标识符的含义完全取决于查看源代码时物理上围绕它的作用域。
这是一种将多个变量注入函数作用域的方法,其方式与@martijn pieters在其答案中所做的类似。我之所以发布它主要是因为它是一个更通用的解决方案,不需要多次应用它——因为需要做同样的事情他(和其他许多人)的答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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() |
python在词汇上的作用域是有限的,所以我担心没有一种干净的方法可以做你想要做的事情,而不会有一些潜在的有害副作用。我建议只通过decorator将var传递到函数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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' |
有一种干净的方法可以不使用全局变量来完成您想要的工作。如果您想要成为无状态的和线程安全的,那么您实际上没有选择的余地。
使用"kwargs"变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | def merge(d1, d2): d = d1.copy() d.update(d2) return d # A decorator to inject variables def valueDecorator(*_args, **_kargs): def wrapper(f): def wrapper2(*args, **kargs): return f(*args, **kargs) wrapper2.__name__ = f.__name__ wrapper2.__doc__ = f.__doc__ oldVars = getattr(f, 'Vars', []) oldNamedVars = getattr(f, 'NamedVars', {}) wrapper2.Vars = oldVars + list(_args) wrapper2.NamedVars = merge(oldNamedVars, _kargs) return wrapper2 return wrapper @valueDecorator(12, 13, a=2) @valueDecorator(10, 11, a=1) def func(): print(func.Vars) print(func.NamedVars) |
更改注释函数本身而不是修改全局范围更为合理。
假设在python函数中是对象,您可以这样做…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #!/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) |
结果是:
1 2 3 4 5 6 | inside class decorator >> class members: 1, 2 wrapped function: (3, 4) inside decorated function >> decorated function arguments: 3, 4 decorator class args: (1, 2) |
这里有更多的解释http://python-3-patterns-types-test.readthedocs.io/en/latest/pythondecorators.html
下面是使用decorator将变量添加到函数范围的简单演示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | >>> 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! >>> |