首页 » Python编程快速上手 » Python编程快速上手全文在线阅读

《Python编程快速上手》第4章 列表

关灯直达底部

在你能够开始编写程序之前,还有一个主题需要理解,那就是列表数据类型及元组。列表和元组可以包含多个值,这样编写程序来处理大量数据就变得更容易。而且,由于列表本身又可以包含其他列表,所以可以用它们将数据安排成层次结构。

本章将探讨列表的基础知识。我也会讲授关于方法的内容。方法也是函数,它们与特定数据类型的值绑定。然后我会简单介绍类似列表的元组和字符串数据类型,以及它们与列表值的比较。下一章将介绍字典数据类型。

4.1 列表数据类型

“列表”是一个值,它包含多个字构成的序列。术语“列表值”指的是列表本身(它作为一个值,可以保存在变量中,或传递给函数,像所有其他值一样),而不是指列表值之内的那些值。列表值看起来像这样:['cat', 'bat', 'rat', 'elephant']。就像字符串值用引号来标记字符串的起止一样,列表用左方括号开始,右方括号结束,即。列表中的值也称为“表项”。表项用逗号分隔(就是说,它们是“逗号分隔的”)。例如,在交互式环境中输入以下代码:

 >>> [1, 2, 3] [1, 2, 3] >>> ['cat', 'bat', 'rat', 'elephant'] ['cat', 'bat', 'rat', 'elephant'] >>> ['hello', 3.1415, True, None, 42] ['hello', 3.1415, True, None, 42]❶ >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam ['cat', 'bat', 'rat', 'elephant']  

spam变量❶仍然只被赋予一个值:列表值。但列表值本身包含多个值。是一个空列表,不包含任何值,类似于空字符串’’。

4.1.1 用下标取得列表中的单个值

假定列表['cat', 'bat', 'rat', 'elephant']保存在名为spam的变量中。Python代码spam[0]将求值为'cat',spam[1]将求值为'bat',依此类推。列表后面方括号内的整数被称为“下标”。列表中第一个值的下标是0,第二个值的下标是1,第三个值的下标是2,依此类推。图4-1展示了一个赋给spam的列表值,以及下标表达式的求值结果。

图4-1 一个列表值保存在spam变量中,展示了每个下标指向哪个值

例如,在交互式环境中输入以下表达式。开始将列表赋给变量spam。

 >>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam[0] 'cat' >>> spam[1] 'bat' >>> spam[2] 'rat' >>> spam[3] 'elephant' >>> ['cat', 'bat', 'rat', 'elephant'][3] 'elephant'❶ >>> 'Hello ' + spam[0]❷ 'Hello cat' >>> 'The ' + spam[1] + ' ate the ' + spam[0] + '.' 'The bat ate the cat.'  

请注意,表达式'Hello ' + spam[0] ❶求值为'Hello ' + 'cat',因为spam[0]求值为字符串'cat'。这个表达式也因此求值为字符串'Hello cat'❷。

如果使用的下标超出了列表中值的个数,Python将给出IndexError出错信息。

>>> spam = ['cat', 'bat', 'rat', 'elephant']>>> spam[10000]Traceback (most recent call last):  File "<pyshell#9>", line 1, in <module>    spam[10000]IndexError: list index out of range  

下标只能是整数,不能是浮点值。下面的例子将导致TypeError错误:

>>> spam = ['cat', 'bat', 'rat', 'elephant']>>> spam[1]'bat'>>> spam[1.0]Traceback (most recent call last):  File "<pyshell#13>", line 1, in <module>    spam[1.0]TypeError: list indices must be integers, not float>>> spam[int(1.0)]'bat'  

列表也可以包含其他列表值。这些列表的列表中的值,可以通过多重下标来访问,像这样:

>>> spam = [['cat', 'bat'], [10, 20, 30, 40, 50]]>>> spam[0]['cat', 'bat']>>> spam[0][1]'bat'>>> spam[1][4]50  

第一个下标表明使用哪个列表值,第二个下标表明该列表值中的值。例如,spam[0][1]打印出'bat',即第一个列表中的第二个值。如果只使用一个下标,程序将打印出该下标处的完整列表值。

4.1.2 负数下标

虽然下标从0开始并向上增长,但也可以用负整数作为下标。整数值−1指的是列表中的最后一个下标,−2指的是列表中倒数第二个下标,以此类推。在交互式环境中输入以下代码:

>>> spam = ['cat', 'bat', 'rat', 'elephant']>>> spam[-1]'elephant'>>> spam[-3]'bat'>>> 'The ' + spam[-1] + ' is afraid of the ' + spam[-3] + '.''The elephant is afraid of the bat.'  

4.1.3 利用切片取得子列表

