装饰器和变量作用域

Decorators and variables scope

我正在编写一个 (python3) 程序,但在尝试实现一个更新外部变量(一种信号发射装饰器)的(函数)装饰器时卡住了。问题是与各种功能范围的冲突。我四处寻找一些类似的问题,但我还没有找到有用的问题......如果可能的话,我需要尊重一些设计限制(见下文),我也想避免使用外部库。

这是一个带有 global 关键字的工作示例,可以用作起点

VAR = 'i am a global variable'


# decorator
def update_external_variable():
    def f_wrapper(f):
        def p_wrapper(p, q):
            r = f(p, q) + ': updating the global variable ??'
            global VAR
            VAR = r
            return r
        return p_wrapper
    return f_wrapper

@update_external_variable()
def a(p, q): return 'a({}, {})'.format(p, q)    #target function
o = a('v', 'w')

print(VAR, id(VAR))

输出

a(v, w): updating the global variable ?? 140497617759280 # yes, it works!

设计限制1:装饰器update_external_variable不应该依赖于外部变量标识符(名称),所以它必须作为参数传递。 update_external_variable 的签名应该包含全局变量的信息,VAR.

尝试 1:mokey 补丁方式 - 我尝试模仿上面的工作示例但没有结果

VAR = 'i am a global variable'

# decorator
def update_external_variable(ext_var_id):   # ext_var_id: string with the variable identifier
    def f_wrapper(f):
        def p_wrapper(p, q):
            r = f(p, q) + ': updating the global variable ??'

            exec('global {}'.format(ext_var_id), {})            # -> global VAR
            exec('{} = "{}"'.format(ext_var_id, eval(ext_var_id))) # initialize VAR??
            #print(dir())
            return r
        return p_wrapper
    return f_wrapper

@update_external_variable(ext_var_id='VAR')
def a(p, q): return 'a({}, {})'.format(p, q)    #target function


o = a('v', 'w')
print(o, id(o))

输出

a(v, w): updating the global variable ?? 140686557781040
i am a global variable         # failure!

尝试2:参数方式

VAR = 'i am a global variable'

# decorator
def update_external_variable(ext_var):  # ext_var: reference of the global variable
    def f_wrapper(f):
        def p_wrapper(p, q, ext_var=ext_var):
            r = f(p, q) + ': updating the global variable ??'
            # global ext_var <- will raise to an error since point to the parameter..
            print(ext_var)
            ext_var = r
            return r
        return p_wrapper
    return f_wrapper


@update_external_variable(ext_var=VAR)
def a(p, q): return 'a({}, {})'.format(p, q)  # target function


o = a('v', 'w')
print(o, id(o))
print(VAR)

输出

i am a global variable
a(v, w): updating the global variable ?? 140406972742896
i am a global variable       # failure!

设计限制2:如果存在使用尝试2的解决方案,那么我需要对p_wrapper 这可能会引起进一步的问题: def p_wrapper(*args, **kwargs): ... 为了给装饰器一个通用的指纹,我需要 p_wrapper 的参数是要装饰的函数的参数,r = func(*args, **kwargs).

如果有人知道如何解决这个问题,可以是尝试 1 或尝试 2,也可以是它们的组合或其他解决方案,我将非常感激!

提前致谢:)

不要使用单独的全局变量;使用装饰函数可以更新的 dict

VARS = {
    'var1': ...,
    'var2': ...,
}

# decorator
def update_external_variable(varkey):
    def f_wrapper(f):
        def p_wrapper(p, q):
            r = f(p, q) + ': updating the global variable ??'
            VARS[varkey] = r
            return r
        return p_wrapper
    return f_wrapper

@update_external_variable('var1')
def a(p, q): 
  return 'a({}, {})'.format(p, q)    #target function

o = a('v', 'w')

print(VARS['var1'])

如果出于某种原因,您的装饰器必须使用您无法更改的现有全局变量,请使用 globals() 访问必要的 dict

VAR1 = ...
VAR2 = ...

# decorator
def update_external_variable(varkey):
    def f_wrapper(f):
        def p_wrapper(p, q):
            r = f(p, q) + ': updating the global variable ??'
            globals()[varkey] = r
            return r
        return p_wrapper
    return f_wrapper

# Still passing the *name* of the variable as a string
@update_external_variable('VAR1')
def a(p, q): 
  return 'a({}, {})'.format(p, q)    #target function

o = a('v', 'w')

print(VAR1)