python程序设计 课件 第5、6章 字典与集合、序列与迭代_第1页
python程序设计 课件 第5、6章 字典与集合、序列与迭代_第2页
python程序设计 课件 第5、6章 字典与集合、序列与迭代_第3页
python程序设计 课件 第5、6章 字典与集合、序列与迭代_第4页
python程序设计 课件 第5、6章 字典与集合、序列与迭代_第5页
已阅读5页,还剩167页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第5章字典与集合Python程序设计第5章字典与集合Python提供了丰富的序列类型、内置函数、方法以及语法支持等,让我们能够轻松运用序列形式的容器(数据结构)对象。但数据并非只有顺序的形式,也有“映射”形式的数据结构,即“字典”,其内含元素都是所谓的“键值配对”,从键映射到值。第5章字典与集合此外,集合也是常见的数据类型,具有严谨的数学定义,可用来存放东西(对象),但只能放进去一次,也就是记录某东西“在不在”里面。集合既非序列、也非映射,自有其一套机制,且与字典相关。有了列表与字典,几乎就可组合出绝大部分所需的数据结构。当谈到某种数据类型时,不外乎学习如何建立、存与取(写与读)、搜寻、排序、分类、过滤等等操作动作。关于杂凑1字典与集合的访问接口4字典结构2集合类型3目录5.1

关于杂凑5.1

关于杂凑杂凑(Hashing)是计算机科学中一种对数据的处理方法,通过某种特定的函数/算法(称为杂凑函数/算法),将要检索的项与用来检索的索引(称为杂凑,或者杂凑值)关联起来,生成一种便于搜索的数据结构(称为杂凑表,散列)。它也常被用作一种信息安全的实施方法,由一串数据中经过杂凑算法计算出来的数据指纹,经常用来识别文档与数据是否有被篡改。5.1

关于杂凑常见的杂凑演算法有MD5、SHA-1、SHA-256等等,可用来求得数据的数字指纹,其应用相当广泛,例如网站若提供大型文件,可一并附上该文件的杂凑值,下载后便能以杂凑值检查文件是否完整无缺。P2P(peer-to-peer)对等网络文件分享,诸如BitTorrent、eMule、BitComet,也会使用杂凑技术,借以检验下载的文件片段是否正确。杂凑因具有不可逆的性质,无法从杂凑值逆向推算原先的数据,所以也用于密码加密,当要存储密码时,并非直接存储,而是存储密码的杂凑值,即便被偷了,也无法破解得知密码。5.1

关于杂凑字典(dict)类型含有键值配对,只要是可杂凑的对象(不可变对象都可杂凑)都能作为键,所以不像列表受限于从0起跳的连续整数索引值,字典可使用不连续的大数字作为键,例如学生的学号,也可使用字符串作为键,例如汽车的车牌、日期时间、人物姓名、水果名等等;集合类型则是储存某些东西的数据结构,同样的,可杂凑的对象方能成为集合的元素。字典类型在其他程序语言里可能称为关联式阵列、杂凑表、映射等,概念相同但细节不同。字典的创建字典的键与值字典生成式全局与局部字典应用实例5.2字典结构5.2

字典结构与列表(list)是存储一个个的对象,并以索引值(从0起跳的整数)存取不同,字典是存储键值配对,透过键来存取相对应的值。建立字典时可使用大括号“{}”,里面放进以逗号隔开的键值配对,键与值则以冒号“:”隔开,存取时仍使用方括号“[]”,在方括号内放入键,便可存取相关联的值。>>>d={} #建立空字典对象>>>d={'name':'Amy','age':27} #以大括号语法建立dict对象>>>d #字典不具顺序性{'age':27,'name':'Amy'}>>>d['name'] #以方括号存取,类似于list'Amy'>>>d['age']=29 #dict是可变对象,可指派新值>>>d['score']=77 #但若是首次指派不存在的键,>>>d #便会建立该键值配对的关系{'age':29,'name':'Amy','score':77}>>>d['ape'] #但若取用时字典无此键,会发生错误Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>KeyError:'ape' #键错误5.2

字典结构5.2

字典结构图5-1示意了上述程序代码里的字典对象d,看起来跟列表很像,但字典的键不限定于整数。

