Python编程基础之六映射(dict)和集合类型(set)

一、简介

       字典是Python语言中唯一的映射类型。映射类型对象里哈希值(键) 和指向的对象(值)是一对多的关系。set称做由不同的元素组成的集合,集合对象是一组无序排列的可哈希的值,集合成员可以做字典中的键。

二、详解

1、映射类型:字典

        一个字典对象是可变的,它是一个容器类型,能存储任意个数的Python对象,其中也包括其他容器类型。字典类型和序列类型容器类(列表、元组)的区别是存储和访问数据的方式不同。序列类型只用数字类型的键(从序列的开始起按数值顺序索引),映射类型可以用其他对象类型做键。一般最常见的是用字符串做键(keys)。和序列类型的键不同,映射类型的键(keys)直接或间接地和存储的
数据值相关联。但因为在映射类型中,不再用"序列化排序"的键(keys),所以映射类型中的数据是无序排列的。
         显然,这并不影响我们使用映射类型,因为映射类型不要求用数字值做索引以从一个容器中获取对应的数据项。可以用键(key)直接 "映射" 到值,这就是为什么叫映射类型(“mapping type”)的原因。映射类型通常被称做哈希表的原因是字典对象就是哈希类型的。字典是Python中最强大的数据类型之一。
         哈希表是一种数据结构,它按照我们所要求的去工作。哈希表中存储的每一条数据,叫做一个值(value),是根据与它相关的一个被称作为键(key)的数据项进行存储的。键和值合在一起被称为“键-值 对”(key-value pairs)。 哈希表的算法是获取键,对键执行一个叫做哈希函数的操作,并根据计算的结果,选择在数据结构的某个地址中来存储你的值。任何一个值存储的地址皆取决于它的键。正因为这种随意性,哈希表中的值是没有顺序的。你拥有的是一个无序的数据集。所能获得的有序集合只能是字典中的键的集合或者值的集合。方法 Keys() 或 values() 返回一个列表,该列表是可排序的。 还可以用 items()方法得到包含键、值对的元组的列表来排序。由于字典本身是哈希的,所以是无序的。哈希表一般有很好的性能,因为用键查询相当快。
        Python中一个字典条目的语法格式是,键:值。 而且多条字典条目被包含在( { } ) 里。
(1)如何创建字典和给字典赋值
         创建字典只需要把字典赋值给一个变量,不管这个字典是否包含元素。也可以用工厂方法 dict() 来创建字典。还可以用一个很方便的内建方法fromkeys()来创建一个"默认"字典, 字典中元素具有相同的值 (如果没有给出, 默认为 None)。

>>> dict1 = {}
>>> dict2 = {'name': 'earth', 'port': 80}
>>> dict1, dict2
({}, {'name': 'earth', 'port': 80})
>>> fdict = dict((['x', 1], ['y', 2]))
>>> fdict
{'y': 2, 'x': 1}
>>> ddict = {}.fromkeys(('x', 'y'), -1)
>>> ddict
{'y': -1, 'x': -1}
>>> edict = {}.fromkeys(('foo', 'bar'))
>>> edict
{'foo': None, 'bar': None}
(2)如何访问字典中的值
        要想遍历一个字典(一般用键),只需循环查看它的键。从Python 2.2开始,可以不必再用keys()方法获取供循环使用的键值列表了。 可以用迭代器来轻松地访问类序列对象(sequence-like objects),比如字典和文件。只需要用字典的名字就可以在 for 循环里遍历字典。
>>> dict2 = {'name': 'earth', 'port': 80}
>>> for key in dict2.keys():
...     print 'key=%s, value=%s' % (key, dict2[key])
... 
key=name, value=earth
key=port, value=80
>>> for key in dict2:     
...     print 'key=%s, value=%s' % (key, dict2[key])
... 
key=name, value=earth
key=port, value=80
         要得到字典中某个元素的值,可以用字典键加上中括号来得到,如dict2['name']。如果想访问该字典中的一个数据元素,而它在这个字典中没有对应的键,将会产生一个错误。而检查一个字典中是否有某个键的方法是用字典的has_key()方法,或者另一种比较好的方法就是使用in或not in操作符。
