0
点赞
收藏
分享

微信扫一扫

【Python编程导论】第五章- 结构化类型、可变性与高阶函数

基本概念

5.1 元组

元组​:相对简单,是str的扩展,与字符串一样,是一些元素的不可变有序序列。与字符串的区别是,元组(tuple)中的元素不一定是字符,其中的单个元素可以是任意类型,且它们彼此之间的类型也可以不同。

  1. ​# tuple类型的字面量形式是位于小括号之中的由逗号隔开的一组元素​
  2. ​t1=()​
  3. ​t2 = (1,)  #注意当元组中只有一个元素时,加逗号​
  4. ​t3 = (1, 'two', 3)​
  5. ​print(t1,t2,t3)​


  1. ​() (1,) (1, 'two', 3)​


  1. ​#可以在元组上使用重复操作。​
  2. ​print(3*('a', 2))​
  3. ​#与字符串一样,元组可以进行连接、索引和切片等操作。​
  4. ​t1 = (1, 'two', 3)​
  5. ​t2 = (t1, 3.25)#,这个元组中有一个绑定了名称t1的元组和一个浮点数3.25。这是可行的,因为元组也是一个对象​
  6. ​print(t2)​
  7. ​print((t1 + t2))​
  8. ​print((t1 + t2)[3])​
  9. ​print((t1 + t2)[2:5])​


  1. ​('a',2,'a',2,'a',2)​
  2. ​((1,'two',3),3.25)​
  3. ​(1,'two',3,(1,'two',3),3.25)​
  4. ​(1,'two',3)​
  5. ​(3,(1,'two',3),3.25)​

5.1.1 序列与多重赋值

如果你知道一个序列(元组字符串)的长度,那么可以使用Python中的多重赋值语句方便地提取单个元素。

  1. ​x, y = (3, 4)#x会被绑定到3,y会被绑定到4。​
  2. ​a, b, c = 'xyz'#会将a绑定到x、b绑定到y、c绑定到z。​

5.2 范围

元组和字符串一样,范围也是不可变的。

range函数会返回一个range类型的对象,最常用在for循环中。range函数接受3个整数参数:start、stop和step。

  1. 如果step是个正数,那么最后一个元素就是小于stop的最大整数start + i * step。
  2. 如果step是个负数,那么最后一个元素就是大于stop的最小整数start +i * step。
  3. 如果只有2个实参,那么步长就为1。
  4. 如果只有1个实参,那么这个参数就是结束值,起始值默认为0,步长默认为1。
  5. ​#除了连接操作和重复操作,其他所有能够在元组上进行的操作同样适用于范围。​
  6. ​range(10)[2:6][2]​


  1. ​4​


  1. ​range(0,7,2)==range(0,8,2)#值就是True​
  2. ​range(0,7,2)==range(6,-1,-2)#注意:值是False。因为尽管这两个范围包含同样的​

5.3 列表与可变性

列表​:与元组类似,也是值的有序序列,每个值都可以由索引进行标识。

  1. ​t1=[]#空list​
  2. ​t2=[1]#单元素list​
  3. ​L = ['I did it all', 4, 'love']​
  4. ​for i in range(len(L)):​
  5. ​    print(L[i])​


  1. ​I did it all​
  2. ​4​
  3. ​love​


  1. ​#list切片操作​
  2. ​[1, 2, 3, 4][1:3][1]​


  1. ​3​

列表与元组相比有一个特别重要的区别:列表是可变的,而元组和字符串是不可变的。

  1. 很多操作符可以创建可变类型的对象,也可以将变量绑定到这种类型的对象上。
  2. 但不可变类型的对象是不能被修改的,相比之下,list类型的对象在创建完成后可以被修改。
  3. ​#解释器会创建两个新列表,然后为其绑定合适的变量​
  4. ​Techs = ['MIT', 'Caltech']​
  5. ​Ivys = ['Harvard', 'Yale', 'Brown']​
  6. ​#也会创建新的列表并为其绑定变量。这些列表中的元素也是列表。​
  7. ​Univs = [Techs, Ivys]​
  8. ​Univs1 = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']]​
  9. ​print('Univs =', Univs)​
  10. ​print('Univs1 =', Univs1)​
  11. ​print(Univs == Univs1)​


  1. ​Univs = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']]​
  2. ​Univs1 = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']]​
  3. ​True​

