Python 奇特的自引用

下面是一个创建自引用列表并演示自引用的 Python 示例:

奇特的结果

下面是一个创建自引用列表并演示自引用的 Python 示例:

>>> a = a[0] = [0]
>>> a
[[...]]
>>> a[0]
[[...]]
>>> a[0][0]
[[...]]
>>> a is a[0]
True

输出结果显示,a[0] 指向 a 本身,这使得它成为一个自引用列表。 为什么这段简单的代码会创建一个自引用列表? 因为在将列表 [0] 赋值给 a[0] 时,a 尚未定义,所以不应该出现 NameError 错误吗?

下面是另一个类似的示例,它也创建了一个自引用列表:

>>> a = a[0] = [0, 0]
>>> a
[[...], 0]

下面是一个类似的字典示例:

>>> a = a[0] = {}
>>> a
{0: {...}}

请注意,在上述示例中,0 被用作字典键。 下面是另一个使用字符串键的非常简单的示例:

>>> a = a['k'] = {}
>>> a
{'k': {...}}

语言参考

我的第一个猜测是,这句话

a = a[0] = [0]

就像

new = [0]
a = new
a[0] = new

这确实会产生一个自引用 list。

The Python Language Reference 第 7.2 节(赋值语句)证实了这种行为。 在此引用该节的相关部分:

赋值语句用于将名称(重新)与值绑定,并修改可变对象的属性或item:

assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)
target_list     ::=  target ("," target)* [","]
target          ::=  identifier
                     | "(" [target_list] ")"
                     | "[" [target_list] "]"
                     | attributeref
                     | subscription
                     | slicing
                     | "*" target

(有关 attributeref、订阅和切分的语法定义,请参见 Primaries)。

赋值语句会对表达式列表(记住,表达式列表可以是单个表达式,也可以是逗号分隔的列表,后者产生的是一个元组)进行评估,并将评估出的单个对象从左到右赋值给每个目标列表。

我们看到赋值语句的定义如下:

assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)

因此

a = a[0] = [0]

有两个 target_list 元素(a 和 a[0])和一个 starred_expression 元素([0])。 因此,右侧的同一个列表从左到右被赋值给 a 和 a[0],即首先将列表 [0] 赋值给 a,然后将 a[0] 设置为同一个列表。 结果,a[0] 被设置为 a 本身。

语句的行为

a = a[0] = {}

可以用类似的方法来解释。首先将右侧的字典对象赋值给 a,然后在同一字典中插入键 0。最后,a[0] 的值被设置为同一个字典。换句话说,a[0] 被设置为 a 本身。

更多实验

首先对右侧的表达式列表进行执行,然后将执行结果从左到右赋值给每个目标列表,这解释了我们在前面章节中观察到的行为。这种从左到右的赋值在主流编程语言中并不常见。例如,在 C、C++、Java 和 JavaScript 中,简单赋值操作符 (=) 具有从右到左的关联性。Python 中的从左到右赋值可以通过一些故意的错误来进一步证明。下面是一个例子:

>>> a[0] = a = [0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

在本例中,当赋值到 a[0] to 时,名为 a 的变量尚未定义,因此导致 NameError。

下面是另一个例子:

>>> a = a[0] = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object does not support item assignment

在此示例中,首先将 0 赋值给 a。然后需要对 a[0] 进行求值,然后才能将 0 赋值给它,但求值失败,因为 a 是 int 类型,不支持订阅(也称为索引),所以会出现 TypeError。

你也许感兴趣的:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注