【问题标题】:Class in function arguments name clash with nested class attributes函数参数名称中的类与嵌套类属性冲突
【发布时间】:2019-07-13 05:44:26
【问题描述】:

考虑这段代码:

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

我不知道这里适用哪些范围/关闭规则。 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_'

除了重命名之外,我还能做些什么来使第一个代码 sn-p 中的分配工作?为什么它会这样?因为有name = ...?为什么 Python 在赋值之前不评估名称?

【问题讨论】:

    标签: python scope closures inner-classes


    【解决方案1】:

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

    nonlocal,正如您所注意到的,它不能解决问题,因为它会导致外部作用域的名称被分配给。

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

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

    【讨论】:

    • 类主体并没有真正创建一个新的范围。这就是为什么 Python 有 LEGB 范围规则而不是 LCEGB(或其他东西)。但正如你所说,重点是bool_ = 让 Python 决定名称在类的命名空间内,并且到那时它还没有被定义。
    【解决方案2】:

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

    一种可能性是使它们成为实例变量,在__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。这可能的原因是命名参数使用标准(词法)范围规则解析值表达式,而参数名称本身在每次调用开始时被注入命名空间。

    如果您使用表单的赋值将值绑定到类变量,您会发现代码也可以工作

    S.bool_ = bool_
    

    因为,当__init__ 方法运行时,类名S 已经绑定在词法封闭的命名空间中。即使这些值绑定到类,它们也可以相对于实例进行引用——因为它们不存在于那里,解释器会继续在实例的类中查找。

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

    【讨论】:

    • 很好解释。正如您所说,这里的范围规则似乎与带有bool_ = bool_-like 调用的嵌套函数相同。我认为在这种情况下它会有所不同,因为类是名称空间并且类的主体不会创建新的范围。您是否有支持您的第一条语句的文档源,还是仅在 cPython 源代码中?
    • 我猜逻辑候选者应该是this page——关于范围和命名空间的文档。
    • 是的,就是这样:“类定义在本地范围内放置了另一个命名空间。”。但对我来说,仅从那句话来看,如果没有代码,就很难得到真正的影响。
    • 该部分可能需要评论 - 我们在 Python 网站管理员地址上对其性质发表了一些评论。
    【解决方案3】:

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

    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。
    • 我现在看到@JCode
    【解决方案4】:

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

    def gee(bool_, int32, int64, str_):
    
        class S:
            bool_ = bool_
    

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

    这就是为什么 Python 的口号是“显式优于隐式”。应该明确定义范围,以便 Python 理解您所指的范围,否则解释器会假定最直接的范围。

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

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

    def gee(bool_, int32, int64, str_):
    
        class S:
            pass
    
        S.bool_ = bool_
        S.int32 = int32
        ...
        return S
    

    这样你可以清楚地定义哪个bool_属于哪个作用域。

    【讨论】:

    • 很好地指出 Python 不会进行猜测,我的构造错过了一些明确的语句来告诉解释器我想要什么。我接受了@holdenweb 的答案,因为它是在你之前发布的,但你的反问真的很有价值。
    • 请注意,虽然@holdenweb 提供了一个很好的答案,但如果定义包含在def __init__() 中,则实例属性在类被实例化之前将不可用。如果您需要在实例化之前 使类属性可用,您可以尝试我的方法(这并不意味着您需要接受我的回答 - 您可以选择最适合您的解决方案)。
    • 实际上有你在@holdenweb 回答中提到的类属性案例。它只是比实例属性示例占用更少的空间。
    • 你说得对,我太关注代码块了。无论哪种方式,如果它解决了您的问题,那么一切都很好,干杯!
    【解决方案5】:

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

    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)
    

    【讨论】:

    【解决方案6】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-23
      • 2015-11-09
      • 1970-01-01
      • 2012-04-15
      • 2015-02-26
      • 1970-01-01
      • 2011-04-15
      • 1970-01-01
      相关资源
      最近更新 更多