Python里的那些“坑”
Python是一门非常容易上手的语言,对于初学者非常友好。但是,这种简单易上手的代价是隐藏在它背后的巨大复杂度。这也就造成了在很多时候,会发生一些看起来很神奇的事情。其实,这只是对Python背后的机制不了解造成的。今天,笔者带大家梳理一下Python中容易踩到的那些”坑“。
变量是“盒子”吗?
很多人都认为,Python里的变量像是一个“盒子”。它里面可以装各种各样的数据,当我们把其他值赋给这个变量的时候,就相当于把盒子里的数据倒掉,然后再装新的值。
但实际上,Python里的数据才是占主导地位的,变量像是一个”便利贴”。把当我们把其他值赋给这个变量的时候,相当于把变量这个便利贴从原数据上撕掉,再贴到新的数据盒子上。
嗯?不信?我们可以做个实验。
1 | a = [1, 2, 3] |
当然,对于不可变对象,你会发现更改b对于a没有影响。这是因为在你更改b的那一瞬间,由于a和b指向的是不可变对象,Python会创建出一个新的对象,然后把b这个便利贴贴过去。
1 | a = (1, 2, 3) |
真的新建了对象吗?
假设我们有一个场景,需要创建一个二维数组九宫格,那初学者可能会直接这样写:
1 | a = [0, 0, 0] |
但是当你给这个b赋值的时候,你会发现,事情好像不太对…
1 | n = 1 |
问题就出在b = [a] * 3
这一步。在这一步里,这三个a实际上是等价的,它们指向的是同一个数据。我们如果想实现真正的内部各不相同的二维数组,需要这样:
1 | b = [[0] * 3 for _ in range(3)] |
在[0] * 3
这一步,由于整数0是不可变对象,所以生成的[0, 0, 0]
更改其中一个并不影响其他值(注意,这三个0指向同一个数据。实际上,Python中所有相同的小整数之间全部等价)。在下一步中,我们使用列表推导式的方法强迫Python生成三个不同的[0, 0, 0]
列表对象出来。所以可以得到正确的结果。
深拷贝 / 浅拷贝
让我们来看这样一个场景:
1 | from copy import copy |
哎,怎么回事。明明进行拷贝了,怎么b的更改依然会影响到a?赶紧打印一下他们的id看看:
1 | print(id(a)) |
明明id确实不一样,说明他们的确不是指向同一个数据,那到底是为什么呢?实际上,a和b确实不同,但a与b这两个列表中的元素指向的却是同一个数据,如下图:
我们需要使用深拷贝deepcopy,才能把列表里的列表(或其他结构,或更深的嵌套维度)全部进行真正的拷贝。
1 | from copy import deepcopy |
未完待续…