Reassigning parameters within decorators in Python

走远了吗. 提交于 2020-01-04 04:46:08

问题


Consider a simple Python decorator with parameters:

def decorator_factory(a=None):
    def decorator(func):
        def wrapper(*args, **kws):
            return func(*args, **kws) + a
        return wrapper
    return decorator

Sometimes, it is useful to reassign the value of a parameter based on its actual value. This is a common design pattern in Python, especially given the issue with default parameter mutability, but it can be used in other situations, e.g.:

def foo(a, b=None):
    if b is None:
        b = a
    return a + b

However, similar code mimicking an analogous design pattern with decorators, for example the following toy code:

def decorator_factory(a=None):
    def decorator(func):
        def wrapper(*args, **kws):
            y = func(*args, **kws)
            if a is None:
                a = y
            return y + a
        return wrapper
    return decorator

will raise the following:

UnboundLocalError: local variable 'a' referenced before assignment

How could this be solved?


回答1:


This is a scoping issue. By reassigning the name, the Python interpreter reserves the reassigned name for local usage, thus shadowing the previous value from the outer scopes, which results in the name being unbound if used before the first assignment.

The simplest solution to this is to never reassign a decorator parameter's name inside the wrapper(). Just use a different name throughout.

For example:

def decorator_factory(a=None):
    def decorator(func):
        def wrapper(*args, **kws):
            y = func(*args, **kws)
            a_ = y if a is None else a
            return y + a_
        return wrapper
    return decorator


@decorator_factory()
def foo(x):
    return 2 * x


print(foo(2))
# 8
print(foo(3))
# 12

Note: the nonlocal statement would avoid raising the UnboundLocalError, BUT the value of the parameter will persist across multiple function calls, e.g.:

def decorator_factory(a=None):
    def decorator(func):
        def wrapper(*args, **kws):
            nonlocal a
            y = func(*args, **kws)
            a = y if a is None else a
            return y + a
        return wrapper
    return decorator


@decorator_factory()
def foo(x):
    return 2 * x


print(foo(2))
# 8
print(foo(3))
# 10

The last foo() call gives 10 because the value of a=4 inside the decorated function comes from the previous foo() call.



来源:https://stackoverflow.com/questions/57691284/reassigning-parameters-within-decorators-in-python

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