字典示意图5.2.1字典的创建字典是一种映射形式的类型,从“键”映射到“值”,值可以是任何类型的对象,例如字符串、整数、列表或另一个字典,键则是不可变对象,例如字符串、整数、元组,最常使用的是字符串与整数。使用字符串的话,便能以容易记忆的字符串作为键,若键为整数的话,可使用任何范围的整数,不像表的索引值必须从0开始计数。>>>d={'str':[0,1,2],(30,41,52):{'x':0,'y':1}}>>>d[(30,41,52)] #此键是个tuple,{'x':0,'y':1} #其值是个dict>>>d={1504301:'Amy',1504305:'Bob'}>>>d[1504301] #以学号(很大的整数)作为键'Amy'5.2.1字典的创建除了以大括号包起来的字面值形式,也能透过内置函数dict(也是类型dict的构造函数)来建立字典对象,其参数的形式非常多,非常方便。>>>d=dict(name='Amy',age=40,job='writer’)#以关键字参数指定键值配对>>>d #你看到的顺序,不一定会和此处相同{'age':40,'job':'writer','name':'Amy'}>>>keys=['name','age','job'] #含有键的列表对象>>>values=['Amy',40,'writer']#含有值的列表对象>>>d2=dict(zip(keys,values)) #透过内置函数zip组合>>>d2 #虽内容物与d相同,但顺序不同{'job':'writer','age':40,'name':'Amy'}5.2.1字典的创建>>>d3=dict(d2,name='Amanda')#以d2为基础,并以关键字参数>>>d3#指定键值配对{'job':'writer','age':40,'name':'Amanda'}>>>d4=dict(zip(range(100,100+3),('Amy','Bob','Cathy')))>>>d4{100:'Amy',101:'Bob',102:'Cathy'}5.2.1字典的创建5.2.1字典的创建字典建立后,接下来便是各种支持字典的运算符与方法。>>>d={'age':29,'name':'Amy','score':77}>>>len(d) #长度,含有几个键值配对3>>>'age'ind #测试字典有无此键True>>>deld['name'] #刪除此键(以及相对应的键值配对关系)>>>'name'indFalse>>>d['name'] #以[]形式取用,若无该键会发生错误Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>KeyError:'name'>>>d.get('name')#以方法get取用,若无该键会返回None>>>d.get('name','Amy')#若无该键就返回指定的缺省值,'Amy'#注意,字典内仍无该键>>>d['name']='Amy'#以[]形式指派键值配对>>>d.setdefault('job')#以方法setdefault指派,若无该键,>>>d #缺省会指派为None{'job':None,'age':29,'score':77,'name':'Amy'}5.2.1字典的创建>>>d.setdefault('city','London') #加入此键值配对>>>d.setdefault('city') #已有此键,返回相对应的值'London'5.2.1字典的创建5.2.1字典的创建虽然字典是一种映射形式的类型,但提供了各种方法,能把键、值,甚至是键值配对以符合序列类型的对象返回,那么,我们就能用“迭代”方法逐一处理。d={'a':1,'b':2,'c':3} #类型dictforkeyind: #放在for/in循环里,会提供键的可迭代项

print(key)forkeyind.keys(): #方法keys(),提供键的可迭代项

print(key)forvalueind.values(): #方法values(),提供值的可迭代项

print(value)fork,vind.items(): #方法items(),提供键值配对的可迭代项

print(k,v)5.2.1字典的创建再强调一次,字典类型的键值配对并无顺序,就算是同样的程序代码,如果放到不同版本的Python来执行、放到另一台电脑上执行,其结果所显示的顺序不一定相同,甚至每次执行的结果都不同;换句话说,字典只提供键映射到值这样的关系而已,并不保证顺序性。字典类型的操作列于表7-1。5.2.1字典的创建表5-1字典操作5.2.2字典的键与值只要符合抽象类型Hashable(可散列)的对象,就能作为字典的键,最常用的是类型str(字符串)与int(整数);可变类型如list、set、dict,都无法作为键。注意到整数1与浮点数1.0的杂凑值相同,但因为类型float无法精确地表示大部分的浮点数,并不适合拿来作为字典的键,实际中也很少有人使用。>>>fromdecimalimportDecimal>>>fromfractionsimportFraction>>>d={1:111,'a':2,Decimal('3.14'):'pi',Fraction(1,3):1.3}>>>d{'a':2,1:111,Decimal('3.14'):'pi',Fraction(1,3):1.3}>>>hash(1.0),hash(1)(1,1) #杂凑值相同,>>>d[1.0],d[1] #所以会得到相同的对应值(111,111)5.2.2字典的键与值5.2.2字典的键与值若键是字符串,也能使用构造函数dict来建立字典对象,但因为需要以参数的形式传入,所以该键(字符串)必须符合Python的识别字规则,例如字符串'123abc'就无法以此形式建立。>>>dict(foo=1,bar=2,abc123=3)#以关键字参数传入键与值{'foo':1,'bar':2,'abc123':3}>>>dict(foo=1,bar=2,123abc=3)#123abc不符合识别字规则

