12

我试图了解装饰器是如何工作的,并且想知道装饰器是否可以访问装饰器的变量。例如,在下面的代码中,如何让 f1 可以访问 localVariable?这有可能吗,这甚至是一种很好的做事方式吗?

def funcDec(func):
    localVariable = "I'm a local string"
    def func2Return(*args):                                                                            
        print "Calling localVariable from decorator " + localVariable
        func(*args)                                      
        print "done with calling f1"
    return func2Return

@funcDec
def f1(x, y):
    print x + y
    print localVariable

f1(2, 3)
4

3 回答 3

16

由于 Python 的作用域规则,装饰函数通常无法访问装饰器中的任何变量。但是,由于函数可以具有分配给它们的任意属性,因此您可以在装饰器中执行以下操作以获得类似的效果(由于相同的范围规则):

def funcDec(func):
    localVariable = "I'm a local string"

    def wrapped(*args):
        print("Calling localVariable from funcDec " + localVariable)
        func(*args)
        print("done with calling f1")

    wrapped.attrib = localVariable
    return wrapped

@funcDec
def f1(x, y):
    print(x + y)
    print('f1.attrib: {!r}'.format(f1.attrib))

f1(2, 3)

这将产生以下输出:

Calling localVariable from funcDec I'm a local string
5
f1.attrib: "I'm a local string"
done with calling f1

有人问这是否可以应用于类的方法:答案是“是”,但是您必须通过类本身或作为self参数传递的它的实例来引用该方法。这两种技术如下所示。使用self是更可取的,因为它使代码独立于它所在的类的名称。

class Test(object):
    @funcDec
    def f1(self):
        print('{}.f1() called'.format(self.__class__.__name__))
        print('self.f1.attrib: {!r}'.format(self.f1.attrib))  # Preferred.
        print('Test.f1.attrib: {!r}'.format(Test.f1.attrib))  # Also works.

print()
test = Test()
test.f1()

输出:

Calling localVariable from funcDec I'm a local string
Test.f1() called
self.f1.attrib: "I'm a local string"
Test.f1.attrib: "I'm a local string"
done with calling f1
于 2012-09-15T10:16:37.170 回答
8

不,你不能。请参阅上一个问题。仅仅因为该函数是一个装饰器并不意味着它调用的函数对其变量具有特殊访问权限。如果你这样做:

def func():
    a = 2
    otherFunc()

然后 otherFunc 无权访问该变量a。这就是它对所有函数调用的工作方式,也是它对装饰器的工作方式。

现在,您在装饰器中定义的包装函数(func2Return在您的示例中)确实可以访问变量,因为该函数在词法上与这些变量在同一范围内。所以你的线print "Calling localVariable from decorator " + localVariable会工作。您可以在某种程度上使用它来包装装饰函数,其行为取决于装饰器中的变量。但是实际被装饰的函数(f1在您的示例中)无权访问这些变量。

函数只能从函数定义实际所在的范围访问局部变量。函数不会从调用范围中获取变量。(这是一件好事。如果他们这样做了,那将是一团糟。)

于 2012-09-15T08:33:03.753 回答
8

我认为如果您记住装饰器会有所帮助

@deco
def f(...): ...

只是语法糖

def f(...): ...
f = deco(f)

而不是某种宏扩展。在 Python 中,变量的范围是静态确定的,因此对于全局(模块级)函数,既不作为参数传递也不分配给的变量将在全局命名空间中查找。

因此,您必须明确传递 func2Return() 的局部变量。更改f1to的签名f1(x, y, localvariable=None)并让包装函数fun2Return调用它

f1(*args, localvariable=localvariable)
于 2012-09-15T08:42:24.150 回答