就像下标可以从列表中取得单个值一样,“切片”可以从列表中取得多个值,结果是一个新列表。切片输入在一对方括号中,像下标一样,但它有两个冒号分隔的整数。请注意下标和切片的不同。

  • spam[2]是一个列表和下标(一个整数)。
  • spam[1:4]是一个列表和切片(两个整数)。

在一个切片中,第一个整数是切片开始处的下标。第二个整数是切片结束处的下标。切片向上增长,直至第二个下标的值,但不包括它。切片求值为一个新的列表值。在交互式环境中输入以下代码:

>>> spam = ['cat', 'bat', 'rat', 'elephant']>>> spam[0:4]['cat', 'bat', 'rat', 'elephant']>>> spam[1:3]['bat', 'rat']>>> spam[0:-1]['cat', 'bat', 'rat']  

作为快捷方法,你可以省略切片中冒号两边的一个下标或两个下标。省略第一个下标相当于使用0,或列表的开始。省略第二个下标相当于使用列表的长度,意味着分片直至列表的末尾。在交互式环境中输入以下代码:

>>> spam = ['cat', 'bat', 'rat', 'elephant']>>> spam[:2]['cat', 'bat']>>> spam[1:]['bat', 'rat', 'elephant']>>> spam[:]['cat', 'bat', 'rat', 'elephant']  

4.1.4 用len取得列表的长度

len函数将返回传递给它的列表中值的个数,就像它能计算字符串中字符的个数一样。在交互式环境中输入以下代码:

>>> spam = ['cat', 'dog', 'moose']>>> len(spam)3  

4.1.5 用下标改变列表中的值

一般情况下,赋值语句左边是一个变量名,就像spam = 4。但是,也可以使用列表的下标来改变下标处的值。例如,spam[1] = 'aardvark'意味着“将列表spam下标1处的值赋值为字符串'aardvark'。在交互式环境中输入以下代码:

>>> spam = ['cat', 'bat', 'rat', 'elephant']>>> spam[1] = 'aardvark'>>> spam['cat', 'aardvark', 'rat', 'elephant']>>> spam[2] = spam[1]>>> spam['cat', 'aardvark', 'aardvark', 'elephant']>>> spam[-1] = 12345>>> spam['cat', 'aardvark', 'aardvark', 12345]  

4.1.6 列表连接和列表复制

+操作符可以连接两个列表,得到一个新列表,就像它将两个字符串合并成一个新字符串一样。*操作符可以用于一个列表和一个整数,实现列表的复制。在交互式环境中输入以下代码:

>>> [1, 2, 3] + ['A', 'B', 'C'][1, 2,  3,  'A',  'B',  'C']>>> ['X', 'Y', 'Z'] * 3['X', 'Y', 'Z', 'X', 'Y', 'Z', 'X', 'Y', 'Z']>>> spam = [1, 2, 3]>>> spam = spam + ['A', 'B', 'C']>>> spam[1, 2, 3, 'A', 'B', 'C']  

4.1.7 用del语句从列表中删除值

del语句将删除列表中下标处的值,表中被删除值后面的所有值,都将向前移动一个下标。例如,在交互式环境中输入以下代码:

>>> spam = ['cat', 'bat', 'rat', 'elephant']>>> del spam[2]>>> spam['cat', 'bat', 'elephant']>>> del spam[2]>>> spam['cat', 'bat']  

del语句也可用于一个简单变量,删除它,作用就像是“取消赋值”语句。如果在删除之后试图使用该变量,就会遇到NameError错误,因为该变量已不再存在。

在实践中,你几乎永远不需要删除简单变量。del语句几乎总是用于删除列表中的值。

4.2 使用列表

当你第一次开始编程时,很容易会创建许多独立的变量,来保存一组类似的值。例如,如果要保存我的猫的名字,可能会写出这样的代码:

catName1 = 'Zophie'catName2 = 'Pooka'catName3 = 'Simon'catName4 = 'Lady Macbeth'catName5 = 'Fat-tail'catName6 = 'Miss Cleo'  

事实表明这是一种不好的编程方式。举一个例子,如果猫的数目发生改变,程序就不得不增加变量,来保存更多的猫。这种类型的程序也有很多重复或几乎相等的代码。考虑下面的程序中有多少重复代码,在文本编辑器中输入它并保存为allMyCats1.py:

print('Enter the name of cat 1:')catName1 = inputprint('Enter the name of cat 2:')catName2 = inputprint('Enter the name of cat 3:')catName3 = inputprint('Enter the name of cat 4:')catName4 = inputprint('Enter the name of cat 5:')catName5 = inputprint('Enter the name of cat 6:')catName6 = inputprint('The cat names are:')print(catName1 + ' ' + catName2 + ' ' + catName3 + ' ' + catName4 + ' ' +catName5 + ' ' + catName6)  

