基本概念
5.1 元组
元组:相对简单,是str的扩展,与字符串一样,是一些元素的不可变有序序列。与字符串的区别是,元组(tuple)中的元素不一定是字符,其中的单个元素可以是任意类型,且它们彼此之间的类型也可以不同。
-
# tuple类型的字面量形式是位于小括号之中的由逗号隔开的一组元素 -
t1=() -
t2 = (1,) #注意当元组中只有一个元素时,加逗号 -
t3 = (1, 'two', 3) -
print(t1,t2,t3)
-
() (1,) (1, 'two', 3)
-
#可以在元组上使用重复操作。 -
print(3*('a', 2)) -
#与字符串一样,元组可以进行连接、索引和切片等操作。 -
t1 = (1, 'two', 3) -
t2 = (t1, 3.25)#,这个元组中有一个绑定了名称t1的元组和一个浮点数3.25。这是可行的,因为元组也是一个对象 -
print(t2) -
print((t1 + t2)) -
print((t1 + t2)[3]) -
print((t1 + t2)[2:5])
-
('a',2,'a',2,'a',2) -
((1,'two',3),3.25) -
(1,'two',3,(1,'two',3),3.25) -
(1,'two',3) -
(3,(1,'two',3),3.25)
5.1.1 序列与多重赋值
如果你知道一个序列(元组字符串)的长度,那么可以使用Python中的多重赋值语句方便地提取单个元素。
-
x, y = (3, 4)#x会被绑定到3,y会被绑定到4。 -
a, b, c = 'xyz'#会将a绑定到x、b绑定到y、c绑定到z。
5.2 范围
元组和字符串一样,范围也是不可变的。
range函数会返回一个range类型的对象,最常用在for循环中。range函数接受3个整数参数:start、stop和step。
- 如果step是个正数,那么最后一个元素就是小于stop的最大整数start + i * step。
- 如果step是个负数,那么最后一个元素就是大于stop的最小整数start +i * step。
- 如果只有2个实参,那么步长就为1。
- 如果只有1个实参,那么这个参数就是结束值,起始值默认为0,步长默认为1。
-
#除了连接操作和重复操作,其他所有能够在元组上进行的操作同样适用于范围。 -
range(10)[2:6][2]
-
4
-
range(0,7,2)==range(0,8,2)#值就是True -
range(0,7,2)==range(6,-1,-2)#注意:值是False。因为尽管这两个范围包含同样的
5.3 列表与可变性
列表:与元组类似,也是值的有序序列,每个值都可以由索引进行标识。
-
t1=[]#空list -
t2=[1]#单元素list -
L = ['I did it all', 4, 'love'] -
for i in range(len(L)): -
print(L[i])
-
I did it all -
4 -
love
-
#list切片操作 -
[1, 2, 3, 4][1:3][1]
-
3
列表与元组相比有一个特别重要的区别:列表是可变的,而元组和字符串是不可变的。
- 很多操作符可以创建可变类型的对象,也可以将变量绑定到这种类型的对象上。
- 但不可变类型的对象是不能被修改的,相比之下,list类型的对象在创建完成后可以被修改。
-
#解释器会创建两个新列表,然后为其绑定合适的变量 -
Techs = ['MIT', 'Caltech'] -
Ivys = ['Harvard', 'Yale', 'Brown'] -
#也会创建新的列表并为其绑定变量。这些列表中的元素也是列表。 -
Univs = [Techs, Ivys] -
Univs1 = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']] -
print('Univs =', Univs) -
print('Univs1 =', Univs1) -
print(Univs == Univs1)
-
Univs = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']] -
Univs1 = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']] -
True
看上去好像Univs和Univs1被绑定到同一个值,但这个表象具有欺骗性。Univs和Univs1被绑定到不同的对象,可以使用Python内置函数id验证这一点,id会返回一 个对象的唯一整数标识符。可以用这个函数检测对象是否相等。
-
print(Univs == Univs1) #测试值是否相等 -
print(id(Univs) == id(Univs1)) #测试对象是否相等 -
print('Id of Univs =', id(Univs)) -
print('Id of Univs1 =', id(Univs1))
-
True -
False -
Id of Univs = 81469128 -
Id of Univs1 = 80584584
Univs中的元素不是Techs和Ivys绑定的列表的复制,而是这些列表本身。 Univs1中的元素也是列表,与Univs中的列表包含同样元素,但不同于Univs中的那些列表。
-
print('Ids of Univs[0] and Univs[1]', id(Univs[0]), id(Univs[1])) -
print('Ids of Univs1[0] and Univs1[1]', id(Univs1[0]), id(Univs1[1]))
-
Ids of Univs[0] and Univs[1] 81469320 80977096 -
Ids of Univs1[0] and Univs1[1] 81465928 81555848
为什么这一点很重要呢?因为列表是可变的。append方法具有副作用。它不创建一个新列表,而是通过向列表Techs的末尾添加一个新元素——字符串'RPI'
-
#注意Techs这个对象改变了,同时改变的只有Univs,因为Univs = [Techs, Ivys] -
Techs.append('RPI') -
print('Univs =', Univs) -
print('Univs1 =', Univs1)
-
Univs = [['MIT', 'Caltech', 'RPI', 'RPI'], ['Harvard', 'Yale', 'Brown']] -
Univs1 = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']]
这种情况称为对象的别名,即两种不同的方式可以引用同一个列表对象。一种方式是通过变量Techs,另一种方式是通过Univs绑定的list对象中的第一个元素。我们可以通过任意一种方式改变这个对象,而且改变的结果对两种方式都是可见的。这非常方便,但也留下了隐患。无意形成的别名会导致程序错误,而且这种错误非常难以捕获。
-
#和元组一样,可以使用for语句遍历列表中的元素。 -
for e in Univs: -
print('Univs contains', e) -
print(' which contains') -
for u in e: -
print(' ', u)
-
Univs contains ['MIT', 'Caltech', 'RPI', 'RPI'] -
which contains -
MIT -
Caltech -
RPI -
RPI -
Univs contains ['Harvard', 'Yale', 'Brown'] -
which contains -
Harvard -
Yale -
Brown
我们将一个列表追加到另一个列表中时,如Techs.append(Ivys),会保持原来的结构。也就是说,结果是一个包含列表的列表。如果我们不想保持原来的结构,而想将一个列表中的元素添加到另一个列表,那么可以使用列表连接操作或extend方法。如下所示:
-
#操作符+确实没有副作用,它会创建并返回一个新的列表。相反,extend和append都会改变L1。 -
L1 = [1,2,3] -
L2 = [4,5,6] -
L3 = L1 + L2 -
print('L3 =', L3) -
L1.extend(L2) -
print('L1 =', L1) -
L1.append(L2) -
print('L1 =', L1)
-
L3 = [1, 2, 3, 4, 5, 6] -
L1 = [1, 2, 3, 4, 5, 6] -
L1 = [1, 2, 3, 4, 5, 6, [4, 5, 6]]
下面给出了一些列表操作。请注意,除了count和index外,这些方法都会改变列表。
- L.append(e):将对象e追加到L的末尾。
- L.count(e):返回e在L中出现的次数。
- L.insert(i, e):将对象e插入L中索引值为i的位置。
- L.extend(L1):将L1中的项目追加到L末尾。
- L.remove(e):从L中删除第一个出现的e。
- L.index(e):返回e第一次出现在L中时的索引值。如果e不在L中,则抛出一个异常(参见第7章)。
- L.pop(i):删除并返回L中索引值为i的项目。如果L为空,则抛出一个异常。如果i被省略,则i的默认值为-1,删除并返回L中的最后一个元素。
- L.sort():升序排列L中的元素。
- L.reverse():翻转L中的元素顺序。
5.3.1 克隆
克隆:使用切片操作复制某个列表。
我们通常应该尽量避免修改一个正在进行遍历的列表。例如,考虑以下代码:
-
def removeDups(L1, L2): -
"""假设L1和L2是列表, -
删除L2中出现的L1中的元素""" -
for e1 in L1: -
if e1 in L2: -
L1.remove(e1) -
L1 = [1,2,3,4] -
L2 = [1,2,5,6] -
removeDups(L1, L2) -
print('L1 =', L1)
-
L1 = [2, 3, 4]
在for循环中,Python使用一个内置计数器跟踪程序在列表中的位置,内部计数器在每次迭代结束时都会增加1。当计数器的值等于列表的当前长度时,循环终止。如果循环过程中列表没有发生改变,那么这种机制是有效的,但如果列表发生改变,就会产生出乎意料的结果。本例中,内置计数器从0开始计数,程序发现了L1[0]在L2中,于是删除了它——将L1的长度减少到3。然后计数器增加1,代码继续检查L1[1]的值是否在L2中。请注意,这时已经不是初始的L1[1]的值(2)了,而是当前的L1[1]的值(3)。
-
#解决方案:避免这种问题的方法是使用切片操作克隆(即复制)这个列表,并使用for e1 in L1[:]这种写法。 -
def removeDups(L1, L2): -
"""假设L1和L2是列表, -
删除L2中出现的L1中的元素""" -
for e1 in L1[:]: -
if e1 in L2: -
L1.remove(e1) -
L1 = [1,2,3,4] -
L2 = [1,2,5,6] -
removeDups(L1, L2) -
print('L1 =', L1)
-
L1 = [3, 4]
在Python中,切片不是克隆列表的唯一方法。
- 表达式list(L)会返回列表L的一份副本。
- 如果待复制的列表包含可变对象,而且你也想复制这些可变对象,那么可以导入标准库模块copy,然后使用函数copy.deepcopy。似乎 deepcopy 更加符合我们对「复制」的直觉定义: 一旦复制出来了,就应该是独立的了。
5.3.2 列表推导
列表推导式提供了一种简洁的方式,将某种操作应用到序列中的一个值上。它会创建一个新 列表,其中的每个元素都是一个序列中的值(如另一个列表中的元素)应用给定操作后的结果
-
mixed = [1, 2, 'a', 3, 4.0] -
print([x**2 for x in mixed if type(x) == int])
-
[1, 4, 9]
5.4 函数对象
在Python中,函数是一等对象。这意味着我们可以像对待其他类型的对象(如int或list)一样对待函数。
- 函数可以具有类型,例如,表达式type(abs)的值是;
- 函数可以出现在表达式中,如作为赋值语句的右侧项或作为函数的实参;函数可以是列表中的元素;等等。
-
# 使用函数作为实参可以实现一种名为高阶编程的编码方式,这种方式与列表结合使用非常方便 -
#将函数应用到列表中的元素 -
def applyToEach(L, f): -
"""假设L是列表,f是函数 -
将f(e)应用到L的每个元素,并用返回值替换原来的元素""" -
for i in range(len(L)): -
L[i] = f(L[i]) -
L = [1, -2, 3.33] -
print('L =', L) -
print('Apply abs to each element of L.') -
applyToEach(L, abs) -
print('L =', L) -
print('Apply int to each element of', L) -
applyToEach(L, int) -
print('L =', L)
-
L = [1, -2, 3.33] -
Apply abs to each element of L. -
L = [1, 2, 3.33] -
Apply int to each element of [1, 2, 3.33] -
L = [1, 2, 3]
Python中有一个内置的高阶函数map,它的功能与applyToEach函数相似,但适用范围更广。 1.map函数被设计为与for循环结合使用。在map函数的最简形式中,第一个参数是个一元函数(即只有一个参数的函数),第二个参数是有序的值集合,集合中的值可以一元函数的参数。 2.在for循环中使用map函数时,它的作用类似于range函数,为循环的每次迭代返回一个值。这些值是对第二个参数中的每个元素应用一元函数生成的。例如,下面的代码:
-
L1 = [1, 28, 36] -
L2 = [2, 57, 9] -
for i in map(min, L1, L2): -
print(i)
-
1 -
28
Python还支持创建匿名函数(即没有绑定名称的函数),这时要使用保留字lambda。Lambda表达式的例子:
-
L = [] -
for i in map(lambda x, y: x**y, [1 ,2 ,3, 4], [3, 2, 1, 0]): -
L.append(i) -
print(L)
-
[1, 4, 3, 1]
5.5 字符串、元组、范围与列表
str、tuple、range和list。它们的共同之处在于,都可以使用下面描述的操作:
- seq[i]:返回序列中的第i个元素。
- len(sep):返回序列长度。
- seq1 + seq2:返回两个序列的连接(不适用于range)。
- n*seq:返回一个重复了n次seq的序列。
- seq[start:end]:返回序列的一个切片。
- e in seq:如果序列包含e,则返回True,否则返回False。
- e not in seq:如果序列不包含e,则返回True,否则返回False。
- for e in seq:遍历序列中的元素。
因为字符串只能包含字符,所以应用范围远远小于元组和列表。但另一方面,处理字符串时有大量内置方法可以使用,这使得完成任务非常轻松。请记住,字符串是不可变的,所以这些方法都返回一个值,而不会对原字符串产生副作用。
- s.count(s1):计算字符串s1在s中出现的次数。
- s.find(s1):返回子字符串s1在s中第一次出现时的索引值,如果s1不在s中,则返回-1。
- s.rfind(s1):功能与find相同,只是从s的末尾开始反向搜索(rfind中的r表示反向)。
- s.index(s1):功能与find相同,只是如果s1不在s中,则抛出一个异常。
- s.index(s1):功能与index相同,只是从s的末尾开始。
- s.lower():将s中的所有大写字母转换为小写。
- s.replace(old, new):将s中出现过的所有字符串old替换为字符串new。
- s.rstrip():去掉s末尾的空白字符。
- s.split(d):使用d作为分隔符拆分字符串s,返回s的一个子字符串列表。
5.6 字典
字典:(dict,dictionary的缩写)字典类型的对象与列表很相似,区别在于字典使用键对其中的值进行引用,可以将字典看作一个键/值对的集合。字典类型的字面量用大括号表示,其中的元素写法是键加冒号再加上值。
-
monthNumbers = {'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5,1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May'} -
print('The third month is ' + monthNumbers[3]) -
dist = monthNumbers['Apr'] - monthNumbers['Jan'] -
print('Apr and Jan are', dist, 'months apart')
-
The third month is Mar -
Apr and Jan are 3 months apart
dict中的项目是无序的,不能通过索引引用。这就是为什么monthNumbers[1]确定无疑地指向键为1的项目,而不是第二个项目。
-
#字典中增加或改变项目 -
monthNumbers['June'] = 6 -
monthNumbers['May'] = 'V'
-
#不同语言互译 -
EtoF = {'bread':'pain', 'wine':'vin', 'with':'avec', 'I':'Je','eat':'mange', 'drink':'bois', 'John':'Jean','friends':'amis', 'and': 'et', 'of':'du','red':'rouge'} -
FtoE = {'pain':'bread', 'vin':'wine', 'avec':'with', 'Je':'I','mange':'eat', 'bois':'drink', 'Jean':'John','amis':'friends', 'et':'and', 'du':'of', 'rouge':'red'} -
dicts = {'English to French':EtoF, 'French to English':FtoE} #俩字典合二为一 -
def translateWord(word, dictionary): -
if word in dictionary.keys(): -
return dictionary[word] -
elif word != '': -
return '"' + word + '"' -
return word -
def translate(phrase, dicts, direction): -
UCLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' -
LCLetters = 'abcdefghijklmnopqrstuvwxyz' -
letters = UCLetters + LCLetters -
dictionary = dicts[direction] -
translation = '' -
word = '' -
for c in phrase: -
if c in letters: -
word = word + c -
else: -
translation = translation+ translateWord(word, dictionary) + c -
word = '' -
return translation + ' ' + translateWord(word, dictionary) -
print (translate('I drink good red wine, and eat bread.',dicts,'English to French')) -
print (translate('Je bois du vin rouge.',dicts, 'French to English'))
-
Je bois "good" rouge vin, et mange pain. -
I drink of wine red.
多数编程语言都不包含这种提供从键到值的映射关系的内置类型。然而,程序员可以使用其他类型实现同样的功能。例如,使用其中元素为键/值对的列表就可以轻松实现字典,然后可以编写一个简单的函数进行关联搜索,如下所示:
-
#这种实现的问题在于计算效率太低。最坏情况下,程序执行一次搜索可能需要检查列表中的每一个元素 -
def keySearch(L, k): -
for elem in L: -
if elem[0] == k: -
return elem[1] -
return None
可以使用for语句遍历字典中的项目。但分配给迭代变量的值是字典键,不是键/值对。迭代过程中没有定义键的顺序。
-
monthNumbers = {'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5, -
1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May'} -
keys = [] -
for e in monthNumbers: -
keys.append(str(e)) -
print(keys) -
keys.sort() -
print(keys)
-
['3', 'May', '2', 'Feb', '1', '5', 'Jan', 'Mar', '4', 'Apr'] -
['1', '2', '3', '4', '5', 'Apr', 'Feb', 'Jan', 'Mar', 'May']
keys方法返回一个dict_keys类型的对象。①这是view对象的一个例子。视图中没有定义视图的顺序。视图对象是动态的,因为如果与其相关的对象发生变化,我们就可以通过视图对象察觉到这种变化。例如:
-
birthStones = {'Jan':'Garnet', 'Feb':'Amethyst', 'Mar':'Acquamarine','Apr':'Diamond', 'May':'Emerald'} -
months = birthStones.keys() -
print(months) -
birthStones['June'] = 'Pearl' -
print(months)
-
dict_keys(['May', 'Feb', 'Apr', 'Jan', 'Mar']) -
dict_keys(['May', 'Feb', 'Jan', 'Mar', 'Apr', 'June'])
可以使用for语句遍历dicttype类型的对象,也可以使用in检测其中的成员。dicttype类型的对象可以很容易地转换为列表,如list(months)。
- 并非所有对象都可以用作字典键:键必须是一个可散列类型的对象。所有Python内置的不可变类型都是可散列的,而且所有Python内置的可变类型都是不可散列的。
- 如果一个类型具有以下两条性质,就可以说它是“可散列的”: (1)具有hash方法,可以将一个这种类型的对象映射为一个int值,而且对于每一个对象,由hash返回的值在这个对象的生命周期中是不变的; (2)具有eq方法,可以比较两个对象是否相等。
字典方法:
- len(d):返回d中项目的数量。
- d.keys():返回d中所有键的视图。
- d.values():返回d中所有值的视图。
- k in d:如果k在d中,则返回True。
- d[k]:返回d中键为k的项目。
- d.get(k, v):如果k在d中,则返回d[k],否则返回v。
- d[k] = v:在d中将值v与键k关联。如果已经有一个与k关联的值,则替换。
- del d[k]:从d中删除键k。
- for k in d:遍历d中的键。