File"<stdin>",line1dict(foo=1,bar=2,123abc=3)^SyntaxError:invalidsyntax>>>d={'foo':1,'bar':2,'123abc':3} #以这种形式就没问题了{'foo':1,'bar':2,'123abc':3}>>>d2=dict(d,foo=555,abc=999) #以先前的字典d为基础,>>>d2#再以关键字参数新增或更新键值配对{'foo':555,'abc':999,'bar':2,'123abc':3}>>>k=['foo','bar'];v=[101,102] #以zip合并两个列表,>>>d3=dict(zip(k,v),abc=999) #便可作为键值配对的提供者>>>d3{'foo':101,'abc':999,'bar':102}5.2.2字典的键与值5.2.2字典的键与值另外也能以字典类型的方法fromkeys来建立列表,此方法不属于某一个实体对象,所以称为类型方法。其首个参数可以是序列对象(或可迭代项),提供键,后一个参数(选用性)则是缺省值,若不指定则会是None。>>>dict.fromkeys(['a','b','c']){'a':None,'b':None,'c':None}>>>dict.fromkeys(['a','b','c'],999){'a':999,'b':999,'c':999}>>>dict.fromkeys(range(5),0){0:0,1:0,2:0,3:0,4:0}5.2.3字典生成式用来建立字典对象的字典生成式与列表生成式非常类似,只是语法稍有不同,它使用大括号“{}”包起来,在for的左边必须是“k:v”的形式,提供键值配对;同样地,也能加上if进行过滤。{表达式:表达式for名称in可迭代项}{表达式:表达式for名称in可迭代项if表达式}>>>{k:k**2forkinrange(5)} #键从0到4,值是平方{0:0,1:1,2:4,3:9,4:16}>>>dict([(k,k**2)forkinrange(5)]) #使用列表生成式与构造函数dict,{0:0,1:1,2:4,3:9,4:16} #达到同样的效果>>>d={'Amy':90,'Joe':45,'Kevin':33}>>>{k:vfork,vind.items()ifv<60}#仅拿出成绩低于60的{'Joe':45,'Kevin':33}>>>li=['a','b','c']>>>{a:ifori,ainenumerate(li,start=101)}{'a':101,'b':102,'c':103} #为每个元素賦予某列举值>>>dict(enumerate(li,start=1)) #呃,不一样...{1:'a',2:'b',3:'c'}>>>dict(zip(li,range(101,101+len(li))))#弄出相同的字典了{'a':101,'b':102,'c':103}5.2.3字典生成式5.2.3字典生成式虽然某些字典生成式能以列表生成式与构造函数dict改写,但后者必须先建立列表对象,然后再传入构造函数dict,效能较为缓慢,从语法来看也较不简洁。5.2.4全局与局部Python会把“名称”放在命名空间里,也就是把名称与对象的绑定关系存储在字典里。【程序实例5-1】全局与局部命名空间。#symbol_table.pya=1b=2c=3deffoo(x,y):a='aaa'b='bbb'print(locals())print(globals())print('*'*10)foo(80,91)5.2.4全局与局部5.2.4全局与局部名称a、b、c、foo被放在全局命名空间里。若调用函数foo,会进入函数局部范围,名称x、y、a、b会被放在局部的命名空间里。以Python内置函数globals取得含有全局名称(与对象绑定关系)的字典,也称为全局符号表,以locals取得含有局部名称的字典(局部符号表)。上述程序代码执行后,会输出如下的信息:{'a':1,'c':3,'b':2,'__builtins__':<module'builtins'(built-in)>,'__file__':'symbol_table.py','__package__':None,'__cached__':None,'__name__':'__main__','foo':<functionfooat0x7fe2d8ec>,'__doc__':None}**********{'a':'aaa','y':91,'b':'bbb','x':80}5.2.4全局与局部在全局字典里,你可看到名称a、b、c、foo以及一些其他名称,例如__file__指向文件名的字符串,__name__则是目前所在模块名,而__builtins__则指向内置模块,也因为有它,Python才知道要去哪寻找内置的函数、常数、异常。在局部字典里,可看到x、y、a、b,因字典不具顺序性,所以并不会按照参数的顺序、赋值语句的先后顺序排列名称。5.2.5字典应用实例若需要根据某对象的内容拿出相对应的数据或执行不同的程序代码,往往会使用if/elif/elif.../else来实作,但可能太过冗长;可以把东西先放进字典,然后直接取用即可。x='apple'result=Noneifx=='apple': #以一连串的if/elif.../else来做判断,

result=1elifx=='banana':result=2elifx=='grape': #该执行哪部分的程序代码

result=3else:result=-1####上述程序代码可改写为:####x='apple'd={'apple':1,'banana':2,'grape':3}result=d.get(x,-1)5.2.5字典应用实例5.2.5字典应用实例因为函数也是对象,也可作为值放进字典里,这样一来便可根据键来执行相对应的程序码,例如:deff0():pass #准备一些函数deff1():passdeff2():pass #作为值放进字典里d={'apple':f0,'banana':f1,'grape':f2}x='apple'ifxind: #若有该键,

d[x]() #便取出对应的值(函数对象)并调用执行若字典的值可杂凑,便可逆转键值配对。不过若值重复的话,逆转后将会只剩一个键值配对。【程序实例5-2】逆转字典的键值配对。#dict_invert.py#学生姓名与成绩d={'Amy':45,'Bob':50,'Cathy':62,'David':45,'Eason':63,'Fred':78,'George':72,'Helen':82,'Ivan':100,'Jason':98,'Kevin':0,'Laura':100}#逆转键值配对d2={v:kfork,vind.items()}#逆转后,键100、值可能会是'Laura'或'Ivan',只剩一个5.2.5字典应用实例5.2.5字典应用实例因为字典不具顺序性,若想要按照某种排序方式输出或操作字典的话,可运用内置函数sorted来排序。可再传入一个名为key的参数,由它来指定根据什么来排序。【程序实例5-3】排序字典。#dict_sort.py#键是学生姓名,值是一个含有三项成绩的tuple对象,假设成绩依序是数学、历史、英文d={'Amy':(45,60,33),'Bob':(50,62,78),'Cathy':(62,98,87),'David':(45,22,12),'Eason':(63,55,71),'Fred':(78,79,32)}forkinsorted(d.keys()): #根据键(姓名)排序

