问题 “赋值前引用的局部变量” - 只有函数?


请使用以下代码:

import something

def Foo():
    something = something.SomeClass()
    return something

......这显然不是有效的代码:

UnboundLocalError: local variable 'something' referenced before assignment

......作为局部变量 something 在RHS之前创建,但未分配 = 被评估。 (例如,参见 这个相关答案的评论对我来说这看起来有点奇怪,但当然,我会顺其自然。现在,为什么以下有效代码?

class Foo(object):
    something = something.SomeClass()

我的理解是内心的 class 定义本质上是一个范围:

然后,使用新创建的本地命名空间和原始全局命名空间,在新的执行框架中执行类的套件(请参阅命名和绑定一节)。

那么,为什么这些代码的行为与函数的行为不同?


8055
2018-01-31 23:33


起源

something 似乎有不止一个含义? - Johnsyweb
@Johnsyweb:是的,有点儿。但在这两种情况下,它都具有同样的意义。 (或者,至少,我读过的所有文档似乎都是这样说的。) - Thanatos


答案:


来自 python类文档

类定义在本地范围中放置另一个命名空间。

Python的一个特殊之处在于 - 如果没有全局语句生效 - 对名称的赋值总是进入最内层范围。分配不复制数据 - 它们只是将名称绑定到对象。删除也是如此:语句del x删除了x与本地范围引用的命名空间的绑定。实际上,引入新名称的所有操作都使用本地范围:特别是,import语句和函数定义绑定本地范围中的模块或函数名称。 (全局语句可用于指示特定变量存在于全局范围内。)

因此,在函数(或范围)中,赋值创建一个在绑定之前访问的本地未绑定变量,而在类定义中,它在赋值时在该类的“命名空间”字典中创建一个条目,允许解析 something 到外部命名空间(模块命名空间)。


6
2018-02-01 00:25





请考虑以下示例,这可能有助于澄清这一点:

import datetime

class Foo(object):
    datetime = datetime.datetime

>>> datetime
<module 'datetime' from '/usr/lib/python2.6/lib-dynload/datetime.so'>
>>> Foo.datetime
<type 'datetime.datetime'>

注意这一行 datetime = datetime.datetime 实际上是分配给名称 Foo.datetime,这与全球性并不含糊 datetime (就像在函数中使用相同的代码一样)。

总之,由于类定义创建了新的命名空间以及新的范围,因此您可以直接访问封闭范围中的名称并在本地范围中指定相同的名称。


5
2018-01-31 23:57



“注意这一行 datetime = datetime.datetime 实际上是分配给名称 Foo.datetime,这与全球性并不含糊 datetime“ - 不:它指的是名字 datetime:如果你加两个 print datetime 陈述,前一个 datetime = 一个又一个,怎么样 datetime 在那个声明中 不 模糊,以某种方式与函数中的相同语句不同? - Thanatos
@Thanatos - 是的,我可以看出这是不清楚的。我想说的是,由类创建的命名空间允许以明确的方式从其他范围访问全局属性和类属性,但你是对的,在类的本地范围内仍然存在歧义。 - Andrew Clark
但问题的症结在于,这与函数中发生的情况有何不同?您声明,“因为类定义创建了一个新的命名空间以及一个新的范围” - 不是函数创建了新的命名空间和新的范围吗? - “你可以直接访问封闭范围内的名字” - 我可以在函数中执行此操作 - “并在本地范围内指定相同的名称” - 我只能在类定义中执行此操作...为什么? - Thanatos
“不要让函数创建新的命名空间和新的范围吗?” - 函数创建的命名空间在函数返回或引发异常时被删除,而类创建的命名空间在类定义后仍然存在。命名空间行为的这种差异是允许在类中而不是在函数中进行赋值的基础。 - Andrew Clark