看上去好像Univs和Univs1被绑定到同一个值,但这个表象具有欺骗性。Univs和Univs1被绑定到不同的对象,可以使用Python内置函数id验证这一点,id会返回一 个对象的唯一整数标识符。可以用这个函数检测对象是否相等。

  1. ​print(Univs == Univs1) #测试值是否相等​
  2. ​print(id(Univs) == id(Univs1)) #测试对象是否相等​
  3. ​print('Id of Univs =', id(Univs))​
  4. ​print('Id of Univs1 =', id(Univs1))​



  1. ​True​
  2. ​False​
  3. ​Id of Univs = 81469128​
  4. ​Id of Univs1 = 80584584​

Univs中的元素不是Techs和Ivys绑定的列表的复制,而是这些列表本身。 Univs1中的元素也是列表,与Univs中的列表包含同样元素,但不同于Univs中的那些列表。

  1. ​print('Ids of Univs[0] and Univs[1]', id(Univs[0]), id(Univs[1]))​
  2. ​print('Ids of Univs1[0] and Univs1[1]', id(Univs1[0]), id(Univs1[1]))​


  1. ​Ids of Univs[0] and Univs[1] 81469320 80977096​
  2. ​Ids of Univs1[0] and Univs1[1] 81465928 81555848​

为什么这一点很重要呢?因为列表是可变的。append方法具有副作用。它不创建一个新列表,而是通过向列表Techs的末尾添加一个新元素——字符串'RPI'

  1. ​#注意Techs这个对象改变了,同时改变的只有Univs,因为Univs = [Techs, Ivys]​
  2. ​Techs.append('RPI')​
  3. ​print('Univs =', Univs)​
  4. ​print('Univs1 =', Univs1)​



  1. ​Univs = [['MIT', 'Caltech', 'RPI', 'RPI'], ['Harvard', 'Yale', 'Brown']]​
  2. ​Univs1 = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']]​

这种情况称为对象的别名,即两种不同的方式可以引用同一个列表对象。一种方式是通过变量Techs,另一种方式是通过Univs绑定的list对象中的第一个元素。我们可以通过任意一种方式改变这个对象,而且改变的结果对两种方式都是可见的。这非常方便,但也留下了隐患。无意形成的别名会导致程序错误,而且这种错误非常难以捕获。

  1. ​#和元组一样,可以使用for语句遍历列表中的元素。​
  2. ​for e in Univs:​
  3. ​    print('Univs contains', e)​
  4. ​    print(' which contains')​
  5. ​    for u in e:​
  6. ​        print(' ', u)​


  1. ​Univs contains ['MIT', 'Caltech', 'RPI', 'RPI']​
  2. ​ which contains​
  3. ​  MIT​
  4. ​  Caltech​
  5. ​  RPI​
  6. ​  RPI​
  7. ​Univs contains ['Harvard', 'Yale', 'Brown']​
  8. ​ which contains​
  9. ​  Harvard​
  10. ​  Yale​
  11. ​  Brown​

我们将一个列表追加到另一个列表中时,如Techs.append(Ivys),会保持原来的结构。也就是说,结果是一个包含列表的列表。如果我们不想保持原来的结构,而想将一个列表中的元素添加到另一个列表,那么可以使用列表连接操作或extend方法。如下所示:

  1. ​#操作符+确实没有副作用,它会创建并返回一个新的列表。相反,extend和append都会改变L1。​
  2. ​L1 = [1,2,3]​
  3. ​L2 = [4,5,6]​
  4. ​L3 = L1 + L2​
  5. ​print('L3 =', L3)​
  6. ​L1.extend(L2)​
  7. ​print('L1 =', L1)​
  8. ​L1.append(L2)​
  9. ​print('L1 =', L1)​


  1. ​L3 = [1, 2, 3, 4, 5, 6]​
  2. ​L1 = [1, 2, 3, 4, 5, 6]​
  3. ​L1 = [1, 2, 3, 4, 5, 6, [4, 5, 6]]​

下面给出了一些列表操作。请注意,除了count和index外,这些方法都会改变列表。

  1. L.append(e):将对象e追加到L的末尾。
  2. L.count(e):返回e在L中出现的次数。
  3. L.insert(i, e):将对象e插入L中索引值为i的位置。
  4. L.extend(L1):将L1中的项目追加到L末尾。
  5. L.remove(e):从L中删除第一个出现的e。
  6. L.index(e):返回e第一次出现在L中时的索引值。如果e不在L中,则抛出一个异常(参见第7章)。
  7. L.pop(i):删除并返回L中索引值为i的项目。如果L为空,则抛出一个异常。如果i被省略,则i的默认值为-1,删除并返回L中的最后一个元素。
  8. L.sort():升序排列L中的元素。
  9. L.reverse():翻转L中的元素顺序。

