Class 在函数参数名称中与嵌套的 class 属性冲突

Class in function arguments name clash with nested class attributes

考虑这段代码:

def gee(bool_, int32, int64, str_):

    class S:
        bool_ = bool_
        int32 = int32
        int64 = int64
        str_ = str_

    return S

gee(1, 2, 3, 4)

运行 这给出了一个错误:

Traceback (most recent call last):
  File "test_.py", line 36, in <module>
    gee(1, 2, 3, 4)
  File "test_.py", line 27, in gee
    class S:
  File "test_.py", line 28, in S
    bool_ = bool_
NameError: name 'bool_' is not defined

我不知道哪些 scope/closure 规则适用于此。 nonlocal 修复了错误,但结果不是我所期望的:

def gee(bool_, int32, int64, str_):

    class S:
        nonlocal bool_, int32, int64, str_
        bool_ = None
        int32 = None
        int64 = None
        str_ = None
    print(bool_, int32, int64, str_ )

    return S

g = gee(1, 2, 3, 4)
g.bool_

输出:

None None None None
Traceback (most recent call last):
  File "test_.py", line 38, in <module>
    g.bool_
AttributeError: type object 'S' has no attribute 'bool_'

除了重命名之外,我还可以做些什么来使第一个代码片段中的赋值工作正常?为什么它会这样?因为有name = ...?为什么 Python 不在赋值前计算名称?

在一个范围内,如果您分配给一个名称,则它在该范围内是局部的。 class 主体是一个范围。因此,_bool 是本地的,因此尝试将 _bool 分配给它是一个错误,因为 _bool 尚未定义。与其他一些语言不同,Python 不会在尚未在作用域中定义名称时查看外部作用域。

正如您所注意到的,

nonlocal 并没有解决问题,因为它导致外部范围的名称被分配给。

简单的答案是使用不同的名称。

如果您仅在 S 的方法中使用这些值,另一种可能性是简单地使用闭包并访问方法中的值,就好像它们是局部值一样。那么你根本不需要它们作为 class 的属性。

当编译 class 时,使用与函数类似的规则来解析名称。因为 class 主体包含对 bool_ 变量的赋值,解释器将其视为 class 变量,因此当被要求解析其值时,会在 class 名称空间中查找作业的右侧。

一种可能性是使它们成为实例变量,在 __init__ 方法中分配,将参数默认为调用 gee:

时提供的值
def gee(bool_, int32, int64, str_):

    class S:
        def __init__(self, bool_=bool_, int32=int32, int64=int64, str_=str_):
            self.bool_ = bool_
            self.int32 = int32
            self.int64 = int64
            self.str_ = str_

    return S

my_class = gee(1, 2, 3, 4)
my_instance = my_class()

print(my_instance.bool_)

您应该会发现此代码打印 1。这是可能的原因是命名参数使用标准(词法)范围规则解析值表达式,而参数名称本身在每次调用开始时被注入命名空间。

如果您使用

形式的赋值将值绑定到 class 变量,您会发现代码也有效
S.bool_ = bool_

因为,在 __init__ 方法运行时,classname S 已经绑定在词法封闭的命名空间中。即使值绑定到 class,也可以相对于实例引用它们 - 因为它们不存在,解释器会继续在实例的 class.

中查找

但是,无论哪种情况,都需要调用 class 来设置 __init__ 中的属性值。

变量命名和范围是您的问题。如果您稍微更改代码,它应该可以工作。

def gee(some_bool, some_int32, some_int64, some_str):

    class S:
        bool_ = some_bool
        int32 = some_int32
        int64 = some_int64
        str_ = some_str

    return S

print(gee(1, 2, 3, 4))
# 1, 2, 3, 4

让我们用另一个问题来回答您的问题 - 您认为 Python 如何知道 bool_ = bool_ 行中的哪个 bool_

def gee(bool_, int32, int64, str_):

    class S:
        bool_ = bool_

第一个 bool_ 是 class 属性吗?还是函数中的 nonlocal bool_?还是 *gasps* 一个 global 对象?那么分配的对象呢,= bool_ 应该指的是哪个范围?

这就是为什么 Python 的口头禅是 "Explicit is better than Implicit"。范围应该明确定义 Python 以理解您所指的内容,否则解释器会假定最直接的范围。

但您已经知道这个问题的解决方案 - 只需将其重命名为不同的名称即可解决问题,因为它 明确地 告诉 Python 解释器哪个范围该对象指的是。

另一种方法是您可以将 class 属性定义移动到 class 范围之外:

def gee(bool_, int32, int64, str_):

    class S:
        pass

    S.bool_ = bool_
    S.int32 = int32
    ...
    return S

这样你就可以清楚地定义哪个bool_属于哪个范围。

您可以使用此代码。无需更改变量的名称。您可以使用属性和 class 构造函数来解决此问题。

def gee(bool_, int32, int64, str_):

    class S:
        def __init__(self, bool_, int32, int64, str_):
            self.bool_ = bool_
            self.int32 = int32
            self.int64 = int64
            self.str_ = str_

        def get_bool_(self):
            return self.bool_

        def set_bool_(self, bool_):
            self.bool_ = bool_

        def get_int32(self):
            return self.int32

        def set_int32(self, int32):
            self.int32 = int32

        def get_int64(self):
            return self.int64

        def set_int64(self, int64):
            self.int64 = int64

        def get_str_(self):
            return self.str_

        def set_str_(self, str_):
            self.str_ = str_

    print(bool_, int32, int64, str_ )

    return S
g = gee(1, 2, 3, 4)

一种可能是使它们成为实例变量,在 init 方法中分配,将参数默认为调用中提供的值。