Scope of variables in python decorator

守給你的承諾、 提交于 2019-12-03 23:35:19

问题


I'm having a very weird problem in a Python 3 decorator.

If I do this:

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

it works just fine. However, if I do this:

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

I get:

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

In other words, I can use nr_of_roundsin the inner function if I use it in a return, but I can't do anything else with it. Why is that?


回答1:


Since nr_of_rounds is picked up by the closure, you can think of it as a "read-only" variable. If you want to write to it (e.g. to decrement it), you need to tell python explicitly -- In this case, the python3.x nonlocal keyword would work.

As a brief explanation, what Cpython does when it encounters a function definition is it looks at the code and decides if all the variables are local or non-local. Local variables (by default) are anything that appear on the left-hand side of an assignment statement, loop variables and the input arguments. Every other name is non-local. This allows some neat optimizations1. To use a non-local variable the same way you would a local, you need to tell python explicitly either via a global or nonlocal statement. When python encounters something that it thinks should be a local, but really isn't, you get an UnboundLocalError.

1The Cpython bytecode generator turns the local names into indices in an array so that local name lookup (the LOAD_FAST bytecode instruction) is as fast as indexing an array plus the normal bytecode overhead.




回答2:


Currently there is no way to do the same for variables in enclosing function scopes, but Python 3 introduces a new keyword, "nonlocal" which will act in a similar way to global, but for nested function scopes.
so in your case just use like:
def inner(*args, **kwargs): nonlocal nr_of_rounds lst = [] while nr_of_rounds > 0: lst.append(func(*args, **kwargs)) nr_of_rounds -= 1 return max(lst) return inner
For more info Short Description of the Scoping Rules?



来源:https://stackoverflow.com/questions/29760593/scope-of-variables-in-python-decorator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!