不必使用多个重复的变量,你可以使用单个变量,包含一个列表值。例如,下面是新的改进版本的allMyCats1.py程序。这个新版本使用了一个列表,可以保存用户输入的任意多的猫。在新的文件编辑器窗口中,输入以下代码并保存为allMyCats2.py。

catNames = while True:    print('Enter the name of cat ' + str(len(catNames) + 1) +      ' (Or enter nothing to stop.):')    name = input    if name == '':break    catNames = catNames + [name] # list concatenationprint('The cat names are:')for name in catNames:    print(' ' + name)  

运行这个程序,输出看起来像这样:

Enter the name of cat 1 (Or enter nothing to stop.):ZophieEnter the name of cat 2 (Or enter nothing to stop.):PookaEnter the name of cat 3 (Or enter nothing to stop.):SimonEnter the name of cat 4 (Or enter nothing to stop.):Lady MacbethEnter the name of cat 5 (Or enter nothing to stop.):Fat-tailEnter the name of cat 6 (Or enter nothing to stop.):Miss CleoEnter the name of cat 7 (Or enter nothing to stop.):The cat names are:  Zophie  Pooka  Simon  Lady Macbeth  Fat-tail  Miss Cleo  

使用列表的好处在于,现在数据放在一个结构中,所以程序能够更灵活的处理数据,比放在一些重复的变量中方便。

4.2.1 列表用于循环

在第2章中,你学习了使用循环,对一段代码执行一定次数。从技术上说,循环是针对一个列表或类似列表中的每个值,重复地执行代码块。例如,如果执行以下代码:

for i in range(4):    print(i)  

程序的输出将是:

0123  

这是因为range(4)的返回值是类似列表的值。Python认为它类似于[0, 1, 2, 3]。下面的程序和前面的程序输出相同:

for i in [0, 1, 2, 3]:    print(i)  

前面的for循环实际上是在循环执行它的子句,在每次迭代中,让变量依次设置为列表中的值。

注意

在本书中,我使用术语“类似列表”,来指技术上称为“序列”的数据类型。但是,你不需要知道这个术语的技术定义。

一个常见的Python技巧,是在for循环中使用range(len(someList)),迭代列表的每一个下标。例如,在交互式环境中输入以下代码:

>>> supplies = ['pens', 'staplers', 'flame-throwers', 'binders']>>> for i in range(len(supplies)):    print('Index ' + str(i) + ' in supplies is: ' + supplies[i]) Index 0 in supplies is: pensIndex 1 in supplies is: staplersIndex 2 in supplies is: flame-throwersIndex 3 in supplies is: binders  

在前面的循环中使用range(len(supplies))很方便,这是因为,循环中的代码可以访问下标(通过变量i),以及下标处的值(通过supplies[i])。最妙的是,range(len(supplies))将迭代supplies的所有下标,无论它包含多少表项。

4.2.2 in和not in操作符

利用in和not in操作符,可以确定一个值否在列表中。像其他操作符一样,in和not in用在表达式中,连接两个值:一个要在列表中查找的值,以及待查找的列表。这些表达式将求值为布尔值。在交互式环境中输入以下代码:

>>> 'howdy' in ['hello', 'hi', 'howdy', 'heyas']True>>> spam = ['hello', 'hi', 'howdy', 'heyas']>>> 'cat' in spamFalse>>> 'howdy' not in spamFalse>>> 'cat' not in spamTrue  

例如,下面的程序让用户输入一个宠物名字,然后检查该名字是否在宠物列表中。打开一个新的文件编辑器窗口,输入以下代码,并保存为myPets.py:

myPets = ['Zophie', 'Pooka', 'Fat-tail']print('Enter a pet name:')name = inputif name not in myPets:    print('I do not have a pet named ' + name)else:    print(name + ' is my pet.')  

输出可能像这样:

Enter a pet name:FootfootI do not have a pet named Footfoot  

4.2.3 多重赋值技巧

多重赋值技巧是一种快捷方式,让你在一行代码中,用列表中的值为多个变量赋值。所以不必像这样:

>>> cat = ['fat', 'black', 'loud']>>> size = cat[0]>>> color = cat[1]>>> disposition = cat[2]  

而是输入下面的代码:

>>> cat = ['fat', 'black', 'loud']>>> size, color, disposition = cat  

变量的数目和列表的长度必须严格相等,否则Python将给出ValueError:

>>> cat = ['fat', 'black', 'loud']>>> size, color, disposition, name = catTraceback (most recent call last):  File "<pyshell#84>", line 1, in <module>    size, color, disposition, name = catValueError: need more than 3 values to unpack  

