在类体内使用非局部或全局

Posted

技术标签:

【中文标题】在类体内使用非局部或全局【英文标题】:using nonlocal or global inside a class body 【发布时间】:2021-12-17 04:44:06 【问题描述】:

在 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

我确定我正在使用的库只需要一个带有属性查找的对象,这样我就可以远离类,但是我看到的每个示例都使用类(我怀疑是因为继承多个配置是自然的)所以我很犹豫走那条路。

从不同的模块复制变量似乎比在上述范围内复制一个更容易,所以我想我想问一下,有没有办法从 @987654332 复制变量@scope 到类属性中而不创建具有不同名称的单独变量?

作为切线,在类体内使用nonlocalglobal 关键字是否有用?我认为它没有用,但也没有理由做额外的工作来禁止它。

【问题讨论】:

【参考方案1】:

nonlocal 在任何情况下都不起作用,因为在 nonlocal 应用的情况下变量只有一个作用域(函数局部变量,与类定义作用域略有不同);通过尝试使用nonlocal,你会说model 从不属于类定义范围,只是它之外的东西。

我个人更喜欢你的有点骇人听闻的重新分配,所以类外的_model 和类内的model 不会冲突,但如果你讨厌它,可以选择直接访问正在进行中的类的命名空间, vars()(或locals();在这种情况下两者是等价的,但我不认为类范围是本地人,尽管他们的行为很像)。 p>

因为作用域真的不是一个函数作用域,实际上你可以通过dictvars/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)

Try it online!

【讨论】:

此外,import 语句之所以有效,是因为它基本上将 attribute 的值分配给本地名称,而不是同名的非本地变量的值。 @chepner:是的,我的印象是 OP 明白,他们只是将其用作他们知道该怎么做但不知道如何适应类似用途的示例案例(他们俩最终都从一些现有名称也命名为X 中创建了一个名称为X 的类属性,但其中只有一个存在范围冲突),这就是我没有直接解决它的原因。 我提到它是因为class 语句的命名空间非作用域行为并不是真正的问题。您也不能从同名的非局部变量的值初始化函数中的局部变量。 @chepner 我想这是公平的,但如果它只是嵌套函数,则使用不同的变量名始终是一种选择,同时具有关键字参数和类属性名称对于这完全是一个问题是必要的.另外我猜var = globals()["var"] 会在需要时为全局变量工作。 使用不同的变量绝对是正确的解决方案。在编写函数时,您可以完全控制局部变量名称。

以上是关于在类体内使用非局部或全局的主要内容,如果未能解决你的问题,请参考以下文章

Python20之全局变量和局部变量

Python之变量的作用域

局部变量和全局变量的区别

Javascript中的作用域问题

shell中的局部变量与全局变量

C++ - 21局部变量和全局变量的区别