我是靠谱客的博主 端庄摩托,最近开发中收集的这篇文章主要介绍python进阶(第三章1) 字典,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

    • 3.1 泛映射类型
        • 什么是可散列的数据类型(键的要求)
        • 字典的构造方法
    • 3.2 字典推导(dictcomp)
    • 3.3 常见的映射方法
        • 用setdefault处理找不到的键
    • 3.4 映射的弹性键查询
      • 3.4.1 defaultdict:处理找不到的键的一个选择
        • 注意:
        • defaultdict与dict实例化字典类型的区别
        • defaultdict的构造
      • 3.4.2 特殊方法__missing__
    • 3.5 字典的变种
        • collections.OrderedDict (添加键会保持顺序)
        • collections.ChainMap(将多个映射合并为单个映射)
        • collections.Counter
          • 例子:统计单词中各个字母出现的次数
        • collections.UserDict
    • 3.6 子类化UserDict
      • MutableMapping.update
      • Mapping.get
        • 从dict或者其他内置类继承有什么不好?
    • 3.7 不可变映射类型(动态的只读的映射视图:MappingProxyType)

3.1 泛映射类型

collections.abc模块有Mapping和MutableMapping 这两个抽象基类,它们的作用是为了dict和其他类似的类型定义形式接口,然后非抽象映射类型一般不会直接继承这些抽象基类,它们会直接对dict或者collections.UserDict进行扩展。这些抽象基类的主要作用是作为形式化的文档,它们定义了构建一个映射类型所需要的最基本的接口。然后它们还可以跟isinstance一起被用来判定某个数据是不是广义上的映射类型:

>>> from collections import abc
>>> my_dict={}                    # 字典是典型的键值对
>>> isinstance(my_dict,abc.Mapping)
True                                
>>> isinstance([1, 2], abc.Mapping)
False									#列表时序列
>>> isinstance((1, 2), abc.Mapping)
False								#元组也是序列
>>> isinstance('sdbd', abc.Mapping)
False								#字符串也是序列
  • 这里用isintance而不是type来检查某个参数是否为dict类型,因为这个参数有可能不是dict,而是一个比较另类的映射类型。(这句话不太明白)
  • 标准库里的所有映射类型都是利用dict来实现的,因此它们有个共同的限制,即只有可散列的数据类型才可以用作这些映射里的键(只有键有这个要求,值没有此要求)

什么是可散列的数据类型(键的要求)

如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的而且这个对象需要实现__hash__()方法。另外可散列对象还要有__eq__()方法,这样才能和其他键作比较。如果两个散列对象是相等的,那么它们的散列值一定是一样
的。

可散列类型包括:

  • (1)原子不可变类型(str, bytes和数值类型)
  • (2)frozenset
  • (3)元组:只有当元组包含的所有元素都是可散列的情况下。
    可以用句话说:python里所有的不可变类型都是可散列的
    一般来讲用户自定义的类型的对象都是可散列的,散列值就是它们的id()函数的返回值。

字典的构造方法

>>> a= dict(one=1,two=2,three=3)
>>> b={'one':1,'two':2,'three':3}
>>> c= dict(zip(['one','two','three'],[1,2,3]))
>>> d = dict({'one':1,'two':2,'three':3})
>>> e=dict([('two',2),('one',1),('three',3)])
>>> a == b ==c == d == e
True
  • 注意这里的相等,只不过是值相等,但是不同的对象

3.2 字典推导(dictcomp)

列表生成器和生成器表达式的概念已经移植到了字典上,从而有了字典推导。
字典推导可以从任何键值对作为元素的可迭代对象中构建出字典。
例子:

>>> DIAL_CODES=[
... (86,'China'),
... (91,'India'),
... (1,'United States'),
... (62,'Indonesia'),
... (55,'Brazil'),
... (92,'Pakistan'),
... (880,'Bangladesh'),
... (234,'Nigeria'),
... (7,'Russia'),
... (81,'Japan'),
... ]
>>> country_code={country:code for code,country in DIAL_CODES}
>>> country_code
{'China': 86, 'India': 91, 'United States': 1, 'Indonesia': 62, 'Brazil': 55, 'Pakistan': 92, 'Bangladesh': 880, 'Nigeria': 234, 'Russia': 7, 'Japan': 81}
>>> {code:country.upper() for country,code in country_code.items() if code <66}
{1: 'UNITED STATES', 62: 'INDONESIA', 55: 'BRAZIL', 7: 'RUSSIA'}

字典推导的表达式会蔓延到其他数据结构类型

3.3 常见的映射方法

除了

  • dict
  • defaultdict
  • OrderedDict

这三种常见方法
在映射对象的方法里,setdefault可能是比较微妙的一个。尽管用的次数不多,但是它一旦发挥作用,就可以节省不少次键查询,让程序更高效。

用setdefault处理找不到的键

我们可以使用d.get(k,default)来代替d[k],给找不到的键一个默认的返回值(这比处理keyError方便不少)
看个例子:

>>> my_dict = {'子': '鼠', '丑': '牛', '寅': '虎',
...                '卯': '兔', '辰': '龙', '巳': '蛇',
...                '午': '马', '未': '羊', '申': '猴',
...                '酉': '鸡', '戌': '狗', '亥': '猪'}
>>> my_dict.setdefault('子','属鼠')       # 显然键 '子'存在,那么 值 '属鼠' 也就无用
'鼠'
>>> my_dict.setdefault('行初心','CSDN')      # 如果找不到,就会添加.
'CSDN'
>>> my_dict.setdefault('行')    # 不存在的键"行",未指定值,默认返回None
>>> my_dict
{'子': '鼠', '丑': '牛', '寅': '虎', '卯': '兔', '辰': '龙', '巳': '蛇', '午': '马', '未': '羊', '申': '猴', '酉': '鸡', '戌': '狗', '亥': '猪', '行初心': 'CSDN', '行': None}

例子2:(使用dict.setdefault()方法来设置默认值,统计字符串出现的次数)

strings = ('puppy', 'kitten', 'puppy', 'puppy',
           'weasel', 'puppy', 'kitten', 'puppy')
counts = {}
for kw in strings:
    counts.setdefault(kw, 0)
    counts[kw] += 1 

dict.setdefault()方法的返回值可以重写for循环中的代码,使其更加简洁:

strings = ('puppy', 'kitten', 'puppy', 'puppy',
           'weasel', 'puppy', 'kitten', 'puppy')
counts = {}
for kw in strings:
    counts[kw] = counts.setdefault(kw, 0) + 1

3.4 映射的弹性键查询

为了方便,就算某个键在映射里不存在,那么你也希望在通过这个键读取值的时候能得到一个默认值。有两个途径帮我们达到这个目的。

  • (1).通过defaultdict这个类型而不是普通的dict
  • (2).给自己定义一个dict类型的子类,然后在这个子类中实现__missing__方法。

3.4.1 defaultdict:处理找不到的键的一个选择

在用户创建defaultdict对象的时候,就需要给它配置一个为找不到的键创造默认值的方法。
具体而言,在实例化一个defaultdict的时候,需要给构造方法提供一个可调用的对象,这个可调用对象会在__getitem__碰到找不到的键的时候被调用,让__getitem__返回某种默认值。
比如,新建一个字典:dd=defaultdict(list),如果键’new-key’在dd中不存在的话,表达式dd[‘new-key’]会按照以下步骤行事。

  • (1).调用list()来创建新列表
  • (2).把这个新列表作为值,'new-key’作为它的键,放到dd中。
  • (3).返回这个列表的引用
    而这个用来生成默认值的可调用对象存放在名为default_factory的实例属性里。
    如果在创建defaultdict的时候没有指定default_factory,查询不存在的键会触发KeyError.

注意:

  • defaultdict里面的default_factory只会在__getitem__里被调用,在其他的方法里完全不会发挥作用。比如,dd是个defaultdict,K是个找不到的键,dd[k]这个表达式会调用default_factory创造某个默认值,而dd.get(k)则会返回None.

所有 这一切背后的功臣其实是特殊方法__missing__.它会在defaultdict遇到找不到的键的时候调用default_factory,而实际上这个特性是所有映射类型都可以去选择的。

看个例子:

from collections import defaultdict
class from_defaultdict(defaultdict):
    def __getitem__(self, key):
        return 'hello'

c = from_defaultdict(list)

print(c['new-key'])	

结果如下:
在这里插入图片描述

defaultdict与dict实例化字典类型的区别

使用defaultdict任何未定义的key都会默认返回一个根据method_factory参数不同的默认值, 而相同情况下dict()会返回KeyError.
比较下面代码:

from collections import defaultdict
d1 = dict()
d2 = defaultdict(list)
print(d2['a'])
print(d1['a'])

输出:

[]
Traceback (most recent call last):
  File "/home/maxzhang/PycharmProjects/pythoncode/t.py", line 5, in <module>
    print(d1['a'])
KeyError: 'a'

defaultdict的构造

python官方文档中对defaultdict的定义如下:

class collections.defaultdict([default_factory[, ...]])

python官方文档中对defaultdict的解释如下:

defaultdi:
dict subclass that calls a factory function to supply missing values
  • default_factory 接收一个工厂函数作为参数, 例如int str,list,set等.
  • defaultdict在dict的基础上添加了一个__missing__(key)方法, 在调用一个不存的key的时候, defaultdict会调用__missing__, 返回一个根据default_factory参数的默认值, 所以不会返回Keyerror.

3.4.2 特殊方法__missing__

所有的映射类型在处理找不到的键的时候,都会牵扯到__missing__方法。这也是和这个方法称作’missing’的原因。虽然基类dict并没有定义这个方法,但是dict是知道有这么一个东西存在的。也就是说,如果有一个类继承了dict,然后这个继承类提供了__missing__方法,那么在__getitem__碰到找不到的键的时候,python会自动调用它。而不是抛出异常。

  • 注意:__missing__方法只会被__getitem__调用。提供__missing__方法对get或者__contains__这些方法的使用没有影响。

