前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Python编程导论】第五章- 结构化类型、可变性与高阶函数

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

作者头像
Datawhale
发布2019-07-08 10:54:05
1.3K0
发布2019-07-08 10:54:05
举报
文章被收录于专栏:Datawhale专栏Datawhale专栏

基本概念

5.1 元组

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

代码语言:javascript
复制
# tuple类型的字面量形式是位于小括号之中的由逗号隔开的一组元素
t1=()
t2 = (1,)  #注意当元组中只有一个元素时,加逗号
t3 = (1, 'two', 3)
print(t1,t2,t3)
代码语言:javascript
复制
() (1,) (1, 'two', 3)
代码语言:javascript
复制
#可以在元组上使用重复操作。
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])
代码语言:javascript
复制
('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中的多重赋值语句方便地提取单个元素。

代码语言:javascript
复制
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。

  1. 如果step是个正数,那么最后一个元素就是小于stop的最大整数start + i * step。
  2. 如果step是个负数,那么最后一个元素就是大于stop的最小整数start +i * step。
  3. 如果只有2个实参,那么步长就为1。
  4. 如果只有1个实参,那么这个参数就是结束值,起始值默认为0,步长默认为1。
代码语言:javascript
复制
#除了连接操作和重复操作,其他所有能够在元组上进行的操作同样适用于范围。
range(10)[2:6][2]
代码语言:javascript
复制
4
代码语言:javascript
复制
range(0,7,2)==range(0,8,2)#值就是True
range(0,7,2)==range(6,-1,-2)#注意:值是False。因为尽管这两个范围包含同样的

5.3 列表与可变性

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

代码语言:javascript
复制
t1=[]#空list
t2=[1]#单元素list
L = ['I did it all', 4, 'love']
for i in range(len(L)):
    print(L[i])
代码语言:javascript
复制
I did it all
4
love
代码语言:javascript
复制
#list切片操作
[1, 2, 3, 4][1:3][1]
代码语言:javascript
复制
3

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

  1. 很多操作符可以创建可变类型的对象,也可以将变量绑定到这种类型的对象上。
  2. 但不可变类型的对象是不能被修改的,相比之下,list类型的对象在创建完成后可以被修改。
代码语言:javascript
复制
#解释器会创建两个新列表,然后为其绑定合适的变量
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)
代码语言:javascript
复制
Univs = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']]
Univs1 = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']]
True

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

代码语言:javascript
复制
print(Univs == Univs1) #测试值是否相等
print(id(Univs) == id(Univs1)) #测试对象是否相等
print('Id of Univs =', id(Univs))
print('Id of Univs1 =', id(Univs1))
代码语言:javascript
复制
代码语言:javascript
复制
True
False
Id of Univs = 81469128
Id of Univs1 = 80584584

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

代码语言:javascript
复制
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]))
代码语言:javascript
复制
Ids of Univs[0] and Univs[1] 81469320 80977096
Ids of Univs1[0] and Univs1[1] 81465928 81555848

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

代码语言:javascript
复制
#注意Techs这个对象改变了,同时改变的只有Univs,因为Univs = [Techs, Ivys]
Techs.append('RPI')
print('Univs =', Univs)
print('Univs1 =', Univs1)
代码语言:javascript
复制
代码语言:javascript
复制
Univs = [['MIT', 'Caltech', 'RPI', 'RPI'], ['Harvard', 'Yale', 'Brown']]
Univs1 = [['MIT', 'Caltech'], ['Harvard', 'Yale', 'Brown']]

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

代码语言:javascript
复制
#和元组一样,可以使用for语句遍历列表中的元素。
for e in Univs:
    print('Univs contains', e)
    print(' which contains')
    for u in e:
        print(' ', u)
代码语言:javascript
复制
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方法。如下所示:

代码语言:javascript
复制
#操作符+确实没有副作用,它会创建并返回一个新的列表。相反,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)
代码语言:javascript
复制
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外,这些方法都会改变列表。

  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 克隆

克隆:使用切片操作复制某个列表。 我们通常应该尽量避免修改一个正在进行遍历的列表。例如,考虑以下代码:

代码语言:javascript
复制
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)
代码语言:javascript
复制
L1 = [2, 3, 4]

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

代码语言:javascript
复制
#解决方案:避免这种问题的方法是使用切片操作克隆(即复制)这个列表,并使用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)
代码语言:javascript
复制
L1 = [3, 4]

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

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

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

代码语言:javascript
复制
mixed = [1, 2, 'a', 3, 4.0]
print([x**2 for x in mixed if type(x) == int])
代码语言:javascript
复制
[1, 4, 9]

5.4 函数对象

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

  1. 函数可以具有类型,例如,表达式type(abs)的值是;
  2. 函数可以出现在表达式中,如作为赋值语句的右侧项或作为函数的实参;函数可以是列表中的元素;等等。
代码语言:javascript
复制
# 使用函数作为实参可以实现一种名为高阶编程的编码方式,这种方式与列表结合使用非常方便
#将函数应用到列表中的元素
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)
代码语言:javascript
复制
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函数,为循环的每次迭代返回一个值。这些值是对第二个参数中的每个元素应用一元函数生成的。例如,下面的代码:

代码语言:javascript
复制
L1 = [1, 28, 36]
L2 = [2, 57, 9]
for i in map(min, L1, L2):
    print(i)
代码语言:javascript
复制
1
28

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

代码语言:javascript
复制
L = []
for i in map(lambda x, y: x**y, [1 ,2 ,3, 4], [3, 2, 1, 0]):
    L.append(i)
print(L)
代码语言:javascript
复制
[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的缩写)字典类型的对象与列表很相似,区别在于字典使用键对其中的值进行引用,可以将字典看作一个键/值对的集合。字典类型的字面量用大括号表示,其中的元素写法是键加冒号再加上值。

代码语言:javascript
复制
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')
代码语言:javascript
复制
The third month is Mar
Apr and Jan are 3 months apart

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

代码语言:javascript
复制
#字典中增加或改变项目
monthNumbers['June'] = 6
monthNumbers['May'] = 'V'
代码语言:javascript
复制
#不同语言互译
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'))
代码语言:javascript
复制
Je bois "good" rouge vin, et mange pain. 
I drink of wine red.

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

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

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

代码语言:javascript
复制
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)
代码语言:javascript
复制
['3', 'May', '2', 'Feb', '1', '5', 'Jan', 'Mar', '4', 'Apr']
['1', '2', '3', '4', '5', 'Apr', 'Feb', 'Jan', 'Mar', 'May']

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

代码语言:javascript
复制
birthStones = {'Jan':'Garnet', 'Feb':'Amethyst', 'Mar':'Acquamarine','Apr':'Diamond', 'May':'Emerald'}
months = birthStones.keys()
print(months)
birthStones['June'] = 'Pearl'
print(months)
代码语言:javascript
复制
dict_keys(['May', 'Feb', 'Apr', 'Jan', 'Mar'])
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中的键。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Datawhale 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本概念
    • 5.1 元组
      • 5.1.1 序列与多重赋值
    • 5.2 范围
      • 5.3 列表与可变性
        • 5.3.1 克隆
        • 5.3.2 列表推导
      • 5.4 函数对象
        • 5.5 字符串、元组、范围与列表
          • 5.6 字典
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档