修饰符:理解它为什么不刷新局部变量

问题描述

我写了一个简单的装饰符:

from functools import wraps
import random

def my_dec(f):
    lst = list()

    @wraps(f)
    def wrapper(*args):
        lst.append(random.randint(0, 9))
        print(lst)
        return f(*args)

    return wrapper

@my_dec
def foo():
    print("foo called")
现在,如果我多次调用foolst不会被刷新。相反,它会随着时间的推移而积累起来。因此,对foo的多次调用返回如下输出:

foo()
> [4]
> foo called

foo()
> [4, 9]
> foo called

foo()
> [4, 9, 1]
> foo called

...

为什么?我以为decorator只是my_dec(foo)的句法糖?!我假设每个对my_dec的调用都刷新lst


解决方案

你说得对...装饰物只是句法上的糖。具体如下:

@decorator
def foo():
    pass

是否与完全相同:

def foo():
    pass
foo = decorator(foo)

让我们更古怪一点,以另一种方式重写它,即基本上等价1

def bar():
    pass
foo = decorator(bar)
del bar

希望是这样写的,您可以看到,如果我调用foo很多次,不是调用decorator很多次。decorator仅被调用一次(以帮助创建foo)。

现在在您的示例中,您的修饰器在被调用时立即创建一个列表:

def my_dec(f):
    lst = list()  # list created here!

    @wraps(f)
    def wrapper(*args):
        lst.append(random.randint(0, 9))
        print(lst)
        return f(*args)

    return wrapper

返回的wrapper函数被分配给您的foo,因此当您调用foo时,您正在调用wrapper。请注意,wrapper中没有重置lst的代码--只有向lst添加更多元素的代码,因此此处没有任何内容指示lst应该在两次调用之间"刷新"。

1(根据修饰符的不同,您可能会看到函数的__name__属性有所不同,但其他方面是相同的...)


还请注意,每次调用修饰符都会有一个lst。如果我们喜欢并装饰两次foo,我们可以疯狂地使用这个:

@my_dec
@my_dec
def foo():
    pass

或者我们可以装饰多个函数:

@my_dec
def foo():
    pass

@my_dec
def bar():
    pass

然后,当我们调用foobar时,我们将看到它们各自累积了自己的(不同的)随机数列表。换句话说,每次将您的修饰符应用于某个对象时,都会创建一个新的列表,并且每次调用"某个对象"时,该列表都会增长。

相关文章