>>> 'server' in dict2    #或 dict2.has_key('server')
False
>>> 'name' in dict2      #或 dict2.has_key('name')
True
        为什么在执行中字典中的键不允许被改变呢? 比方说,你创建了一个字典,字典中包含一个元素(一个键和一个值)。可能是由于某个变量的改变导致键发生了改变。这时候你如果用原来的键来取出字典里的数据,会得到 KeyError(因为键的值已经改变了),现在你没办法从字典中获取该值了,因为键本身的值发生了变化。由于上面的原因,字典中的键必须是可哈希的,所以数字和字符串可以作为字典中的键,而列表和其他字典不行。
(3)如何更新字典
        字典的修改:添加一个新数据项或新元素(即一个键-值对);修改一个已存在的数据项或删除一个已存在的数据项。
        如果字典中该键已经存在,则字典中该键对应的值将被新值替代。print语句展示了另一种在字典中使用字符串格式符( %)的方法。用字典参数可以简化print语句,因为只须用到一次该字典的名字,而不用在每个元素出现的时候都用元组参数表示。也可以用内建方法update()将整个字典的内容添加到另一个字典。
>>> dict2['name'] = 'venus' # 更新已有条目
>>> dict2['arch'] = 'sunos5'# 增加新条目
>>> dict2
{'arch': 'sunos5', 'name': 'venus', 'port': 80}
>>> print 'host %(name)s is running on port %(port)d' %dict2
host venus is running on port 80
(4)如何删除字典元素和字典
        删除整个字典的操作不常见。通常删除字典中的单个元素或是清除整个字典的内容。但是如果真想删除一个字典,用del语句。
>>> del dict2['name']  #删除键为“name”的条目,dict2.pop('name')删除并返回键为“name”的条目
>>> dict2.clear()      #删除 dict2 中所有的条目
>>> del dict2          #删除整个 dict2 字典

2、映射类型操作符

(1)标准类型操作符

        字典可以和所有的标准类型操作符一起工作,但却不支持像拼接(concatenation)和重复(repetition)这样的操作。这些操作对序列有意义,可对映射类型行不通。
        字典是如何比较的呢? 与列表和元组一样,这个过程比数字和字符串的比较更复杂些。
(2)映射类型操作符
字典的键查找操作符([ ])
        键查找操作符是唯一仅用于字典类型的操作符,它和序列类型里单一元素的切片(slice)操作符很相象。对序列类型来说,用索引做唯一参数或下标(subscript)以获取一个序列中某个元素的值。而对字典类型来说,是用键(key)查询字典中的元素,所以键是参数(argument),而不是一个索引(index)。键查找操作符既可以用于给字典赋值,也可以用于从字典中取值。
(键)成员关系操作( in ,not in)
       从Python 2.2起,可以不用has_key()方法,而用in和not in操作符来检查某个键是否存在于字典中。

3、映射类型的内建函数和工厂函数

(1)标准类型函数[type()、str()和 cmp()]
        对一个字典调用type()工厂方法,会返回字典类型:“<type 'dict'>”。调用str()工厂方法将返回该字典的字符串表示形式。
        字典是通过这样的算法来比较的:首先是字典的大小,然后是键,最后是值。可是用cmp()做字典的比较一般不是很有用。

        算法按照以下的顺序:
首先比较字典长度
        如果字典的长度不同,那么用cmp(dict1, dict2)比较大小时,如果字典dict1比dict2长,cmp()返回正值,如果dict2比dict1长,则返回负值。也就是说字典中的键的个数越多,这个字典就越大,即:len(dict1) > len(dict2) ==> dict1 > dict2。
其次比较字典的键
        如果两个字典的长度相同,那就按字典的键比较。键比较的顺序和keys()方法返回键的顺序相同。(注意: 相同的键会映射到哈希表的同一位置,这保证了对字典键的检查的一致性 )。这时,如果两个字典的键不匹配时,对这两个(不匹配的键)直接进行比较。当dict1中第一个不同的键大于dict2中第一个不同的键,cmp()会返回正值。