print(k,d[k])5.2.5字典应用实例deffoo(item):returnitem[1][2] #根据英文成绩来排序foriteminsorted(d.items(),key=foo):print(item)defbar(item):returnsum(item[1]) #根据总分来排序foriteminsorted(d.items(),key=bar):print(item)5.2.5字典应用实例5.2.5字典应用实例字符串格式化,包括运算符“%”与字符串方法format,都可以运用字典。集合的创建集合的元素集合的数学运算集成生成式5.3集合类型5.3

集合类型接下来,我们来看看类型set(集合)。集合是一种不具顺序性的容器类型,同一个对象只能放进去一次。建立集合时,字面值语法是以大括号“{}”包住想要的一个个元素,或调用内置函数set,传入可迭代项(提供一个个元素)。元素可以是任何不可变对象,例如int、tuple、str。>>>x={1,2,3,4,5} #以大括号包住集合元素>>>x{1,2,3,4,5}>>>x2=set([1,'a',(3,4),5]) #内置函数set,传入含有元素的列表>>>x2{1,'a',(3,4),5}>>>x3=set(range(1,5+1)) #内置函数set,传入含有元素的可迭代项>>>x4={0,1,[2,3,4]} #list是可变对象,发生错误Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>TypeError:unhashabletype:'list'5.3

集合类型5.3

集合类型上述程序中,x2对象的示意如图所示。与字典相同,集合内的元素也不具有顺序性,类型set仅代表某东西是否存在于该集合内。

集合示意>>>x={};type(x) #{}会建立空字典,不是空集合<class'dict'>>>>x={1,2,3,4,5}>>>x[0] #集合不具顺序性,不支持索引存取方式Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>TypeError:'set'objectdoesnotsupportindexing>>>len(x) #长度,集合内元素的个数5>>>1inx #判断某对象存不存在于集合内True5.3

集合类型>>>x.add(6) #加入新元素>>>x.add(6) #加入已有的元素,无作用>>>x.remove(1) #移除某元素>>>x.remove(7) #若该元素不存在于集合内,发生错误Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>KeyError:7>>>x.discard(7) #移除某元素,若不存在并不会发生错误>>>x{2,3,4,5,6}5.3

集合类型>>>x&{5,6,999} #“&”运算符,交集(intersection){5,6}>>>x|{5,6,999} #“|”运算符,联集(union){2,3,4,5,6,999}>>>x-{5,6,999} #“-”运算符,差集(difference){2,3,4}>>>x^{5,6,999} #“^”运算符,对称差集(symmetricdifference){2,3,4,999}5.3

集合类型5.3

集合类型当想检查两个列表是否含有相同的元素,但不在乎顺序与是否重复时,便可利用类型set,非常便利。>>>li0=[0,1,2];li1=[2,2,1,0,2,1]#不同的列表>>>set(li1) #传入内置函数set,得到不重复元素的集合{0,1,2}>>>set(li0)==set(li1) #以集合的眼光来看是相等的True5.3