4.3 增强的赋值操作

在对变量赋值时,常常会用到变量本身。例如,将42赋给变量spam之后,用下面的代码让spam的值增加1:

>>> spam = 42>>> spam = spam + 1>>> spam43  

作为一种快捷方式,可以用增强的赋值操作符+=来完成同样的事:

>>> spam = 42>>> spam += 1>>> spam43  

针对+、-、*、/和%操作符,都有增强的赋值操作符,如表4-1所示。

表4-1 增强的赋值操作符

增强的赋值语句

等价的赋值语句

spam += 1

spam = spam + 1

spam -= 1

spam = spam - 1

spam *= 1

spam = spam * 1

spam /= 1

spam = spam / 1

spam %= 1

spam = spam % 1

+=操作符也可以完成字符串和列表的连接,*=操作符可以完成字符串和列表的复制。在交互式环境中输入以下代码:

>>> spam = 'Hello'>>> spam += ' world!'>>> spam'Hello world!'>>> bacon = ['Zophie']>>> bacon *= 3>>> bacon['Zophie', 'Zophie', 'Zophie']  

4.4 方法

方法和函数是一回事,只是它是调用在一个值上。例如,如果一个列表值存储在spam中,你可以在这个列表上调用index列表方法(稍后我会解释),就像spam.index('hello')一样。方法部分跟在这个值后面,以一个句点分隔。

每种数据类型都有它自己的一组方法。例如,列表数据类型有一些有用的方法,用来查找、添加、删除或操作列表中的值。

4.4.1 用index方法在列表中查找值

列表值有一个index方法,可以传入一个值,如果该值存在于列表中,就返回它的下标。如果该值不在列表中,Python就报ValueError。在交互式环境中输入以下代码:

>>> spam = ['hello', 'hi', 'howdy', 'heyas']>>> spam.index('hello')0>>> spam.index('heyas')3>>> spam.index('howdy howdy howdy')Traceback (most recent call last):  File "<pyshell#31>", line 1, in <module>    spam.index('howdy howdy howdy')ValueError: 'howdy howdy howdy' is not in list  

如果列表中存在重复的值,就返回它第一次出现的下标。在交互式环境中输入以下代码,注意index返回1,而不是3:

>>> spam = ['Zophie', 'Pooka', 'Fat-tail', 'Pooka']>>> spam.index('Pooka')1  

4.4.2 用append和insert方法在列表中添加值

要在列表中添加新值,就使用append和 insert方法。在交互式环境中输入以下代码,对变量spam中的列表调用append方法:

>>> spam = ['cat', 'dog', 'bat']>>> spam.append('moose')>>> spam['cat', 'dog', 'bat', 'moose']  

前面的append方法调用,将参数添加到列表末尾。insert方法可以在列表任意下标处插入一个值。insert方法的第一个参数是新值的下标,第二个参数是要插入的新值。在交互式环境中输入以下代码:

>>> spam = ['cat', 'dog', 'bat']>>> spam.insert(1, 'chicken')>>> spam['cat', 'chicken', 'dog', 'bat']  

请注意,代码是spam.append('moose')和spam.insert(1, 'chicken'),而不是spam = spam.append('moose')和spam = spam.insert(1, 'chicken')。append和insert都不会将spam的新值作为其返回值(实际上,append和insert的返回值是None,所以你肯定不希望将它保存为变量的新值)。但是,列表被“当场”修改了。在4.6.1节“可变和不变数据类型”中,将更详细地介绍当场修改一个列表。

方法属于单个数据类型。append和insert方法是列表方法,只能在列表上调用,不能在其他值上调用,例如字符串和整型。在交互式环境中输入以下代码,注意产生的AttributeError错误信息:

>>> eggs = 'hello'>>> eggs.append('world')Traceback (most recent call last):  File "<pyshell#19>", line 1, in <module>    eggs.append('world')AttributeError: 'str' object has no attribute 'append'>>> bacon = 42>>> bacon.insert(1, 'world')Traceback (most recent call last):  File "<pyshell#22>", line 1, in <module>    bacon.insert(1, 'world')AttributeError: 'int' object has no attribute 'insert'  

4.4.3 用remove方法从列表中删除值

给 remove方法传入一个值,它将从被调用的列表中删除。在交互式环境中输入以下代码:

>>> spam = ['cat', 'bat', 'rat', 'elephant']>>> spam.remove('bat')>>> spam['cat', 'rat', 'elephant']  

试图删除列表中不存在的值,将导致ValueError错误。例如,在交互式环境中输入以下代码,注意显示的错误:

>>> spam = ['cat', 'bat', 'rat', 'elephant']>>> spam.remove('chicken')Traceback (most recent call last):  File "<pyshell#11>", line 1, in <module>    spam.remove('chicken')ValueError: list.remove(x): x not in list  