然后比较字典的值
       如果两个字典的长度相同而且它们的键也完全匹配,则用字典中每个相同的键所对应的值进行比较。一旦出现不匹配的值,就对
这两个值进行直接比较。若dict1比dict2中相同的键所对应的值大,cmp()会返回正值。
完全匹配
       到此为止,即每个字典有相同的长度、相同的键、每个键也对应相同的值,则字典完全匹配,返回 0 值。
(2)映射类型相关的函数
 dict()

       工厂函数被用来创建字典如果不提供参数会生成空字典当容器类型对象做为一个参数传递给方法 dict(),如果参数是可以迭代的即一个序列或是一个迭代器或是一个支持迭代的对象那每个可迭代的元素必须成对出现。在每个值对中,第一个元素是字典的键、第二个元素是字典中的值。

>>> dict(zip(('x', 'y'), (1, 2)))
{'y': 2, 'x': 1}
>>> dict([['x', 1], ['y', 2]])
{'y': 2, 'x': 1}
>>> dict([('xy'[i-1], i) for i in range(1,3)])
{'y': 2, 'x': 1}
        如果输入参数是(另)一个映射对象,比如一个字典对象,对其调用dict()会从存在的字典里复制内容来生成新的字典。新生成的字典是原来字典对象的浅复制版本,它与用字典的内建方法copy()生成的字典对象是一样的。但是从已存在的字典生成新的字典速度比用copy()方法慢,推荐使用copy()。
len()
       内建函数len()很灵活,它可用在序列、映射类型和集合上。对字典调用 len(),它会返回所有元素(键-值对)的数目。
hash()
       内建函数hash()本身并不是为字典设计的方法,但它可以判断某个对象是否可以做一个字典的键。将一个对象作为参数传递给 hash(),会返回这个对象的哈希值。 只有这个对象是可哈希的,才可作为字典的键 (函数的返回值是整数,不产生错误或异常)。如果用比较操作符来比较两个数值,发现它们是相等的,那么即使二者的数据类型不同, 它们也会得到相同的哈希值。如果非可哈希类型作为参数传递给hash()方法,会产生TypeError错误,因此如果使用这样的对象作为键给字典赋值时会出错。

4、映射类型内建方法

       字典提供了大量方法,基本的字典方法关注他们的键和值。它们有keys()方法,该 方法很有用,返回一个列表,包含字典中所有的键,可以与 for 循环一起使用来获取字典中的值;values()方法,返回一个列表,包含字典中所有的值;items(),返回一个包含所有(键--值)元组的列表。这些方法在不按任何顺序遍历字典的键或值时很有用。
         keys()返回的元素是没有顺序的(和哈希表中的键(keys)一样),若希望它们能按某种方式排序,先调用字典的 keys()方法获得键的列表,然后调用列表的sortd()方法得到一个有序可遍历的列表。
>>> for eachKey in sorted(dict2):
...     print 'dict2 key', eachKey, 'has value', dict2[eachKey]
... 
dict2 key name has value earth
dict2 key port has value 80
       update()方法可以用来将一个字典的内容添加到另外一个字典中。字典中原有的键如果与新添加的键重复,那么重复键所对应的原有条目的值将被新键所对应的值所覆盖。原来不存在的条目则被添加到字典中。clear()方法可以用来删除字典中的所有的条目。

>>> dict2= {'host':'earth', 'port':80}
>>> dict3= {'host':'venus', 'server':'http'}
>>> dict2.update(dict3)
>>> dict2
{'host': 'venus', 'port': 80, 'server': 'http'}
        copy() 方法返回一个字典的副本,注意这只是浅复制。get()方法和键查找(key-lookup)操作符( [ ] )相似,不同的是它允许为不存在的键提供默认值。如果该键不存在,也未给出它的默认值,则返回 None。此方法比采用键查找(key-lookup)更灵活,因为你不必担心因键不存在而引发异常。
