在类定义体中使用nonlocal或global关键字

3
在Python中,可以在类内使用import语句来定义从其他模块中“窃取”的类变量。
class CustomMath:
    from math import pi

assert isinstance(CustomMath.pi, float) # passes

引用全局变量到类中也是有效的,使用非常令人困惑的语句x=x

x = 1
class Test:
    # this loads the global variable x and stores in in the class scope
    x = x
assert Test.x == 1 # passes

但是,如果我使用一个函数来生成类,是否有一种类似的方法可以将函数参数中的变量复制到类体中?

我想要这样的东西:

def generate_meta_options(model, design):
    class Meta:
        # copy the nonlocal variable model to this scope
        # but this isn't valid syntax
        model = (nonlocal model)
        design = translate_old_design_spec_to_new_format((nonlocal design))
    return Meta

参数名称很重要,因为它是通过关键字而不是顺序传递的。例如:generate_meta_options(design="A", model=Operation),类中的名称必须相同。我发现一种解决方法是将所有输入变量都创建别名,但这有点麻烦。
def generate_meta_options_works(model, design):
    _model = model
    _design = design
    class Meta:
        model = _model
        design = translate_old_design_spec_to_new_format(_design)
    return Meta

使用nonlocal关键字有没有更好的方法来实现这个?似乎Python允许在类中使用nonlocalglobal,但是变量不会保留在类中,因此我无法想象其用例。

gl = 'this is defined globally'
def nonlocal_in_class_test():
    lo = 'this is defined locally'
    class Test:
        nonlocal lo
        global gl
        lo = 'foo'
        gl = 'bar'
    assert lo == 'foo' # passes
    assert gl == 'bar' # passes
    assert hasattr(Test, 'lo') or hasattr(Test, 'gl'), 'lo nor gl was put in the class scope' # fails

我相信我正在使用的库只需要一个具有属性查找的对象,以便我可以摆脱类,但是我看到的每个示例都使用类(我怀疑这是因为继承多个配置很自然),所以我不愿意选择那条路。

似乎很奇怪,从不同模块复制变量比在上面的范围内复制变量更容易,因此我想问一下,是否有一种方法可以将nonlocal范围中的变量复制到类属性中,而不创建具有不同名称的单独变量?

作为一个旁枝问题,是否有任何情况下,在类体内使用 nonlocalglobal 关键字是有用的呢?我认为它没有用,但也没有理由去做额外的工作来禁止它。

1个回答

3

nonlocal 在任何情况下都不起作用,因为变量在应用 nonlocal 的情况下只有一个作用域 (函数局部变量,与类定义作用域略有不同);尝试使用 nonlocal 会导致你说 model 从未 是类定义作用域的一部分,只是来自于外部。

我个人更喜欢你有点儿 hacky 的重新赋值方法,使得类外的 _model 和类内的 model 不冲突,但如果你不喜欢,还有一种方式可以直接访问即将创建的类的名称空间,使用 vars() (或者 locals();在这种情况下两者是等效的,但我认为类作用域并不像局部变量,尽管它们的行为很像)。

由于该作用域实际上并不是一个函数作用域,你可以通过从 vars/locals 的字典进行修改来改变它,从而使你所需的结果如下:

def generate_meta_options(model, design):
    class Meta:
        vars().update(
            model=model,
            design=translate_old_design_spec_to_new_format(design)
        )
    return Meta

使用dict.update的关键字参数传递形式,代码看起来甚至大部分都像正常的赋值语句。如果在类定义中早期(查找外部作用域)或晚期(查找新定义的名称)使用这些名称,Python也不会报错:

def generate_meta_options(model, design):
    print("Before class, in function:", model, design)  # Sees arguments
    class Meta:
        print("Inside class, before redefinition:", model, design)  # Sees arguments
        vars().update(
            model=model,
            design=design+1
        )
        print("Inside class, after redefinition:", model, design)  # Sees class attrs
    print("After class, in function:", model, design)  # Sees arguments
    return Meta

MyMeta = generate_meta_options('a', 1)
print(MyMeta.model, MyMeta.design)

在线试用!


此外,导入语句的工作原理是将一个属性的值分配给本地名称,而不是分配给具有相同名称的非本地变量的值。 - chepner
@chepner:是的,我有这样的印象,即原帖作者理解了这一点,他们只是将其作为一个示例,说明他们知道如何做,但无法弄清如何适应类似用例(两者最终都会从某个现有名称也称为“X”的名称创建一个名为X的类属性,但只有其中一个存在作用域冲突),这就是为什么我没有直接回答它的原因。 - ShadowRanger
我提到这个是因为class语句的命名空间不是作用域行为并不是真正的问题。你也不能从同名的非局部变量的值初始化函数中的局部变量。 - chepner
@chepner 我想这是公平的,但如果只是嵌套函数,使用不同的变量名始终是一种选择。同时,如果要使关键字参数和类属性名称重名才能成为问题,那么两者都需要。此外,我认为 var = globals()["var"] 对于全局变量(globals)也可以起作用。 - Tadhg McDonald-Jensen
使用不同的变量绝对是正确的解决方案。在编写函数时,您完全可以控制本地变量名称。 - chepner
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接