如果该值在列表中出现多次,只有第一次出现的值会被删除。在交互式环境中输入以下代码:

>>> spam = ['cat', 'bat', 'rat', 'cat', 'hat', 'cat']>>> spam.remove('cat')>>> spam['bat', 'rat', 'cat', 'hat', 'cat']  

如果知道想要删除的值在列表中的下标,del语句就很好用。如果知道想要从列表中删除的值,remove方法就很好用。

4.4.4 用sort方法将列表中的值排序

数值的列表或字符串的列表,能用sort方法排序。例如,在交互式环境中输入以下代码:

>>> spam = [2, 5, 3.14, 1, -7]>>> spam.sort>>> spam[-7, 1, 2, 3.14, 5]>>> spam = ['ants', 'cats', 'dogs', 'badgers', 'elephants']>>> spam.sort>>> spam['ants', 'badgers', 'cats', 'dogs', 'elephants']  

也可以指定reverse关键字参数为True,让sort按逆序排序。在交互式环境中输入以下代码:

>>> spam.sort(reverse=True)>>> spam['elephants', 'dogs', 'cats', 'badgers', 'ants']  

关于sort方法,你应该注意3件事。首先,sort方法当场对列表排序。不要写出spam = spam.sort这样的代码,试图记录返回值。

其次,不能对既有数字又有字符串值的列表排序,因为Python不知道如何比较它们。在交互式环境中输入以下代码,注意TypeError错误:

>>> spam = [1, 3, 2, 4, 'Alice', 'Bob']>>> spam.sortTraceback (most recent call last):  File "<pyshell#70>", line 1, in <module>    spam.sortTypeError: unorderable types: str < int  

第三,sort方法对字符串排序时,使用“ASCII字符顺序”,而不是实际的字典顺序。这意味着大写字母排在小写字母之前。因此在排序时,小写的a在大写的Z之后。例如,在交互式环境中输入以下代码:

>>> spam = ['Alice', 'ants', 'Bob', 'badgers', 'Carol', 'cats']>>> spam.sort>>> spam['Alice', 'Bob', 'Carol', 'ants', 'badgers', 'cats']  

如果需要按照普通的字典顺序来排序,就在sort方法调用时,将关键字参数key设置为str.lower。

>>> spam = ['a', 'z', 'A', 'Z']>>> spam.sort(key=str.lower)>>> spam['a', 'A', 'z', 'Z']  

这将导致sort方法将列表中所有的表项当成小写,但实际上并不会改变它们在列表中的值。

4.5 例子程序:神奇8球和列表

前一章我们写过神奇8球程序。利用列表,可以写出更优雅的版本。不是用一些几乎一样的elif语句,而是创建一个列表,针对它编码。打开一个新的文件编辑器窗口,输入以下代码,并保存为magic8Ball2.py:

import randommessages = ['It is certain',    'It is decidedly so',    'Yes definitely',    'Reply hazy try again',    'Ask again later',    'Concentrate and ask again',    'My reply is no',    'Outlook not so good',    'Very doubtful']print(messages[random.randint(0, len(messages) - 1)])  

Python中缩进规则的例外

在大多数情况下,代码行的缩进告诉Python它属于哪一个代码块。但是,这个规则有几个例外。例如在源代码文件中,列表实际上可以跨越几行。这些行的缩进并不重要。Python知道,没有看到结束方括号,列表就没有结束。例如,代码可以看起来像这样:

spam = ['apples',    'oranges',    'bananas','cats']print(spam)  

当然,从实践的角度来说,大部分人会利用Python的行为,让他们的列表看起来漂亮且可读,就像神奇8球程序中的消息列表一样。

也可以在行末使用续行字符/,将一条指令写成多行。可以把/看成是“这条指令在下一行继续”。/续行字符之后的一行中,缩进并不重要。例如,下面是有效的Python代码:

print('Four score and seven ' + /      'years ago...')  

如果希望将一长行的Python代码安排得更为可读,这些技巧是有用的。

运行这个程序,你会看到它与前面的magic8Ball.py程序效果一样。

请注意用作messages下标的表达式:random.randint(0, len(messages) - 1)。这产生了一个随机数作为下标,不论messages的大小是多少。也就是说,你会得到0与len(messages) - 1之间的一个随机数。这种方法的好处在于,很容易向列表添加或删除字符串,而不必改变其他行的代码。如果稍后更新代码,就可以少改几行代码,引入缺陷的可能性也更小。

4.6 类似列表的类型:字符串和元组