>>> dict2= {'host':'earth', 'port':80}
>>> dict2.get('host')
'earth'
>>> dict2.get('server')
>>> dict2.get('server', 'no key')
'no key'
        setdefault()是自2.0才有的内建方法,使得代码更加简洁。它实现了常用的语法: 检查字典中是否含有某键。 如果字典中这个键存在,你可以取到它的值。 如果所找的键在字典中不存在,你可以给这个键赋默认值并返回此值。fromkeys()创建并返回一个新字典,以seq中的元素做该字典的键,val做该字典中所有键对应的初始值(如果不提供此值,则默认为 None)。
>>> myDict = {'host': 'earth', 'port': 80}
>>> myDict.items()
[('host', 'earth'), ('port', 80)]
>>> myDict.setdefault('port', 8080)
80
>>> myDict.setdefault('prot', 'tcp')
'tcp'
>>> myDict.items()
[('host', 'earth'), ('prot', 'tcp'), ('port', 80)]
>>> {}.fromkeys(('love', 'honor'), True)
{'love': True, 'honor': True}
          keys()、items()和values()方法的返回值都是列表。数据集如果很大会导致很难处理,这也正是 iteritems()、iterkeys()和itervalues()方法被添加到Python 2.2的主要原因。这些函数与返回列表的对应方法相似,只是它们返回惰性赋值的迭代器,所以节省内存。未来的Python版本中,甚至会更灵活,那时这些方法将会返回强大的对象,暂叫做视图(views)。视图(views)是访问容器对象的接口集。举例来说,可以从一个视图(views)中删除某个字典的键,从而改变某个字典。

5、字典的键

       字典中的值没有任何限制, 可以是任意Python对象,即从标准对象到用户自定义对象皆可,但是字典中的键是有类型限制的。
(1)不允许一个键对应多个值
        必须明确一条原则:每个键只能对应一个项。也就是说:一键对应多个值是不允许的(像列表、元组和其他字典这样的容器对象是可以的)。 当有键发生冲突(即字典键重复赋值),取最后(最近)的赋值。Python并不会因字典中的键存在冲突而产生一个错误它不会检查键的冲突是因为如果真这样做的话在每个键-值对赋值的时候都会做检查这将会占用一定量的内存

>>> dict1 = {'foo':789, 'foo': 'xyz'}
>>> dict1
{'foo': 'xyz'}
>>> dict1['foo'] = 123
>>> dict1
{'foo': 123}
(2)键必须是可哈希的
       大多数Python对象可以作为键,但它们必须是可哈希的对象。像列表和字典这样的可变类型,由于它们不是可哈希的,所以不能作为键。
       所有不可变的类型都是可哈希的,因此它们都可以做为字典的键。要说明的是:值相等的数字表示相同的键,即整型数字1和浮点数1.0的哈希值是相同的,它们是相同的键。
       同时,也有一些可变对象(很少)是可哈希的,它们可以做字典的键,但很少见。举一个例子,一个实现了__hash__() 特殊方法的类。因为__hash__()方法返回一个整数,所以仍然是用不可变的值(做字典的键)。
       为什么键必须是可哈希的?解释器调用哈希函数,根据字典中键的值来计算存储你的数据的位置。如果键是可变对象,它的值可改变。如果键发生变化,哈希函数会映射到不同的地址来存储数据。如果这样的情况发生,哈希函数就不可能可靠地存储或获取相关的数据。选择可哈希的键的原因就是因为它们的值不能改变。
        数字和字符串可以被用做字典的键,元组是不可变的但也可能不是一成不变的,因此用元组做有效的键必须要加限制:若元
组中只包括像数字和字符串这样的不可变参数,才可以作为字典中有效的键。

6、集合类型

        数学上,,把set称做由不同的元素组成的集合,集合(set)的成员通常被称做集合元素(set elements)。Python把这个概念引入到它的集合类型对象里。集合对象是一组无序排列的可哈希的值,集合成员可以做字典中的键。数学集合转为Python的集合对象很有效,集合关系测试和union、intersection等操作符在Python里也同样如我们所预想地那样工作。
        和其他容器类型一样,集合支持用in和not in操作符检查成员,由len()内建函数得到集合的基数(大小), 用 for 循环迭代集合的成员。但是因为集合本身是无序的,不可以为集合创建索引或执行切片(slice)操作,也没有键(keys)可用来获取集合中元素的值。
        集合(sets)有两种不同的类型:可变集合(set)和不可变集合(frozenset)。对可变集合(set),可以添加和删除元素,对不可变集合(frozenset)则不允许这样做。注意:可变集合(set)不是可哈希的,因此既不能用做字典的键也不能做其他集合中的元素。不可变集合