5.3.1 克隆

克隆​:使用切片操作复制某个列表。

我们通常应该尽量避免修改一个正在进行遍历的列表。例如,考虑以下代码:

  1. ​def removeDups(L1, L2):​
  2. ​    """假设L1和L2是列表,​
  3. ​    删除L2中出现的L1中的元素"""​
  4. ​    for e1 in L1:​
  5. ​        if e1 in L2:​
  6. ​            L1.remove(e1)​
  7. ​L1 = [1,2,3,4]​
  8. ​L2 = [1,2,5,6]​
  9. ​removeDups(L1, L2)​
  10. ​print('L1 =', L1)​


  1. ​L1 = [2, 3, 4]​

在for循环中,Python使用一个内置计数器跟踪程序在列表中的位置,内部计数器在每次迭代结束时都会增加1。当计数器的值等于列表的当前长度时,循环终止。如果循环过程中列表没有发生改变,那么这种机制是有效的,但如果列表发生改变,就会产生出乎意料的结果。本例中,内置计数器从0开始计数,程序发现了L1[0]在L2中,于是删除了它——将L1的长度减少到3。然后计数器增加1,代码继续检查L1[1]的值是否在L2中。请注意,这时已经不是初始的L1[1]的值(2)了,而是当前的L1[1]的值(3)。

  1. ​#解决方案:避免这种问题的方法是使用切片操作克隆(即复制)这个列表,并使用for e1 in L1[:]这种写法。​
  2. ​def removeDups(L1, L2):​
  3. ​    """假设L1和L2是列表,​
  4. ​    删除L2中出现的L1中的元素"""​
  5. ​    for e1 in L1[:]:​
  6. ​        if e1 in L2:​
  7. ​            L1.remove(e1)​
  8. ​L1 = [1,2,3,4]​
  9. ​L2 = [1,2,5,6]​
  10. ​removeDups(L1, L2)​
  11. ​print('L1 =', L1)​


  1. ​L1 = [3, 4]​

在Python中,切片不是克隆列表的唯一方法。

  1. 表达式list(L)会返回列表L的一份副本。
  2. 如果待复制的列表包含可变对象,而且你也想复制这些可变对象,那么可以导入标准库模块copy,然后使用函数copy.deepcopy。似乎 deepcopy 更加符合我们对「复制」的直觉定义: 一旦复制出来了,就应该是独立的了。

5.3.2 列表推导

列表推导式提供了一种简洁的方式,将某种操作应用到序列中的一个值上。它会创建一个新 列表,其中的每个元素都是一个序列中的值(如另一个列表中的元素)应用给定操作后的结果

  1. ​mixed = [1, 2, 'a', 3, 4.0]​
  2. ​print([x**2 for x in mixed if type(x) == int])​


  1. ​[1, 4, 9]​

5.4 函数对象

在Python中,函数是一等对象。这意味着我们可以像对待其他类型的对象(如int或list)一样对待函数。

  1. 函数可以具有类型,例如,表达式type(abs)的值是;
  2. 函数可以出现在表达式中,如作为赋值语句的右侧项或作为函数的实参;函数可以是列表中的元素;等等。
  3. ​# 使用函数作为实参可以实现一种名为高阶编程的编码方式,这种方式与列表结合使用非常方便​
  4. ​#将函数应用到列表中的元素​
  5. ​def applyToEach(L, f):​
  6. ​    """假设L是列表,f是函数​
  7. ​        将f(e)应用到L的每个元素,并用返回值替换原来的元素"""​
  8. ​    for i in range(len(L)):​
  9. ​        L[i] = f(L[i])​
  10. ​L = [1, -2, 3.33]​
  11. ​print('L =', L)​
  12. ​print('Apply abs to each element of L.')​
  13. ​applyToEach(L, abs)​
  14. ​print('L =', L)​
  15. ​print('Apply int to each element of', L)​
  16. ​applyToEach(L, int)​
  17. ​print('L =', L)​


  1. ​L = [1, -2, 3.33]​
  2. ​Apply abs to each element of L.​
  3. ​L = [1, 2, 3.33]​
  4. ​Apply int to each element of [1, 2, 3.33]​
  5. ​L = [1, 2, 3]​