如果要自定义一个映射类型,更合适的策略是继承collections.UserDict类。

3.5 字典的变种

collections.OrderedDict (添加键会保持顺序)

这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的。OrderedDict的popitem方法默认删除并返回的是字典里的最后一个元素,但是如果像my_odict.popitem(last=False)这样调用它,那么它删除并返回第一个被添加进去的元素。
例子:

>>> d = collections.OrderedDict()
>>> d['a'] = 'A'
>>> d['b'] = 'B'
>>> d['c'] = 'C'
>>> for k ,v in d.items():
...     print(k,v)
... 
a A
b B
c C
>>> d.popitem(last=False)
('a', 'A')
>>> d
OrderedDict([('b', 'B'), ('c', 'C')])

collections.ChainMap(将多个映射合并为单个映射)

该类型可以容纳数个不同的映射对象,然后在进行键查找操作的时候,这些对象会被当做一个整体逐个查找,直到键被找到为止。
例子:

>>> import collections
>>> a = {'x': 1, 'z': 3}
>>> b = {'y': 2, 'z': 4}
>>> c = collections.ChainMap(a, b)
>>> c['x']
1

collections.Counter

这个映射会给键准备一个整数计数器。每次更新一个键的时候都会增加这个基数器。所以这个类型可以用来给可散列表对象计数,或者是当成多重集来使用—多重集合就是集合里的元素可以出现不止一次。Counter实现了+和-运算符用来合并记录,还有像most_common([n])这类很有用的方法。most_common([n])会按照次序返回映射里最常见的n个键和它们的计数。

例子:统计单词中各个字母出现的次数
>>> ct = collections.Counter('afalfjlahgksdadaa')
>>> ct
Counter({'a': 6, 'f': 2, 'l': 2, 'd': 2, 'j': 1, 'h': 1, 'g': 1, 'k': 1, 's': 1})
>>> ct.update('aaaaaaaaadffdwe')
>>> ct
Counter({'a': 15, 'f': 4, 'd': 4, 'l': 2, 'j': 1, 'h': 1, 'g': 1, 'k': 1, 's': 1, 'w': 1, 'e': 1})
>>> ct.most_common(2)
[('a', 15), ('f', 4)]

collections.UserDict

这个类其实就是把标准dict用纯python又实现了一遍
UserDict就是让用户继承写子类的。

3.6 子类化UserDict

就创造自定义映射类型来说,以UserDict为基类,总比以普通的dict为基类要来的方便。
更倾向于从UserDict而不是从dict继承的主要原因是,后者有时会在某些方法的实现上走一些捷径,导致我们不得不在它的子类中重写这些方法,但是UserDict就不会带来这些问题。
另外一个值得注意的地方是,UserDict并不是dict的子类,但是UserDict有一个叫做data的属性是dict的实例,这个属性就是UserDict最终存储数据的地方。
例子:

import collections

class StrKeyDict(collections.UserDict):
    def __missing__(self, key):
        if isinstance(key,str):
            raise KeyError
        return self[str(key)]
    def __contains__(self, key):
        return str(key) in self.data
    
    def __setitem__(self, key, item):
        self.data[str(key)] = item

因为UserDict 继承的是MutableMapping,所以StrKeyDict里剩下的的那些映射类型的方法都是从UserDict,MutableMapping和Mapping这些超类继承而来的。特别是最后的Mapping类,它虽然是一个抽象类(ABC),但是它提供了许多使用的方法。

MutableMapping.update

这个方法不但可以直接利用,它还用在__init__里,让构造方法可以利用传入的各种参数(其他映射类型,元素是(key,value)对的可迭代对象和键值参数)来新建实例。因为这个方法在背后是使用self[key]=value来添加新值的,所以它其实是在使用我们的__setitem__方法

Mapping.get

从dict或者其他内置类继承有什么不好?

3.7 不可变映射类型(动态的只读的映射视图:MappingProxyType)

标准库里所有的映射类型都是可变的,但是有时候你会有这样的需求,比如不能让用户错误修改某个映射。

在types模块中引入了一个封装类名叫MappingProxyType。如果给这个类一个映射,它会返回一个动态的只读的映射视图。如果对原映射做出了修改,这个视图可以观察到,但是无法通过这个视图对原映射进行修改。
例子:

>>> from types import MappingProxyType
>>> d = {1:'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1]                  #d中的内容可以通过d_proxy看到
'A'
>>> d_proxy[2] = 'x'          #但是d_proxy不能做任何的修改
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2]= 'B'
>>> d_proxy                 #d_proxy是动态的,也就是说对d所做的任何改动都会反馈到它上面
mappingproxy({1: 'A', 2: 'B'})
>>> d_proxy[2]
'B'
>>> 

最后

以上就是端庄摩托为你收集整理的python进阶(第三章1) 字典的全部内容,希望文章能够帮你解决python进阶(第三章1) 字典所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(82)

评论列表共有 0 条评论

立即
投稿
返回
顶部