(frozenset)则正好相反,即它们有哈希值,能被用做字典的键或是作为集合中的一个成员。
         集合操作符和关系符号:


(1)如何创建集合类型和给集合赋值
        集合与列表( [ ] )和字典( { } ) 不同,没有特别的语法格式。列表和字典可以分别用他们自己的工厂方法 list() 和 dict() 创建,这也是集合被创建的唯一方法:用集合的工厂方法set()和frozenset()。

>>> s = set('cheeseshop')
>>> s
set(['c', 'e', 'h', 'o', 'p', 's'])
>>> t = frozenset('bookshop')
>>> t
frozenset(['b', 'h', 'k', 'o', 'p', 's'])
>>> len(s), len(t)
(6, 6)
>>> s == t
False
(2)如何访问集合中的值
       可以遍历查看集合成员或检查某项元素是否是一个集合中的成员。

>>> 'k' in t
True
>>> for i in s:
...     print i
... 
c
e
h
o
p
s
(3)如何更新集合
         用各种集合内建的方法和操作符添加和删除集合的成员。只有可变集合能被修改,试图修改不可变集合会引发异常。
>>> s.add('z')
>>> s
set(['c', 'e', 'h', 'o', 'p', 's', 'z'])
>>> s.update('pypi')
>>> s
set(['c', 'e', 'i', 'h', 'o', 'p', 's', 'y', 'z'])
>>> s.remove('z')
>>> s -= set('pypi')
>>> s
set(['c', 'e', 'h', 'o', 's'])
(4)如何删除集合中的成员和集合
        删除集合成员remove()内建方法。删除集合本身,可以像删除任何Python对象一样,令集合超出它的作用范围或调用del将它们直接清除出当前的名字空间。如果它的引用计数为零,也会被标记以便被垃圾回收。如del s。

7、集合类型操作符

(1)标准类型操作符(所有的集合类型)
成员关系 (in, not in)
        就序列而言,Python中的in和not in操作符决定某个元素是否是一个集合中的成员。
集合等价/不等价
        等价/不等价被用于在相同或不同的集合之间做比较。两个集合相等是指,对每个集合而言,当且仅当其中一个集合中的每个成员同时也是另一个集合中的成员。也可以说每个集合必须是另一个集合的一个子集, 即s <= t 和s>= t 的值均为真(True),或(s <= t and s>= t) 的值为真(True)。集合等价/不等价与集合的类型或集合成员的顺序无关,只与集合的元素有关。
子集/超集
        set用Python的比较操作符检查某集合是否是其他集合的超集或子集。“小于”符号(<、<=)用来判断子集,“大于”符号(>、 >= )用来判断超集。“小于” 和 “大于”意味着两个集合在比较时不能相等。等于号允许非严格定义的子集和超集。
        set支持严格( < )子集和非严格 ( <= ) 子集,也支持严格( > )超集和非严格 ( >= )超集。只有当第一个集合是第二个集合的严格子集时,我们才称第一个集合“小于”第二个集合。同理,只有当第一个集合是第二个集合的严格超集时,我们才称第一个集合“大于”第二个集合。

(2)集合类型操作符(所有的集合类型)
联合( | )
        联合(union)操作和集合的 OR(又称可兼析取(inclusive disjunction))其实是等价的,两个集合的联合是一个新集合,该集合中的每个元素都至少是其中一个集合的成员,即属于两个集合其中之一的成员。联合符号有一个等价的方法:union()。
交集( & )
       可以把交集操作比做集合的AND(或合取)操作。两个集合的交集是一个新集合,该集合中的每个元素同时是两个集合中的成员,即属于两个集合的成员。交集符号有一个等价的方法:intersection()。