Python中有一个内置的高阶函数map,它的功能与applyToEach函数相似,但适用范围更广。 1.map函数被设计为与for循环结合使用。在map函数的最简形式中,第一个参数是个一元函数(即只有一个参数的函数),第二个参数是有序的值集合,集合中的值可以一元函数的参数。 2.在for循环中使用map函数时,它的作用类似于range函数,为循环的每次迭代返回一个值。这些值是对第二个参数中的每个元素应用一元函数生成的。例如,下面的代码:

  1. ​L1 = [1, 28, 36]​
  2. ​L2 = [2, 57, 9]​
  3. ​for i in map(min, L1, L2):​
  4. ​    print(i)​


  1. ​1​
  2. ​28​

Python还支持创建匿名函数(即没有绑定名称的函数),这时要使用保留字lambda。Lambda表达式的例子:

  1. ​L = []​
  2. ​for i in map(lambda x, y: x**y, [1 ,2 ,3, 4], [3, 2, 1, 0]):​
  3. ​    L.append(i)​
  4. ​print(L)​


  1. ​[1, 4, 3, 1]​

5.5 字符串、元组、范围与列表

str、tuple、range和list。它们的共同之处在于,都可以使用下面描述的操作:

  1. seq[i]:返回序列中的第i个元素。
  2. len(sep):返回序列长度。
  3. seq1 + seq2:返回两个序列的连接(不适用于range)。
  4. n*seq:返回一个重复了n次seq的序列。
  5. seq[start:end]:返回序列的一个切片。
  6. e in seq:如果序列包含e,则返回True,否则返回False。
  7. e not in seq:如果序列不包含e,则返回True,否则返回False。
  8. for e in seq:遍历序列中的元素。

因为字符串只能包含字符,所以应用范围远远小于元组和列表。但另一方面,处理字符串时有大量内置方法可以使用,这使得完成任务非常轻松。请记住,字符串是不可变的,所以这些方法都返回一个值,而不会对原字符串产生副作用。

  1. s.count(s1):计算字符串s1在s中出现的次数。
  2. s.find(s1):返回子字符串s1在s中第一次出现时的索引值,如果s1不在s中,则返回-1。
  3. s.rfind(s1):功能与find相同,只是从s的末尾开始反向搜索(rfind中的r表示反向)。
  4. s.index(s1):功能与find相同,只是如果s1不在s中,则抛出一个异常。
  5. s.index(s1):功能与index相同,只是从s的末尾开始。
  6. s.lower():将s中的所有大写字母转换为小写。
  7. s.replace(old, new):将s中出现过的所有字符串old替换为字符串new。
  8. s.rstrip():去掉s末尾的空白字符。
  9. s.split(d):使用d作为分隔符拆分字符串s,返回s的一个子字符串列表。

5.6 字典

字典​:(dict,dictionary的缩写)字典类型的对象与列表很相似,区别在于字典使用键对其中的值进行引用,可以将字典看作一个键/值对的集合。字典类型的字面量用大括号表示,其中的元素写法是键加冒号再加上值。

  1. ​monthNumbers = {'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5,1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May'}​
  2. ​print('The third month is ' + monthNumbers[3])​
  3. ​dist = monthNumbers['Apr'] - monthNumbers['Jan']​
  4. ​print('Apr and Jan are', dist, 'months apart')​


  1. ​The third month is Mar​
  2. ​Apr and Jan are 3 months apart​

