关于python:如何使用装饰器将变量注入范围?

How to inject variable into scope with a decorator?

[ disclaimer:有可能是更多的语言方式(做我想做的,但我想知道Python的范围厂在这里]

在我想找到一个办法让A公司之类的东西,那是一个名称为"注入另一个功能(范围),这样的名字不泄漏公司的外部的范围)。例如,如果有一个打印功能,A说"这是var变量没有定义,我的定义的两类信息在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()

我喜欢两个打印"Message",但它给予:

1
NameError: global name 'var' is not defined

《追踪两个平衡点wher var冰的定义:

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

我不明白为什么它不能找到var

有任何的方式做一些这样的吗?


不能。作用域名称(闭包)是在编译时确定的,不能在运行时添加更多。

最好的方法是使用函数自己的全局命名空间添加全局名称:

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

f.__globals__是包装函数的全局命名空间,因此即使装饰器位于不同的模块中,也可以这样做。如果var已经被定义为全局,它将被替换为新的值,并且在调用函数后,全局将被恢复。

这是因为在一个函数中,没有被分配给并且在周围的作用域中找不到的任何名称都被标记为全局名称。

演示:

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

但我可以直接在全球范围内定义var,而不是装饰。

请注意,更改全局变量并不是线程安全的,对同一模块中其他函数的任何临时调用也将看到相同的全局变量。


不能。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!
>>>