集合类型集合类型是一种不具顺序性的容器类型,其元素必须是可杂凑的对象,记录某对象在不在集合里。因为不具顺序性,所以集合不支持索引与切片的存取方式。5.3.1集合的创建建立集合时,可使用被圆括号“()”包住、以逗号“,”隔开元素的字面值形式,或是使用类型set的构造函数。调用构造函数set时,参数是个可迭代项,负责提供一个个的元素,元素必须可杂凑。>>>empty_dict={} #这是空字典>>>empty_set=set() #这才是空集合>>>{'a','b','c'} #含有三个元素的集合{'c','b','a'} #集合不具顺序性>>>{(0,1,2),3,3.14} #元素可以是任何不可变对象{(0,1,2),3.14,3}>>>set('abc') #使用构造函数set,传入序列对象{'c','b','a'}>>>set([30,41,52]) #传入列表{41,52,30}5.3.1集合的创建>>>set(range(5)) #传入可迭代项{0,1,2,3,4}>>>s0=set(range(5))>>>set(s0) #传入另一个集合对象{0,1,2,3,4}>>>len(s0) #长度5>>>1ins0 #判断里面是否含有某元素True5.3.1集合的创建5.3.2集合的元素集合建立后,因为是可变对象,可使用集合专属的方法原地修改其元素。>>>s={0,1,2,3,4,5}>>>s.add(6) #加入int对象6>>>s.add(3) #加入已有的元素,无作用>>>s.remove(0) #移除0>>>s{1,2,3,4,5,6}>>>s.remove(999) #若想移除的元素不存在,引发异常Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>KeyError:9995.3.2集合的元素>>>s.discard(999) #使用discard的话,元素不存在不会引发异常>>>s.pop() #随机移除某元素、返回该元素2>>>s{3,4,5,6}>>>s1=s.copy() #浅复制>>>s.clear() #清空集合>>>s.pop() #若集合为空,pop会引发异常Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>KeyError:'popfromanemptyset'5.3.2集合的元素5.3.3集合的数学运算集合有严谨的数学定义,Python也以运算符和方法支持集合的数学运算,包括联集、交集、差集、对称差集以及子集合、超集合的判断。>>>s0={1,2,3,4} #测试用集合>>>s1={3,4,5,6,7}>>>s2={1,2,3}>>>s3={91,92,93}>>>s0==s1,s0==s0 #判断是否含有同样的元素(False,True)>>>s2<=s0,s2.issubset(s0) #子集合(True,True)>>>s2<s0,s0<s0 #真子集合(True,False)5.3.3集合的数学运算>>>s0>=s2,s0.issuperset(s2) #超集合(True,True)>>>s0>s2,s0>s0 #真超集合(True,False)>>>s0|s1 #联集,产生新集合对象{1,2,3,4,5,6,7}>>>s0&s1 #交集,产生新集合对象{3,4}>>>s0-s1 #差集,产生新集合对象{1,2}5.3.3集合的数学运算>>>s0^s1 #对称差集,产生新集合对象{1,2,5,6,7}>>>s0&s3,s0.isdisjoint(s3) #无交集(set(),True)5.3.3集合的数学运算5.3.3集合的数学运算同一种集合运算,例如联集,有运算符“|”与方法“union”可用,但运算符只能接受集合对象,而方法则可接受可迭代项,例如列表。>>>s0={1,2,3,4}>>>s0|[3,4,5,6] #运算符只能接受集合对象Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>TypeError:unsupportedoperandtype(s)for|:'set'and'list'>>>s0.union([3,4,5,6]) #方法可以接受可迭代项{1,2,3,4,5,6}>>>set('abc')&'bcd'Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>TypeError:unsupportedoperandtype(s)for&:'set'and'str'>>>set('abc').intersection('bcd'){'c','b'}5.3.3集合的数学运算5.3.3集合的数学运算使用增强型赋值语句“|=、&=、-=、^=”时须小心,这是原地修改的动作,不会产生新集合对象,而是更新(修改)语句左边的集合对象。>>>s0={1,2,3,4}>>>s0|={3,4,5,6} #只示范“|=”,其余都类似>>>s0{1,2,3,4,5,6}5.3.4集合生成式集合也有集合生成式可用,其语法和列表生成式、字典生成式类似,一般形式如下:{表达式for名称in可迭代项}{表达式for名称in可迭代项if表达式}>>>li=['a','bar','candy','o','car']>>>{len(x)forxinli}{1,3,5} #记录曾出现过的字符串长度>>>{xforxinliiflen(x)==1}{'a','o'} #只收录长度为1的字符串>>>li=[0,1,1,2,2,3,3,4,5,5,6]>>>{xforxinliifli.count(x)==2}{1,2,3,5} #挑出在原列表出现2次的元素5.4字典与集合的访问接口可杂凑项映射5.4

字典与集合的访问接口在学习了字典与集合的基本知识后,我们来介绍相关的抽象类型,了解字典与集合提供哪些存取接口。5.4.1可杂凑项之前介绍类型dict时,我们说“键”可以是任何不可变对象,这种说法还不够正确,应该说任何“可杂凑项(hashable)”都可作为字典的键。Python有个抽象类型Hashable,符合此接口的对象可依照杂凑演算法算出独一无二的杂凑值,只要对象不相同,就会得到不同的杂凑值。Python内置的不可变类型都符合此抽象类型,而内置的可变类型则都不符合。调用内置函数hash可得到某对象的杂凑值,但实际中很少直接使用该值。>>>fromcollections.abcimport*>>>issubclass(int,Hashable) #内置的不可变类型都符合Hashable接口True>>>issubclass(tuple,Hashable)True>>>issubclass(list,Hashable) #内置的可变类型,不符合Hashable接口False #因为可变对象的内容会改变,其杂凑值不定>>>issubclass(dict,Hashable)False>>>hash(1),hash('abc'),hash((30,41,52))(1,-39521455,1696699620) #不可变对象拥有独一无二的杂凑值5.4.1可杂凑项>>>a=1;b='abc';c=(30,41,52)>>>hash(a),hash(b),hash(c) #既然a==1为真,内容相同,(1,-39521455,1696699620) #那么其杂凑值也会相同>>>hash([0,1,2]) #不可变对象没有杂凑值Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>TypeError:unhashabletype:'list'>>>hash('name'),hash('age'),hash('score')(1574142124,537780116,-23663026)>>>hash(1),hash('a'),hash((3,4)),hash(5)(1,738615097,1699342716,5)5.4.1可杂凑项5.4.1可杂凑项不可变对象在其生存周期内内容绝对不变,所以其杂凑值不仅独一无二,而且也不会改变,可作为字典的存储依据;所以较正确的字典示意图应如图所示,字典其实是存储“对象的杂凑值”与“值(任何对象)”的配对关系。下次当你给定某键时,字典就会算出该键的杂凑值,然后取出相对应的值。

字典示意图5.4.1可杂凑项若对象属于容器类型,那么不仅对象本身必须不可变,其内容也必须不可变,方可算是不可变对象。所以,tuple对象本身虽不可变,但若含有可变对象(例如list),那么该对象就不算是不可变,无法作为字典的键。>>>li=[0,1,2] #list对象,可变>>>t=(3,4,li) #tuple对象,含有可变对象>>>d={'a':1,t:2} #无法作为字典的键Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>TypeError:unhashabletype:'list'5.4.1可杂凑项同样的道理,类型set也不是直接存储某对象,而是存储杂凑值。当把某对象放进集合时,会先算出杂凑值再放进去;当要测试某对象存不存在集合内时,只要算出对象的杂凑值,然后再检查即可(见图示意)。