dict中的项目是无序的,不能通过索引引用。这就是为什么monthNumbers[1]确定无疑地指向键为1的项目,而不是第二个项目。

  1. ​#字典中增加或改变项目​
  2. ​monthNumbers['June'] = 6​
  3. ​monthNumbers['May'] = 'V'​


  1. ​#不同语言互译​
  2. ​EtoF = {'bread':'pain', 'wine':'vin', 'with':'avec', 'I':'Je','eat':'mange', 'drink':'bois', 'John':'Jean','friends':'amis', 'and': 'et', 'of':'du','red':'rouge'}​
  3. ​FtoE = {'pain':'bread', 'vin':'wine', 'avec':'with', 'Je':'I','mange':'eat', 'bois':'drink', 'Jean':'John','amis':'friends', 'et':'and', 'du':'of', 'rouge':'red'}​
  4. ​dicts = {'English to French':EtoF, 'French to English':FtoE} #俩字典合二为一​
  5. ​def translateWord(word, dictionary):​
  6. ​    if word in dictionary.keys():​
  7. ​        return dictionary[word]​
  8. ​    elif word != '':​
  9. ​        return '"' + word + '"'​
  10. ​    return word​

  11. ​def translate(phrase, dicts, direction):​
  12. ​    UCLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'​
  13. ​    LCLetters = 'abcdefghijklmnopqrstuvwxyz'​
  14. ​    letters = UCLetters + LCLetters​
  15. ​    dictionary = dicts[direction]​
  16. ​    translation = ''​
  17. ​    word = ''​
  18. ​    for c in phrase:​
  19. ​        if c in letters:​
  20. ​            word = word + c​
  21. ​        else:​
  22. ​            translation = translation+ translateWord(word, dictionary) + c​
  23. ​            word = ''​
  24. ​    return translation + ' ' + translateWord(word, dictionary)​
  25. ​print (translate('I drink good red wine, and eat bread.',dicts,'English to French'))​
  26. ​print (translate('Je bois du vin rouge.',dicts, 'French to English'))​


  1. ​Je bois "good" rouge vin, et mange pain. ​
  2. ​I drink of wine red.​

多数编程语言都不包含这种提供从键到值的映射关系的内置类型。然而,程序员可以使用其他类型实现同样的功能。例如,使用其中元素为键/值对的列表就可以轻松实现字典,然后可以编写一个简单的函数进行关联搜索,如下所示:

  1. ​#这种实现的问题在于计算效率太低。最坏情况下,程序执行一次搜索可能需要检查列表中的每一个元素​
  2. ​def keySearch(L, k):​
  3. ​    for elem in L:​
  4. ​        if elem[0] == k:​
  5. ​            return elem[1]​
  6. ​    return None​

可以使用for语句遍历字典中的项目。但分配给迭代变量的值是字典键,不是键/值对。迭代过程中没有定义键的顺序。

  1. ​monthNumbers = {'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5,​
  2. ​1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May'}​
  3. ​keys = []​
  4. ​for e in monthNumbers:​
  5. ​    keys.append(str(e))​
  6. ​print(keys)​
  7. ​keys.sort()​
  8. ​print(keys)​


  1. ​['3', 'May', '2', 'Feb', '1', '5', 'Jan', 'Mar', '4', 'Apr']​
  2. ​['1', '2', '3', '4', '5', 'Apr', 'Feb', 'Jan', 'Mar', 'May']​

keys方法返回一个dict_keys类型的对象。①这是view对象的一个例子。视图中没有定义视图的顺序。视图对象是动态的,因为如果与其相关的对象发生变化,我们就可以通过视图对象察觉到这种变化。例如:

  1. ​birthStones = {'Jan':'Garnet', 'Feb':'Amethyst', 'Mar':'Acquamarine','Apr':'Diamond', 'May':'Emerald'}​
  2. ​months = birthStones.keys()​
  3. ​print(months)​
  4. ​birthStones['June'] = 'Pearl'​
  5. ​print(months)​


  1. ​dict_keys(['May', 'Feb', 'Apr', 'Jan', 'Mar'])​
  2. ​dict_keys(['May', 'Feb', 'Jan', 'Mar', 'Apr', 'June'])​

可以使用for语句遍历dicttype类型的对象,也可以使用in检测其中的成员。dicttype类型的对象可以很容易地转换为列表,如list(months)。

  1. 并非所有对象都可以用作字典键:键必须是一个可散列类型的对象。所有Python内置的不可变类型都是可散列的,而且所有Python内置的可变类型都是不可散列的。
  2. 如果一个类型具有以下两条性质,就可以说它是“可散列的”: (1)具有hash方法,可以将一个这种类型的对象映射为一个int值,而且对于每一个对象,由hash返回的值在这个对象的生命周期中是不变的; (2)具有eq方法,可以比较两个对象是否相等。

字典方法:

  1. len(d):返回d中项目的数量。
  2. d.keys():返回d中所有键的视图。
  3. d.values():返回d中所有值的视图。
  4. k in d:如果k在d中,则返回True。
  5. d[k]:返回d中键为k的项目。
  6. d.get(k, v):如果k在d中,则返回d[k],否则返回v。
  7. d[k] = v:在d中将值v与键k关联。如果已经有一个与k关联的值,则替换。
  8. del d[k]:从d中删除键k。
  9. for k in d:遍历d中的键。


举报

相关推荐

0 条评论