装饰函数可以访问装饰器的变量

ima*_*453 10 python

我试图了解装饰器是如何工作的,并且想知道装饰函数是否可以访问装饰器的变量.例如,在以下代码中,如何使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)
Run Code Online (Sandbox Code Playgroud)

mar*_*eau 13

由于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)
Run Code Online (Sandbox Code Playgroud)

哪会产生以下输出:

Calling localVariable from funcDec I'm a local string
5
f1.attrib: "I'm a local string"
done with calling f1
Run Code Online (Sandbox Code Playgroud)

有人问这是否可以应用于类的方法:答案是"是",但您必须通过类本身或作为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()
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)


小智 7

我认为如果你记住一个装饰者会有所帮助

@deco
def f(...): ...
Run Code Online (Sandbox Code Playgroud)

只是语法糖

def f(...): ...
f = deco(f)
Run Code Online (Sandbox Code Playgroud)

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

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

f1(*args, localvariable=localvariable)
Run Code Online (Sandbox Code Playgroud)


Bre*_*arn 6

不,你不能。请参阅前面的问题。仅仅因为函数是一个装饰器并不意味着它调用的函数就可以对其变量进行特殊访问。如果您这样做:

def func():
    a = 2
    otherFunc()
Run Code Online (Sandbox Code Playgroud)

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

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

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