这节课提供更多有关Python储存序列的信息。复制并比较序列会有预料之外的结果。我们会解释Python内部是如何运行的,让你理解并避免这些错误。比如,一个普遍的问题是几个不同的变量会“表明”或“指向”相同的序列。这节课的末尾我们会引入"is
"运算符。这个运算符是用来判断是否多个变量确实指向同一个序列。
范例
我们想要一串代码,将一个英尺为单位的长度序列oldSize
转变为一个以厘米为单位的长度序列newSize
。其中一种方式是使用newSize = oldSize
来建立一份副本,然后接着往下改变所有的值:
oldSize = ["letter", 8.5, 11] # size of paper in inches newSize = oldSize # make a copy? newSize[1] = newSize[1]*2.54 # convert to cm newSize[2] = newSize[2]*2.54 # convert to cm现在让我们看看是否能正常运行。如果你打印
newSize
,那么你会得到["letter", 21.59, 27.94]
。注意,当我们打印oldSize
的时候,oldSize
的值会改变!下面是具体解释:
我们会在下面通过使用图表详细解释刚刚发生了什么。其实这里的主要问题是newSize = oldSize
没有复制整个序列:它只是将参考(箭头)复制到了相同的序列上。
点击标题改变标签。
city = "Moose Factory" population = 2458 employer = city这个表格(带黑色边框的)展示了Python 的内存:
第一个变量的名字是
city
,值为字符串"Moose Factory"
。第二个变量的名字是population
,值是整数2458
。第三个变量名字是employer
,值是字符串"Moose Factory"
。myList = ["Moose Factory", 2458]只会创建一个变量,名为
myList
。一个序列被创建,myList
的值"指向"或"对应"那个序列。我们用一个盒子代表序列,序列的项在盒子里紧挨着他们对应的索引。序列是蓝色的。箭头表明
myList
指向新的序列。序列中索引为0的那一项是字符串"Moose Factory"
,索引为1的那一项是整数2458
。比如,如果你print(myList[1])
,那么Python会输出2458
。myList = ["Moose Factory", 2458] myList[1] = myList[1]+1Python计算
2458+1
,结果是2459
,这个会替换序列中索引为1的项。更新后,我们得到以下图表。(划掉的2458 是只是为了强调这个改变。)
oldSize
and newSize
oldSize = ["letter", 8.5, 11]这一行运行后,Python的内存就如以下图表所示。我们创建了一个长为3的序列。
newSize = oldSize这节课最重要的一点是:
=
并不复制序列!相反,它只创建了一个新的指向,指向同一个序列。这可以用两个不同的箭头指向同一个盒子表明。如下表所示,我们有两个变量,两个都指向同一个序列。
newSize[1] = newSize[1]*2.54Python会查看
newSize
,查看索引为1的值(8.5),并乘2.54,然后代替原来的值。然而,因为oldSize
指向相同的序列,我们也影响了oldSize
指向的值!(同样,划掉的8.5 只是为了强调这个改变。)
newSize[2] = newSize[2]*2.54影响序列中的另一个值。在这一行运行后,Python的内存就如下图所示。
现在当我们打印
newSize
或 oldSize
,Python输出["letter", 21.59, 27.94]
。为了查看没有评论的图片,在可视化窗口中运行相同的代码。 |
Python 中的每一个值都是位于特定内存块中的对象。并且每个变量只是一个参考/指针/箭头。比如,在第一个标签中,内存只包含一个"Moose Factory",两个箭头指向它。因为数字和字符串是"不可改变的",我们在这些课中不用箭头画它们。但是在后台,Python对所有的数据一视同仁。详情。 |
Python中的"深度"复制序列
在上述例子中,我们使用=
来在现存的序列中创建第二指针/参考/箭头,通常被叫做"浅"复制。尽管它是虽然有时这么做有用,但在此时这并不是我们想要的。反而,我们想创建一个全新的序列副本。那么该如何完成呢?
我们给出三个答案。它们基本上相同,但是每一个都会告诉你一个关于Python的新知识,所以三个都值得阅读。
方法1,使用[:]
在上述例子中,我们证明了newList = oldList[:]
创建了原来序列的副本。尽管这个句法看起来很陌生,但其实我们见到过类似的东西。在字符串课程中,我们介绍了一种分离子链的方式:string[first:tail]
返还子链,从索引first
开始,直到索引tail-1
结束。我们曾经提到这还可以应用于创建序列的子序列。除此以外,
- 如果你省略
first
,然后取默认值0
; - 如果你省略
tail
,然后取len
的默认值(序列/字符串的长度)。
所以实际上,oldList[:]
创建一个新的子序列。这个子序列与原序列相同,是一份新的副本。
方法2,使用copy.copy()
有一个模块叫做copy
,里面有几个关于复制的方法。最简单的一个叫做copy.copy(L)
:当你对序列L
调用这个程序时,程序会返还L
真正的副本。
copy.copy()
函数也对其他种类的对象适用,但在这里我们先不讨论。
方法3,使用list()
最后一种得到真正副本的方式是使用list()方程。本来,list()
是被用来将其它类型的数据转化成list
种类(比如,list("hello")
将字符串"hello"转化为一个有五项的序列,每一项是一个字符)。但是如果你想要将一个序列转化成一个序列,它只会创建一个副本。
序列作为参数
注意,因为序列本身运行的方式,任何将序列当作参数的函数都可以改变序列中的内容(在replace
练习题中出现过)。
def func(list): list[0] = list[0] + list[1] list[1] = list[1] + list[0] data = [3, 4] func(data) print(data[0]*data[1])
func
中,索引0的数字变为7,索引1的数字变为7+4=11。所以我们打印7*11。使用is
比较序列
什么时候两个序列变量L1
和L2
相等?我们有两种方式来转述这个问题:
- 相同身份:
L1
和L2
指向相同的序列对象么? - 相同值:序列
L1
的内容与序列L2
的内容相同么?
在Python中,标准的相同运算符==
意味着相同的值,如下所示。
为了测试相同身份,我们用Python中的is
运算符。使用这个运算符的方式与==
一样:句法
«list1» is «list2»
如果这些序列指向同一个序列,返还True
;如果他们指向不同的序列(即使他们有相同的内容),返还False
。
True
出现了多少次?(绘制一个图来帮助追踪每一步)
list1 = [9, 1, 1] list3 = list(list1) list2 = list1 list4 = list3[:] list1[0] = 4 list5 = list1 print(list1 is list2, list2 is list3, list3 is list4, list4 is list5) print(list1 == list2, list2 == list3, list3 == list4, list4 == list5)
True False False False
True False True False 你不应该将is 用在字符串或数字中,因为 == 已经正确地测试出相等性,is 很难预测字符串和数字。 |
嵌套序列
我们已经讲了很多,但是还有一个值得一提的常见情况。前面的课中提到过,一个嵌套序列是一个序列嵌套另一个序列,比如
sample = [365.25, ["first", 5]]
sample
所指的外面的序列有两项;索引为0的项是小数,索引为1的是里面的序列。里面的序列是["first", 5]
(也可以有多层嵌套)。一旦你开始使用嵌套序列,记得以下几点:
- 将上面的三个方法用于
sample
会复制外面的序列,而不会复制里面的序列。所以copy(sample)[1] is sample[1]
意味着副本仍指向原序列中的一部分。可能这不是你想要的。如果你想在各个层次上复制,使用copy.deepcopy()
。 - 使用
==
测试嵌套序列给了我们启发:Python重复地对序列地每一项调用==
。比如,[[1, 2], 3]==[[1, 2], 3]
是True
,[[1, 2], 3]==[1, 2, 3]
是False
,因为前几项是不一样的([1, 2] != 1
)。
这节课的内容到此结束!下面的内容为选学。
数组(元组):("immutable", "lists")
在上面提到过,对一个序列调用一个方程可以改变序列。有时我们并不想让这种情况发生!Python中的一种解决方式是创建数组。数组本质和序列一样,但是不可以被改动。我们说序列是"可改动的",数组是"不可改动的"(字符串和数字也是不可改动的)。这可以用来预防任何编程错误,包括误改序列。数组使用小括号()
而不是中括号[]
。数组和序列可以通过使用tuple()
和list()
相互转换。
To ∞
and Beyond:不合群
创建一个包含自身的序列是可能的!只需创建一个序列,然后将其中的一项重新指回整个的序列:
注意Python的输出引擎能够识别序列自身循环: 它打印出"...
",而不是重复打印所有的L
,以此避免无限循环。