列表并不是唯一表示序列值的数据类型。例如,字符串和列表实际上很相似,只要你认为字符串是单个文本字符的列表。对列表的许多操作,也可以作用于字符串:按下标取值、切片、用于for循环、用于len,以及用于in和not in操作符。为了看到这种效果,在交互式环境中输入以下代码:

>>> name = 'Zophie'>>> name[0]'Z'>>> name[-2]'i'>>> name[0:4]'Zoph'>>> 'Zo' in nameTrue>>> 'z' in nameFalse>>> 'p' not in nameFalse>>> for i in name:print('* * * ' + i + ' * * *') * * * Z * * ** * * o * * ** * * p * * ** * * h * * ** * * i * * ** * * e * * *  

4.6.1 可变和不可变数据类型

但列表和字符串在一个重要的方面是不同的。列表是“可变的”数据类型,它的值可以添加、删除或改变。但是,字符串是“不可变的”,它不能被更改。尝试对字符串中的一个字符重新赋值,将导致TypeError错误。在交互式环境中输入以下代码,你就会看到:

>>> name = 'Zophie a cat'>>> name[7] = 'the'Traceback (most recent call last):  File "<pyshell#50>", line 1, in <module>    name[7] = 'the'TypeError: 'str' object does not support item assignment  

“改变”一个字符串的正确方式,是使用切片和连接。构造一个“新的”字符串,从老的字符串那里复制一些部分。在交互式环境中输入以下代码:

>>> name = 'Zophie a cat'>>> newName = name[0:7] + 'the' + name[8:12]>>> name'Zophie a cat'>>> newName'Zophie the cat'  

我们用[0:7]和[8:12]来指那些不想替换的字符。请注意,原来的'Zophie a cat'字符串没有被修改,因为字符串是不可变的。尽管列表值是可变的,但下面代码中的第二行并没有修改列表eggs:

>>> eggs = [1, 2, 3]>>> eggs = [4, 5, 6]>>> eggs[4, 5, 6]  

这里eggs中的列表值并没有改变,而是整个新的不同的列表值([4, 5, 6]),覆写了老的列表值。如图4-2所示。

图4-2 当eggs = [4, 5, 6]被执行时,eggs的内容被新的列表值取代

如果你确实希望修改eggs中原来的列表,让它包含[4, 5, 6],就要这样做:

>>> eggs = [1, 2, 3]>>> del eggs[2]>>> del eggs[1]>>> del eggs[0]>>> eggs.append(4)>>> eggs.append(5)>>> eggs.append(6)>>> eggs[4, 5, 6]  

在第一个例子中,eggs最后的列表值与它开始的列表值是一样的。只是这个列表被改变了,而不是被覆写。图4-3展示了前面交互式脚本的例子中,前7行代码所做的7次改动。

图4-3 del语句和append方法当场修改了同一个列表值

改变一个可变数据类型的值(就像前面例子中del语句和append方法所做的事),当场改变了该值,因为该变量的值没有被一个新的列表值取代。

区分可变与不可变类型,似乎没有什么意义,但4.7.1节“传递引用”将解释,使用可变参数和不可变参数调用函数时产生的不同行为。首先,让我们来看看元组数据类型,它是列表数据类型的不可变形式。

4.6.2 元组数据类型

除了两个方面,“元组”数据类型几乎与列表数据类型一样。首先,元组输入时用圆括号,而不是用方括号。例如,在交互式环境中输入以下代码:

>>> eggs = ('hello', 42, 0.5)>>> eggs[0]'hello'>>> eggs[1:3](42, 0.5)>>> len(eggs)3  

但元组与列表的主要区别还在于,元组像字符串一样,是不可变的。元组不能让它们的值被修改、添加或删除。在交互式环境中输入以下代码,注意TypeError出错信息:

>>> eggs = ('hello', 42, 0.5)>>> eggs[1] = 99Traceback (most recent call last):  File "<pyshell#5>", line 1, in <module>    eggs[1] = 99TypeError: 'tuple' object does not support item assignment  

如果元组中只有一个值,你可以在括号内该值的后面跟上一个逗号,表明这种情况。否则,Python将认为,你只是在一个普通括号内输入了一个值。逗号告诉Python,这是一个元组(不像其他编程语言,Python接受列表或元组中最后表项后面跟的逗号)。在交互式环境中,输入以下的type函数调用,看看它们的区别:

>>> type(('hello',))<class 'tuple'>>>> type(('hello'))<class 'str'>  

你可以用元组告诉所有读代码的人,你不打算改变这个序列的值。如果需要一个永远不会改变的值的序列,就使用元组。使用元组而不是列表的第二个好处在于,因为它们是不可变的,它们的内容不会变化,Python可以实现一些优化,让使用元组的代码比使用列表的代码更快。

4.6.3 用list和tuple函数来转换类型

