本节是第五讲的第七小节,上节为大家介绍了Python语言常用的集合类型,本节将为大家介绍另一种组合数据类型-字典。
映射类型(Mapping Types)
映射类型是一种支持成员关系操作符(in)与对象大小函数(len())的数据类型,并且也是可以迭代的。映射是键-值数据项的组合,并提供了存取数据项及其键、值的方法。进行迭代时,映射类型以任意顺序提供其数据项。Python 3.0支持两种无序的映射类型一内置的dict类型与以及标准库中的collections.defaultdict类型。Python 3.1 中引入了一种新的、有序的映射类型collections.OrderedDict,该类型是一个字典,与内置的dict具有相同的方法和属性(也即相同的API),但在存储数据项时以插入顺序进行。在差别无关紧要时,我们将使用术语dictionary来引用其中的任一种类型。
只有可哈希运算的对象可用作字典的键,因此,固定的数据类型(比如float、 frozenset、int、str以及tuple)都可以用作字典的键,可变的数据类型(比如dict、list 与set)则不能。另一方面,每个键相关联的值实际上是对象引用,可以引用任意类型的对象,包括数字、字符串、列表、集合、字典、函数等。
字典类型可以使用标准的比较操作符(<、<=、==、!=、>=、>)进行比较,这种比较实际是逐项进行的(对嵌套项,比如字典内的元组或字典,递归进行处理)。可以认为,对字典而言,唯一有意义的比较操作符是==与!=。
字典(Dictionaries)
dict是一种无序的组合数据类型,其中包含0个或多个键-值对。其中,键是指向可哈希运算的对象的对象引用,值是可以指向任意类型对象的对象引用。字典是可变的,因此我们可以很容易地对其进行数据项的添加或移除操作。由于字典是无序的, 因此,索引位置对其而言是无意义的,从而也不能进行分片或按步距分片。
dict数据类型可以作为函数调用:dict()。不带参数调用该函数时,将返回一个空字典;带一个映射类型参数时,将返回以该参数为基础的字典。比如,该参数本身为字典,则返回该参数的浅拷贝。使用序列型参数也是可能的,前提是序列中的每个数据项本身是一个包含两个对象的序列,其中第一个用作键,第二个用作值。还有一种可替代的方案是,对键为有效的Python标识符的字典((key,value)项的无序组合), 可以使用关键字参数,其中键作为关键字,值作为键的值。字典也可以使用花括号创建——空的花括号{}会创建空字典,非空的花括号必须包含一个或多个逗号分隔的项, 其中每一项都包含一个键、一个字面意义的冒号以及一个值。另一种创建字典的方式是使用字典内涵——本小节稍后将讲解的一个主题。
#下面给出一些实例,展示了各种语法——这些语句产生的是同样的字典:
d1= dict({"id": 1948,"name": "Washer“, "size": 3})
d2 = dict(id=1948, name="Washer", size=3)
d3 = dict([("id",1948), ("name", "Washer"), ("size", 3)])
d4 = dict(zip(("id","name", “size”),(1948, "Washer", 3)))
d5 = {”id”:1948, "name": “Washer”,”size”:3}
字典d1是使用字典字面值创建的,字典d2是使用关键字参数创建的,字典d3 与d4是从序列中创建的,字典d5是从字典字面值创建的。用于创建字典d4的内置 zip()函数返回一个元组列表,其中第一个元组包含的是zip()函数的iterable参数的每一 项,第二个元组包含的是每一项的具体值,依此类推。如果键是有效的标识符,那么关键字参数语法(用于创建字典d2)通常是最紧凑与最方便的。
如图给出了使用如下代码段创建的字典:
d = {"root":18, "blue": [75, "R", 2], 21: "venus", -14: None,"mars": "rover", (4, 11): 18, 0: 45}
字典的键是独一无二的,因此,如果向字典中添加一个键-值项,并且该键与字典 中现存的某个键相同,那么实际的效果是使用新值替换该键的现值。方括号用于存取单独的值——比如,按照图中展示的字典,d["root”]返回18, d[21]返回字符串 "venus",而 d[91 ]会导致 KeyError 异常。 方括号也可以用于添加或删除字典项,要添加一个项,可以使用操作符=,比如, d["X"] = 59;要删除一个项,可以使用del语句——比如,del d["mars"]将从字典中删除键为“mars"的项,或者产生一个KeyError异常(如果没有哪个项的键为该数据)。
语法 描述
d.clear() 从dict d中移除所有项
d.copy() 返回dict d的浅拷贝
d.fromkeys(s, v) 返回一个dict,该字典的键为序列s中的项,值为None或v (如果给定了参数V)
d.get(k) 返回键k相关联的值,如果k不在dict d中就返回None
d.get(k, v) 返回键k相关联的值,如果k不在dict d中就返回V
d.items() 返回dict d中所有(key, value)对的视图
d.keys() 返回dict d中所有键的视图
d.pop(k) 返回键k相关联的值,并移除键为k的项,如果k不包含在d中就产生KeyError异常
d.pop(k, v) 返回键k相关联的值,并移除键为k的项,如果k不包含在d中就返回V
d.popitem() 返回并移除dict d中一个任意的(key, value)对,如果d为空就产生KeyError异常
d.setdefault(k,v) 与dict.get()方法一样,不同之处在于,如果k没有包含在dict d中就插入一个键为k的新项,其值为None或v (如果给定了参数v)
d.update(a) 将a中每个尚未包含在dict d中的(key, value)对添加到d,对同时包含在d与a中的每个键,使用a中对应的值替换d中对应的值——a可以是字典,也可以是(key, value)对的一 个iterable,或关键字参数
d.values() 返回dict d中所有值的视图
由于字典既包含键,又包含值,因此我们可以根据(key, value)项、根据键或根据值对其进行迭代。比如,这里给出两个根据(key, value)对进行迭代的等价方法:
for item in d.items():
print(iteam[0], item[1])
for key, value in d.items():
print(key, value)
#根据字典的值进行迭代在形式上非常相似:
for value in d.values():
print(value)
要根据字典的值进行迭代,我们可以使用dict.keys(),或简单地将字典看做一个在其值上进行迭代的iterable,如下面两个等价代码段所展示的:
for key in d:
print(key)
for key in d.keys():
print(key)
如果我们需要改变字典中的值,惯用的做法是在键上进行迭代,并使用方括号操作符来引用并改变其对应的值。比如,下面的代码展示了如何对字典d中每个值进行递增操作,这里假定所有的值都是数字:
for key in d:
d[key] += 1
dict.items()、dict.keys()以及dict.values()等方法都会返回字典视图。在实际作用上, 字典视图是一个只读的iterable对象,看起来存放了字典的项、键或值,具体依赖于我们的实际需求。
通常,我们可以简单地将视图看做iterables.不过,视图与通常的iterates有两个不同点:第一,如果该视图引用的字典发生变化,那么视图将反映该变化;第二,键 视图与项视图支持一些类似于集合的操作。给定字典视图v与set (或字典视图)X, 支持的操作包括:
v & x # Intersection
v | x # Union
v - x # Difference
v ^ x # Symmetric difference
我们可以使用成员关系操作符in来査看某个特定的键是否存在于字典中,比如, x in d。我们也可以使用联合操作符来査看来自给定集合的哪些键存在于字典中,比如:
d = {}.fromkeys("ABCD”, 3) #d== {'A': 3, ‘B’: 3,'C':3, 'D': 3}
s = set("ACX") # s == {'A', 'C', 'X'}
matches = d.keys() & s # matches == {'A', 'C'}
注意,代码段的注释信息中使用了字母序一这仅仅是为了便于阅读,实际上字典与集合都是无序的。字典常用于存放排他性项的个数。
字典内涵(Dictionary Comprehensions)
字典内涵是一个表达式,同时也是一个循环,该循环带有一个可选的条件(包含在方括号中),与集合内涵非常类似。与列表内涵和集合内涵一样,也支持两种语法格式:
{keyexpression: valueexpression for key, value in iterable}
{keyexpression: valueexpression for key, value in iterable if condition}
下面的实例展示了如何使用字典内涵创建字典,其中,每个键是当前目录中文件的文件名。值则为以字节计数的文件大小。
file_sizes = {name: os.path.getsize(name) for name in os.listdir(".")}
os (操作系统)模块的os.listdir()函数返回的是传递给函数的路径中包含的文件与目录列表,但列表中绝不会包含”."或os.path.getsize()函数返回的是给定文件的大小(以字节计数)。通过使用相应条件,可以避免返回目录以及其他非文件的条目:
file_sizes = {name: os.path.getsize(name) for name in os.listdir(".") if os.path.isfile(name)}
如果传递给os.path模块中os.path.isfile()函数的是文件路径,那么该函数返回True; 对于其他情况(路径中包含的是目录、链接等),则返回False。
字典内涵也可用于创建反转的目录,比如,给定字典d,我们可以生成一个新字典,新字典的键是d的值,值则是d的键:
inverted_d = {v: k for k, v in d.items()}
如果原字典中的所有值都是独一无二的,那么上面生成的新字典又可以发转回原字典一但是如果有任何值不是可哈希运算的,那么这种反转会失败,并产生TypeError 异常。
默认字典(Default Dictionaries)
默认字典也是一种字典——这种字典包含字典所能提供的所有操作符与方法与普通字典相比,默认字典的不同之处在于可以对遗失的键进行处理,而在所有其他方面,与普通字典都是一样的(在面向对象的术语中,defaultdict是dict的子类)。
在存取一个字典时,如果我们使用一个不存在(遗失的)的键,就会产生一个 KeyError异常。由于我们经常需要知道某个键是否存在于字典中,因此这种处理方式是有用的。但在某些情况下,我们希望使用的每个键都在位,即便这意味着某些项的键在存取时才插入到字典中。
比如,如果有某个字典d,其中不包含键为m的项,那么代码x=d[m]会产生 KeyError异常。但如果d创建为一个默认字典,键为m的项在默认字典中,就将返回相应的值,就像在字典中一样——如果m不是默认字典中的键,就会创建一个新项(键为m,值为默认值),并返回新创建项的值。
早前,我们编写了一个小程序,该程序用于对文件中独一无二出现的单词进行计数,该文件则在命令行中给出,其中,用于存储单词的字典是以如下方式创建的:
words = {}
字典words中的每个键都是一个单词,每个值则是一个整数,整数值代表的是某个单词在读入的所有文件中出现的次数。下面给出的是遇到某个适当单词时如何进行递增操作:
words[word] = words.get(word, 0) + 1
这里使用dict.get(),以便处理某个单词初次遇到(此时需要创建一个新项,其计数值为1),也处理该单词后续遇到的情况(此时需对其计数值加1)。
创建默认字典时,我们可以传入一个工厂函数。工厂函数是函数的一种,调用时, 将返回某种特定类型的对象。Python所有内置的数据类型都可以用作工厂函数,比如, 数据类型str可以作为函数str()进行调用——不带任何参数时,将返回一个空字符串对象。对默认字典调用工厂函数时,将为遗失的键创建默认值。
注意,函数名实际上是对该函数的对象引用——因此,在需要将函数作为参数进行传递时,我们只传递名。在同时使用圆括号时,则Python据此判断应该调用该函数。
下面给出的是其中如何创建默认字典:
words = collections.defaultdict(int)
默认字典words永远不会产生KeyError异常。如果我们使用语句x = words ["xyz"],
但实际上不存在键为“xyz"的项,进行这样的存取操作时,由于没有找到该键,因此 默认字典会立即创建一个新项,其键为“xyz”,其值为0 (通过调用int()),该值将赋值给x。
words[word] += 1
现在我们不需要使用dict.get(),而是简单地对数据项的值进行递增。在初次遇见某个单词时,会创建一个新项(其值为0,并立即进行加1操作),而随后对其进行的相继访问中,每次遇到时都对当前值加1。
有序字典(Ordered Dictionaries)
有序字典collections.OrderedDict是在Python 3.1中引入的,是履行PEP 372的需要。有序字典可用作对无序字典dict的下降替代,因为二者提供了同样的API其间的差别是,有序字典以数据项插入的顺序进行存储——这也是一种非常方便的特征。
要注意的是,有序字典在创建时如果接收了无序的dict或关键字参数,则数据项顺序将是任意的,这是因为,在底层,Python会使用无序的dict传递关键字参数。使用update()方法时也有类似的效果。由于这些原因,最好避免在创建有序字典时传递关键字参数或无序的dict,也最好不要使用update()方法。然而,如果在创建有序字典时传递键-值二元组构成的元组列表时,则顺序会得以保留(因为这是作为一个单独项目——元组列表——进行传递的)。
#下面给出的是如何使用二元组列表创建有序字典:
d = collections.OrderedDict([('z', -4), ('e', 19), (k, 7)])
由于我们使用单独的列表作为参数,因此键顺序得以保留。以如下方式递增式地创建有序字典可能更常见:
tasks = collections.OrderedDict()
tasks[8031] = "Backup"
tasks[4027] = "Scan Email"
tasks[5733] = "Build System"
如果我们以同样的方式创建了无序的dicts,则返回键的顺序将是任意的。但对有序字典,我们可以依赖于键以被插入的顺序返回。因此,对这些实例,如果我们使用语句list(d.keys()),则可以确保获得列表['z', 'e', 'k'];如果使用语句list(tasks.keys()), 则可以确保获得列表[8031,4027, 5733]。
有序字典另一个很好的特性是,如果我们改变某个数据项的值一也就是说,我们插入一个新的数据项,该项的键与已有键相同——则顺序不会改变。因此,如果我们赋值tasks[8031] = "Daily backup",之后査询键列表,则会获得与之前完全相同顺序的列表。
如果我们想要将某个数据项移到尾部,就必须删除该数据项,之后再重新插入。 在有序字典中,我们也可以调用popitem()来删除并返回最后一个键-值项,或者调用 popitem(last=False),此时将删除并返回第一数据项。
有序字典另一种稍专业一些的用途是生成排序字典。给定一个字典d,可以按如下方式转换为排序字典:d = collections.OrderedDict(sorted(d.items()))。要注意的是,如果我们要插入任意额外的键,这些键将被插入在尾部,因此,在进行插入操作之后, 为保持排序的顺序,我们就必须重新创建该字典(重新执行前面创建该字典的代码)。 插入与重新创建并不会像听起来那样低效,因为Python的排序算法是高度优化的,尤其对部分排序数据而言,当然这仍然会带来潜在的处理开销。
通常,只有在期望对字典进行多次迭代、并且一旦创建后不需要(或极少)进行任何插入操作时,生成有序字典才是有意义的。
以上内容部分摘自视频课程05后端编程Python-7字典类型,更多实操示例请参照视频讲解。跟着张员外讲编程,学习更轻松,不花钱还能学习真本领。