集合示意图5.4.1可杂凑项因为杂凑演算法的特性,给定某内容(对象)就算出相对应的杂凑值,若对象不同就必定会得到不一样的杂凑值,如此一来便能轻松得到字典与集合的功能。还请注意,不同Python版本可能会使用不同的杂凑演算法,不同平台的Python实现也可能使用不一样的杂凑演算法,所以你执行上述程序时,不会得到跟实例一样的杂凑值。5.4.2映射项图7-5显示了与以映射(Mapping)为中心的相关抽象类型,类型dict符合抽象类型MutableMapping(可变映射)定义的接口,而且dict也是目前Python唯一的内置映射类型;类型set符合抽象类型Set定义的接口。

映射相关的抽象类型层级5.4.2映射项我们先从类型set的抽象类型Set着手,因为它是个容器类型,很自然地继承了抽象类型Sized与Container,所以可使用内置函数len算出长度(含有元素个数),并以运算符“in”检查某对象是否存在于集合之中。而且也符合可迭代项接口,所以可使用内置函数iter得到迭代器,然后调用next逐一取出其中的元素,只不过因为类型set不具顺序性,所以我们并不知道迭代器会以何顺序给出元素。>>>x={2,'a',(3,5),'b',17} #类型set>>>len(x) #长度,元素的个数5>>>'b'inx #成员关系运算符True>>>xit=iter(x)#取得迭代器>>>next(xit),next(xit)#取出前两个元素,(17,'a')#注意,不具顺序性>>>[eforeinxiftype(e)isint]#既然符合Iterable接口[17,2] #所以可用于需要迭代协定的地方5.4.2映射项5.4.2映射项然后是类型dict符合的接口面,抽象类型Mapping(映射)与MutableMapping(可变映射),同样的,因为属于容器类型,所以也都继承了Sized与Container,并拥有许多字典专属的方法。>>>d={'name':'Amy','age':40,'score':77}>>>len(d) #长度,键值配对的个数3>>>'age'ind #“in”运算符,某键是否存在True>>>d['name'] #以键取出值'Amy'>>>d['job']='wrier' #重新指派,新增或更新>>>d.pop('name') #移除指定键(与其键值配对)'Amy'5.4.2映射项>>>d.pop('abc') #若不存在该键,发生错误Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>KeyError:'abc'>>>d.pop('abc','default')#指定不存在该键时应返回的缺省值'default'>>>d.update({'name':'John','age':33})#传入可提供键值配对的对象,>>>d#更新原字典的内容{'job':'wrier','age':33,'name':'John','score':77}>>>d2=d.copy() #浅复制>>>d.clear() #刪除字典的全部内容5.4.2映射项5.4.2映射项类型dict符合的接口抽象类型Mapping,也继承了Iterable,所以也能如同类型list与set,透过内置函数iter拿到能迭代内容的迭代器,再以next逐一取出。但因为字典含有键与值,较为复杂,所以又再提供能动态浏览字典内容的抽象类型MappingView(映射浏览),从图7-5你可得知,当要迭代字典的键与键值配对时,其接口(KeysView与ItemsView)是个符合Set与MappingView的可迭代项,因为键与键值配对不具顺序性,所以继承自抽象类型Set,而MappingView则提供动态浏览字典内容的功能,当字典内容改变时,符合MappingView接口的对象也能随之反映;至于字典的值,因为有可能重复(不同键可映射到同样的值),所以其接口(ValuesView)较为特殊,并不继承自Set,除了继承自MappingView动态反映字典内容,另行继承自Container与Iterable。5.4.2映射项换句话说,当想要以迭代方式操作字典时,作法是调用相对应的方法,得到能迭代键、值、或键值配对的可迭代项,如此即可。而因为这些可迭代项符合MappingView(映射浏览)接口,提供动态浏览字典内容的特色,一旦字典内容改变,也会随之反映。>>>d={'a':1,'b':2,'c':3}>>>keys=d.keys() #以方法keys与values,取得>>>values=d.values() #键与值的映射浏览对象,>>>list(zip(keys,values)) #其顺序互相对应[('a',1),('c',3),('b',2)]>>>deld['a'] #动态刪除某键(值配对)>>>list(keys) #也随之反映['c','b']>>>keys&{'a','b'} #因为也是个集合,可做集合的运算{'b'}5.4.2映射项5.4.2映射项不过,若在迭代过程中对字典进行修改动作,例如刪除、新增、更新,都可能影响字典内键值配对的顺序,连带影响迭代的结果,甚至可能出错;反之,只要不作修改,方法keys()、values()、items()返回的映射浏览对象就能一致反映出正确的字典内容与顺序。>>>d={'a':1,'b':2,'c':3}>>>list(d.keys()) #虽然我们不知道字典内键值配对的存放顺序,['a','c','b']>>>list(d.values()) #但keys()、values()、items()返回的映射浏览对象,[1,3,2] #仍会呈现出一致的顺序,>>>list(d.items()) #只要不去修改原字典的话[('a',1),('c',3),('b',2)]>>>dict(zip(d.keys(),d.values())) #因此,这一行程序代码才能产生出{'a':1,'c':3,'b':2} #与原字典相同内容的新字典对象5.4.2映射项>>>pairs=zip(d.values(),d.keys()) #这么做可顛倒键与值>>>list(pairs)[(1,'a'),(3,'c'),(2,'b')]>>>di=dict(zip(d.values(),d.keys()))#若字典的值都不相同且有杂凑值>>>di#便可得到顛倒键值关系的字典{1:'a',2:'b',3:'c'}5.4.2映射项5.4.2映射项至此,读者应该了解字典与集合的基本用法与概念,明白与列表有何不同,并且知道相关的抽象类型与接口。谢谢大家第6章序列与迭代Python程序设计第6章序列与迭代Python的序列类型包括列表、元组、字符串、文件等,存取这些抽象类型时,核心的概念就是迭代,一个接着一个地逐一操作。这种形式的数据不胜枚举,所以Python提供了充分的支持。以列表为中心,围绕着许多相关类型、内置函数、方法、列表生成式等,以方便我们在程序里处理序列形式的数据。事实上,顺序、条件判断、再加上迭代(重复)的执行形式,就组成了基本的程序流程。类型与对象1迭代的概念4抽象数据类型2元素的访问3列表生成式5目录6.1类型与对象类型即对象命名空间6.1