正如str(42)将返回'42',即整数42的字符串表示形式,函数list和tuple将返回传递给它们的值的列表和元组版本。在交互式环境中输入以下代码,注意返回值与传入值是不同的数据类型:

>>> tuple(['cat', 'dog', 5])('cat', 'dog', 5)>>> list(('cat', 'dog', 5))['cat', 'dog', 5]>>> list('hello')['h', 'e', 'l', 'l', 'o']  

如果需要元组值的一个可变版本,将元组转换成列表就很方便。

4.7 引用

正如你看到的,变量保存字符串和整数值。在交互式环境中输入以下代码:

>>> spam = 42>>> cheese = spam>>> spam = 100>>> spam100>>> cheese42  

你将42赋给spam变量,然后拷贝spam中的值,将它赋给变量cheese。当稍后将spam中的值改变为100时,这不会影响cheese中的值。这是因为spam和cheese是不同的变量,保存了不同的值。

但列表不是这样的。当你将列表赋给一个变量时,实际上是将列表的“引用”赋给了该变量。引用是一个值,指向某些数据。列表引用是指向一个列表的值。这里有一些代码,让这个概念更容易理解。在交互式环境中输入以下代码:

❶ >>> spam = [0, 1, 2, 3, 4, 5]❷ >>> cheese = spam❸ >>> cheese[1] = 'Hello!' >>> spam [0, 'Hello!', 2, 3, 4, 5] >>> cheese [0, 'Hello!', 2, 3, 4, 5]  

这可能让你感到奇怪。代码只改变了cheese列表,但似乎cheese和spam列表同时发生了改变。

当创建列表时❶,你将对它的引用赋给了变量。但下一行❷只是将spam中的列表引用拷贝到cheese,而不是列表值本身。这意味着存储在spam和cheese中的值,现在指向了同一个列表。底下只有一个列表,因为列表本身实际从未复制。所以当你修改cheese变量的第一个元素时❸,也修改了spam指向的同一个列表。

记住,变量就像包含着值的盒子。本章前面的图显示列表在盒子中,这并不准确,因为列表变量实际上没有包含列表,而是包含了对列表的“引用”(这些引用包含一些ID数字,Python在内部使用这些ID,但是你可以忽略)。利用盒子作为变量的隐喻,图4-4展示了列表被赋给spam变量时发生的情形。

图4-4 spam = [0, 1, 2, 3, 4, 5]保存了对列表的引用,而非实际列表

然后,在图4-5中,spam中的引用被复制给cheese。只有新的引用被创建并保存在cheese中,而非新的列表。请注意,两个引用都指向同一个列表。

图4-5 spam = cheese复制了引用,而非列表

当你改变cheese指向的列表时,spam指向的列表也发生了改变,因为cheese和spam都指向同一个列表,如图4-6所示。

图4-6 cheese[1] = 'Hello!'修改了两个变量指向的列表

变量包含对列表值的引用,而不是列表值本身。但对于字符串和整数值,变量就包含了字符串或整数值。在变量必须保存可变数据类型的值时,例如列表或字典,Python就使用引用。对于不可变的数据类型的值,例如字符串、整型或元组,Python变量就保存值本身。

虽然Python变量在技术上包含了对列表或字典值的引用,但人们通常随意地说,该变量包含了列表或字典。

4.7.1 传递引用

要理解参数如何传递给函数,引用就特别重要。当函数被调用时,参数的值被复制给变元。对于列表(以及字典,我将在下一章中讨论),这意味着变元得到的是引用的拷贝。要看看这导致的后果,请打开一个新的文件编辑器窗口,输入以下代码,并保存为passingReference.py:

def eggs(someParameter):    someParameter.append('Hello')spam = [1, 2, 3]eggs(spam)print(spam)  

请注意,当eggs被调用时,没有使用返回值来为spam赋新值。相反,它直接当场修改了该列表。在运行时,该程序产生输出如下:

[1, 2, 3, 'Hello']  

尽管spam和someParameter包含了不同的引用,但它们都指向相同的列表。这就是为什么函数内的append('Hello')方法调用在函数调用返回后,仍然会对该列表产生影响。

请记住这种行为:如果忘了Python处理列表和字典变量时采用这种方式,可能会导致令人困惑的缺陷。

4.7.2 copy模块的copy和deepcopy函数

在处理列表和字典时,尽管传递引用常常是最方便的方法,但如果函数修改了传入的列表或字典,你可能不希望这些变动影响原来的列表或字典。要做到这一点,Python提供了名为copy的模块,其中包含copy和deepcopy函数。第一个函数copy.copy,可以用来复制列表或字典这样的可变值,而不只是复制引用。在交互式环境中输入以下代码:

