有趣的 Python 特性,一个你绝对猜不到的结果。
本文字数:1441 字
阅读本文大概需要:4 分钟
写在之前
Python 提供了很多让使用者觉得舒服至极的功能特性,但是随着不断的深入学习和使用 Python,我发现其中存在着许多玄学的输出与之前预想的结果大相径庭,这个对于初学者来说难以理解,但是在理解它们以后又会觉得是这么的有意思,所以我准备了这个「有趣的 Python 特性」系列,写一些我碰到或看到的一些你所不知道的「奇葩」,这里面会涉及到在 Python2 和 Python3 中的异同,希望大家能从学习的过程中体会到真正的乐趣。
猜不到的结果
首先我们先来看一行代码:
a, b = a[b] = {}, 5
看完上面的代码,现在问题来了,你知道 a,b 的值是多少么?先仔细思考一下。如果思考完毕,请继续往下看。
在交互模式中输出一下,结果如下所示:
>>> a
{5: ({...}, 5)}
>>> b
5
怎么样?猜对了么?我猜大多数人看到这个结果都会很懵圈,就算不说结果,很多人看到最开始的那行代码,也会觉得没有头脑,下面就让我来详细的说一下,为什么是这样。
首先关于赋值语句,很多人都用过,但是更多的只是常用的形式,就是 a = b 这种模式,很少有人去看官方文档中关于赋值语句的形式:
(target_list "=")+ (expression_list | yield_expression)
上面的 expression_list 是赋值语句计算表达式列表,这个可以是单个表达式或者是以逗号分割的列表(如果是后者的话,返回的是元组),并且将单个结果对象从左到右分给目标列表(target_list)中的每一项。
下面我结合这个赋值语句的形式和文章开头的代码详细说一下为什么会出现这样一个我们猜不到的结果:
首先是 (target_list "=")+,前面好容易理解,后面带着的 + 意味着可以有一个或者多个的「目标列表」。在上面的代码中,目标列表就有两个:a, b 和 a[b]。这里要注意的是「表达式列表」只能有一个({}, 5)。
「表达式列表」计算结束后,将它的值从左到右分配给「目标列表」。在上面的代码中,即将 {},5 元组并赋值给 a, b,所以我们就得到了 a = {},b = 5(此处 a 被赋值的 {} 是「可变对象」)。
接着我们来看第二个「目标列表」a[b],很多人对这个地方有困惑,觉得这个地方应该报错,因为他们觉得在之前的语句中 a 和 b 并没有被赋值。其实我们已经赋值了,我们刚将 a 赋值了 {},b 赋值了 5。
下面我们将 a 字典中 5 键的值设置为元组 ({}, 5)来创建「循环引用」,{...} 指的是与 a 引用了相同的对象。
下面再来看一个简单一些的循环引用的例子:
>>> test_list = test_list[0] = [0]
>>> test_list
[[...]]
>>> test_list[0]
[[...]]
>>> test_list[0][0][0][0] is test_list
True
其实在文章最初时的那行代码中也是像这样的,比如 a[b][0] 和 a 其实是相同的对象,同样 a[b][0][b][0],a[b][0][b][0][b][0],... 都和 a 是相同的对象。
>>> a[b][0][b][0] is a
True
>>> a[b][0] is a
True
如上,我们也可以完全把文章开头的例子拆解成如下形式:
a, b = {}, 5
a[b] = a, b
这样,是不是更好理解一些了呢?
新上课程