差补/相对补集( – )
        两个集合(s 和 t)的差补或相对补集是指一个集合 C,该集合中的元素,只属于集合 s,而不属于集合 t。差符号有一个等价的方法:difference()。
对称差分( ^ )
       和其他的布尔集合操作相似,对称差分是集合的XOR(又称“异或” (exclusive disjunction))。两个集合(s 和 t)的对称差分是指另外一个集合C,该集合中的元素,只能是属于集合 s 或者集合t的成员,不能同时属于两个集合。对称差分有一个等价的方法:symmetric_difference()。
混合集合类型操作
        如果左右两个操作数的类型相同,既都是可变集合或不可变集合,则所产生的结果类型是相同的。但如果左右两个操作数的类型不相同(左操作数是 set,右操作数是 frozenset,或相反情况),则所产生的结果类型与左操作数的类型相同 。

       注意:加号不是集合类型的运算符。

>>> t | s
frozenset(['c', 'b', 'e', 'h', 'k', 'o', 'p', 's'])
>>> t ^ s
frozenset(['c', 'b', 'e', 'k', 'p'])
>>> s | t
set(['c', 'b', 'e', 'h', 'k', 'o', 'p', 's'])
>>> s ^ t
set(['p', 'b', 'e', 'k', 'c'])
(3)集合类型操作符(仅适用于可变集合)
(Union) Update ( |= )
       这个更新方法从已存在的集合中添加(可能多个)成员,此方法和update()等价。
>>> s = set('cheeseshop')
>>> s |= set('pypi')
>>> s
set(['c', 'e', 'i', 'h', 'o', 'p', 's', 'y'])
保留/交集更新( &= )
       保留(或交集更新)操作保留与其他集合的共有成员,此方法和 intersection_update()等价。
差更新 ( –= )
       对集合s和t进行差更新操作s-=t,差更新操作会返回一个集合,该集合中的成员是集合s去除掉集合t中元素后剩余的元素。此方法
和difference_update()等价。
对称差分更新( ^= )
        对集合s和t进行对称差分更新操作(s^=t),对称差分更新操作会返回一个集合,该集合中的成员仅是原集合s或仅是另一集合t中的成员。此方法和symmetric_difference_update()等价。

8、集合内建函数和内建方法

(1)标准类型函数
       len():把集合作为参数传递给内建函数 len(),返回集合的基数(或元素的个数)。
(2)集合类型工厂函数
       set()和 frozenset()工厂函数分别用来生成可变和不可变的集合。如果不提供任何参数,默认会生成空集合。如果提供一个参数,则该参数必须是可迭代的,即一个序列或迭代器或支持迭代的一个对象,例如一个文件或一个字典。
(3)方法(所有的集合方法)
方法名称                                      操作
s.issubset(t)                  如果s是t的子集,则返回True,否则返回False
s.issuperset(t)               如果t是s的超集,则返回True,否则返回False
s.union(t)                       返回一个新集合,该集合是s和t的并集
s.intersection(t)            返回一个新集合,该集合是s和t的交集
s.difference(t)               返回一个新集合,该集合是 s 的成员,但不是 t 的成员
s.symmetric_difference(t)     返回一个新集合,该集合是s或t的成员,但不是s和t共有的成员
s.copy()                         返回一个新集合,它是集合s的浅复制
       内建方法copy() 没有等价的操作符。和同名的字典方法一样,copy()方法比用像set()、frozenset()或dict()这样的工厂方法复制对象的副本要快。
(4)方法(仅适用于可变集合)

可变集合类型的方法:


       新的方法有 add()、remove()、 discard()、 pop()、clear(),须是可哈希的。

三、总结

(1)请不要用 dict, list,file, bool, str, input, len这样的内建对象名字作为变量的标识符,不要以内建模块的名称作为文件名如copy.py。
(2)字典和集合的操作符、函数和方法的综合总结表格没列出,因涉及内容太多。
(3)若有不足,请留言,在此先感谢!
  
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乌托邦2号

博文不易,支持的请给予小小打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值