>>> import copy>>> spam = ['A', 'B', 'C', 'D']>>> cheese = copy.copy(spam)>>> cheese[1] = 42>>> spam['A', 'B', 'C', 'D']>>> cheese['A', 42, 'C', 'D']  

现在spam和cheese变量指向独立的列表,这就是为什么当你将42赋给下标7时,只有cheese中的列表被改变。在图4-7中可以看到,两个变量的引用ID数字不再一样,因为它们指向了独立的列表。

图4-7 cheese = copy.copy(spam)创建了第二个列表,能独立于第一个列表修改

如果要复制的列表中包含了列表,那就使用copy.deepcopy函数来代替。deepcopy函数将同时复制它们内部的列表。

4.8 小结

列表是有用的数据类型,因为它们让你写代码处理一组可以修改的值,同时仅用一个变量。在本书后面的章节中,你会看到一些程序利用列表来完成工作。没有列表,这些工作很困难,甚至不可能完成。

列表是可变的,这意味着它们的内容可以改变。元组和字符串虽然在某些方面像列表,却是不可变的,不能被修改。包含一个元组或字符串的变量,可以被一个新的元组或字符串覆写,但这和现场修改原来的值不是一回事,不像append和remove方法在列表上的效果。

变量不直接保存列表值,它们保存对列表的“引用”。在复制变量或将列表作为函数调用的参数时,这一点很重要。因为被复制的只是列表引用,所以要注意,对该列表的所有改动都可能影响到程序中的其他变量。如果需要对一个变量中的列表修改,同时不修改原来的列表,就可以用copy或deepcopy。

4.9 习题

1.什么是?

2.如何将'hello'赋给列表的第三个值,而列表保存在名为spam的变量中?(假定变量包含[2, 4, 6, 8, 10])。

对接下来的3个问题,假定spam包含列表['a', 'b', 'c', 'd']。

3.spam[int('3' * 2) / 11]求值为多少?

4.spam[-1]求值为多少?

5.spam[:2]求值为多少?

对接下来的3个问题。假定bacon包含列表[3.14, 'cat', 11, 'cat', True]。

6.bacon.index('cat')求值为多少?

7.bacon.append(99)让bacon中的列表值变成什么样?

8.bacon.remove('cat')让bacon中的列表时变成什么样?

9.列表连接和复制的操作符是什么?

10.append和insert列表方法之间的区别是什么?

11.从列表中删除值有哪两种方法?

12.请说出列表值和字符串的几点相似之处。

13.列表和元组之间的区别是什么?

14.如果元组中只有一个整数值42,如何输入该元组?

15.如何从列表值得到元组形式?如何从元组值得到列表形式?

16.“包含”列表的变量,实际上并未真地直接包含列表。它们包含的是什么?

17.copy.copy和copy.deepcopy之间的区别是什么?

4.10 实践项目

作为实践,编程完成下列任务。

4.10.1 逗号代码

假定有下面这样的列表:

spam = ['apples', 'bananas', 'tofu', 'cats']  

编写一个函数,它以一个列表值作为参数,返回一个字符串。该字符串包含所有表项,表项之间以逗号和空格分隔,并在最后一个表项之前插入and。例如,将前面的spam列表传递给函数,将返回'apples, bananas, tofu, and cats'。但你的函数应该能够处理传递给它的任何列表。

4.10.2 字符图网格

假定有一个列表的列表,内层列表的每个值都是包含一个字符的字符串,像这样:

grid = [['.', '.', '.', '.', '.', '.'],['.', 'O', 'O', '.', '.', '.'],['O', 'O', 'O', 'O', '.', '.'],['O', 'O', 'O', 'O', 'O', '.'],['.', 'O', 'O', 'O', 'O', 'O'],['O', 'O', 'O', 'O', 'O', '.'],['O', 'O', 'O', 'O', '.', '.'],['.', 'O', 'O', '.', '.', '.'],['.', '.', '.', '.', '.', '.']]  

你可以认为grid[x][y]是一幅“图”在x、y坐标处的字符,该图由文本字符组成。原点(0, 0)在左上角,向右x坐标增加,向下y坐标增加。

复制前面的网格值,编写代码用它打印出图像。

..OO.OO...OOOOOOO..OOOOOOO...OOOOO.....OOO.......O....  

提示

你需要使用循环嵌套循环,打印出grid[0][0],然后grid[1][0],然后grid[2][0],以此类推,直到grid[8][0]。这就完成第一行,所以接下来打印换行。然后程序将打印出grid[0][1],然后grid[1][1],然后grid[2][1],以此类推。程序最后将打印出grid[8][5]。

而且,如果你不希望在每次print调用后都自动打印换行,记得向print传递end关键字参数。