类型与对象我们已经熟悉类型、对象、名称(标识符)之间的关系了,也学会了不少类型,包括整数(int)、浮点数(float)、列表(list)、字符串(str)等等,甚至函数(function)、模块(module)也都是类型。例如:>>>a=22;b=4+5j;c='hi';d=(169,44)>>>e=['Amy',a,d]>>>defsq(x):returnx*x...>>>importmath这里的a、b、c、d、e、sq、math都是名称,分别指向某种对象,而对象必定属于某种类型,规定了该对象的接口与用法;我们可用内置函数type查出某对象的类型,type()会显示<class'XXX'>。>>>type(a);type(c);type(e) #查询对象的类型<class'int'><class'str'><class'list'>>>>type(sq);type(math) #模块与函数也是对象,<class'function'>#也有类型<class'module'>6.1

类型与对象6.1.1类型即对象所谓“类型(type)”在程序里也是以对象形式存在的,使用type()得到代表类型的对象后,可以比较判断两个对象的类型是否相同;有些基本类型直接输入其名称便可得到代表该类型的对象,有些类型则被放在某个模块内。>>>it=type(a) #类型是对象,也能命名>>>it<class'int'>>>>type(a)istype(99) #比较类型是否为同一个True>>>int,float,complex #基本的类型,直接取用(<type'int'>,<type'float'>,<type'complex’>)>>>tuple,list,str(<class'tuple'>,<class'list'>,<class'str'>)>>>type(e)islist #直接取用类型作判断True>>>importtypes #有些类型被放在模块内>>>type(sq)istypes.FunctionTypeTrue>>>type(math)istypes.ModuleTypeTrue6.1.1类型即对象6.1.1类型即对象整数对象的类型是int,类型既然是对象,那么它也有类型,类型对象的类型是type。>>>a=3 #名称a指向int对象,值为3>>>type(a) #其类型是int<class'int'>>>>at=type(a)#名称at指向type对象>>>type(at) #其类型是type<class'type'>表5-1列出了Python的部分类型。6.1.1类型即对象表6-1代表类型的对象位于何处(部分)6.1.1类型即对象每个对象的类型各有其独特的性质,例如int对象不可变,list对象是可变的容器,函数对象可被调用。在需要区别的时候,我们会称呼某类型建立的对象为“实体”,以避免混淆。使用内置函数instance可判断对象是否为某类型的实体。>>>callable(sq),callable(math)#sq是函数,可被调用(True,False)>>>callable(list),callable(int) #list、int不仅是类型的名称,也可被调用(True,True)>>>isinstance(sq,types.FunctionType)#sq是函数类型的实体True>>>isinstance(d,tuple) #d是tuple类型的实体True6.1.2命名空间名称会指向对象,命名空间是名称存在的地方,模块、函数、类型、实体(对象)都具备命名空间的功能。importmathpi=math.pic=complex(3,4)defcircle(r)area=pi*r*rreturnarea在上述代码中,全局范围的命名空间里含有名称math、pi、circle,其中math是个模块、pi与c都是对象,都具有命名空间的功能,例如math里的sqrt、floor、trunc都指向函数,c对象里面的real与imag则指向浮点数。6.1.2命名空间函数具有命名空间的功能,但上述程序只“定义”了函数circle但尚未调用,也就是说,此时尚未产生circle的局部命名空间,更别提名称r是否存在了。调用函数才会建立出局部(函数)范围的命名空间,里面会放着r、area。一旦函数结束(返回),该局部(函数)范围的命名空间也会消失,这里说的是“名称”消失了,所以在函数之外将无法使用函数内的名称area,不过函数返回的对象若还有名称指向它,就仍会继续存活。当在程序里取用某名称时,如果位于函数内,会先到局部(函数)范围命名空间寻找,找不到会去全局范围命名空间寻找,若再找不到会去存放内置名称命名空间(由模块__builtins__提供)寻找。在局部与全局之间还有一个“外围函数”的范围。

