python装饰器中的变量范围


问题内容

我在Python 3装饰器中遇到一个非常奇怪的问题。

如果我这样做:

def rounds(nr_of_rounds):
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            return nr_of_rounds
        return inner
    return wrapper

它工作正常。但是,如果我这样做:

def rounds(nr_of_rounds):
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            lst = []
            while nr_of_rounds > 0:
                lst.append(func(*args, **kwargs))
                nr_of_rounds -= 1
            return max(lst)
        return inner
    return wrapper

我得到:

while nr_of_rounds > 0:
UnboundLocalError: local variable 'nr_of_rounds' referenced before assignment

换句话说,nr_of_rounds如果我在返回中使用它,我可以在内部函数中使用它,但是我不能做任何其他事情。这是为什么?


问题答案:

由于nr_of_rounds是由 闭包 拾取的,因此您可以将其视为“只读”变量。如果要写入它(例如减少它),则需要明确地告诉python
-在这种情况下,python3.xnonlocal关键字将起作用。

简要说明一下,当Cpython遇到函数定义时,它会执行以下操作:查看代码并确定所有变量是 local 还是 non-local
。局部变量(默认情况下)是赋值语句,循环变量和输入参数出现在左侧的任何内容。其他所有名称都不是本地名称。这样可以进行一些巧妙的优化1。要以与本地相同的方式使用非本地变量,需要通过globalnonlocal语句明确告知python
。当python遇到它认为 应该 是本地的东西但实际上不是的时候,您会得到一个UnboundLocalError

1 Cpython字节码生成器将本地名称转换为数组中的索引,以便本地名称查找(LOAD_FAST字节码指令)与索引数组一样快,并且具有正常的字节码开销。