搜寻名称时的顺序规则5.2抽象数据类型序列的概念迭代器共同接口6.2

抽象数据类型前面介绍类型与对象时,一个对象看似都只有一个类型,例如3是int,[0,1,2]是list,但实际情况会更复杂。例如list、tuple、str都是序列类型,那么“序列”的类型在哪里呢?6.2.1序列的概念序列是个抽象类型,list、tuple、str都是实际类型,可产生出对象,但我们不能用序列类型产生对象,只能说类型为list(或tuple、str)的对象符合序列定义的操作接口。某些类型拥有相同的接口,例如list拥有“长度”的概念,而tuple、str也有,但int、float没有。此时可把这部分的接口提出来,放在抽象类型里,让list、tuple、str遵循并符合此抽象类型的描述。序列相关的抽象类型层级6.2.1序列的概念“长度或大小”的观念由抽象类型Sized定义,所以符合此接口的类型对象必须含有方法__len__,从其名称前后有两个下划线便可猜想,这是个具有特定意义、预先规定好名称的方法,会被运算符或内置函数取用。而抽象类型Container定义了“某东西是否在里面”的观念,由__contains__负责,Sequence(序列)抽象类型“继承”了Sized与Container,也就是说符合其定义的接口,更进一步定义了“取出元素”(__getitem__)、“某元素的索引”(index)等接口,而MutableSequence(可变序列)抽象类型又再以Sequence为基础、加入可修改内容的接口,诸如“移除”、“附加”、等等。这些抽象类型被放在模块collections.abc里,可以使用内置函数isinstance得知实体对象是否符合某类型(与抽象类型),而内置函数issubclass可得知类型与类型之间的继承关系(包括抽象类型)。6.2.2迭代器再来看可迭代项(Iterable)与迭代器(Iterator)。抽象类型Iterable定义了“建立Iterator”的接口,而Iterator定义了“逐步取出下一个元素”的接口,下面是个简单的实例。>>>li=[30,41,52] #list对象,也是可迭代项>>>lit=iter(li) #以内置函数iter取得迭代器>>>lit #名称lit指向这个迭代器<list_iteratorobjectat0x00BD6C90>>>>next(lit) #以内置函数next可取得下一个元素30>>>next(lit)41>>>next(lit)52>>>next(lit) #没有下一个了,发生错误Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>StopIteration6.2.2迭代器6.2.3共同接口Python运用各种类型(包括抽象类型)来规范对象的接口,根据类型定义出一套存取方式,list、tuple、str都符合序列抽象类型,list还符合可变序列抽象类型。表5-2列出这些类型都支持的操作动作,表5-3列出可变的操作动作。除了list、tuple、str之外,还有其他类型继承自序列抽象类型。字符串str除了这里列出的操作动作,还有很多跟“文本”相关的方法。在表5-2里,s与t代表某一种序列类型的实体对象,i、j、k则都是整数。6.2.3共同接口表6-2序列类型共同的操作动作6.2.3共同接口在表6-3里,s代表可变序列类型的实体对象,t是可迭代项。表6-3可变序列类型共同的操作动作索引、切片序列比较运算符“+”与“*”序列类型的方法浅复制与深复制6.3元素的访问6.3

元素的访问序列类型是一种容器(数据结构),里面的内容称为元素,它们的存取方式介绍如下。6.3.1序列元素索引以方括号“[]”的索引方式来指定想存取的元素,索引值从0开始计数,所以长度为n的序列,有效合法的索引值会是0到n-1。>>>li=[30,41,52,63,74,85]>>>li[0] #取得索引值0之处的对象30>>>li[5],li[len(li)-1] #以内置函数len得知长度(85,85)>>>li[99] #超过了,发生错误Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>IndexError:listindexoutofrange#索引错误:列表索引值超过界限>>>s='hellopython' #字符串也能使用索引>>>s[0],s[6]('h','p')>>>s[0],s[6],s[len(s)-1]('h','p','n')6.3.1序列元素索引6.3.1序列元素索引任何序列类型,例如list、tuple、str等都可以使用索引存取方式。索引值也可以是负数,如果是-i(假设i为正数),会被当作“长度-i”。假设序列对象s长度是n,那么s[-i]将等同于s[len(s)-i]。负索引值从-1(最后的元素)到-n(最前面的元素)。>>>li=[30,41,52,63,74,85] #长度是6>>>li[-1],li[len(li)-1] #此处[-1]等同于[6-1](85,85) #以[-1]存取最后的元素,比len()方便>>>li[-2],li[-5],li[-6],li[-len(li)](74,41,30,30)>>>li[0],li[-0] #注意,-0还是0,不是6–0(30,30)>>>li[-7] #超过了,发生错误Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>IndexError:listindexoutofrange6.3.1序列元素索引6.3.1序列元素索引如果是可变序列类型,例如list,就能以赋值语句修改其内容,即让该索引值转而指向新对象。>>>li=[30,41,52,63,74,85]>>>li[0]='Amy' #修改第0个>>>li[len(li)

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论