文章目录
- 1 泛映射类型
- 1.1 字典的键必须是可散列的
- 2 字典推导式
- 3 字典实现的魔法方法
- 4 字典怎么处理找不到的键
- 5 __missing__特殊方法
- 6 标准库中特殊的字典
- 6.1 OrderedDict
- 6.2 ChainMap
- 6.3 Counter
- 7 子类化UserDict
- 8 剖析抽象基类-Sized为例
字典类型是Python语言的基石。字典有关的内置函数在
__builtins__.__dict__
Python使用散列表对字典进行了优化,使得字典查询时间复杂度能够达到O(1)
1 泛映射类型
_collections_abc.py 文件定义了很多抽象基类,抽象基类的作用是为其他类定义接口,其中mutablemapping和mapping就继承了Container基类,sized基类和Iterable基类。
- 实例一
from _collections_abc import *
my_dict = {}
print(isinstance(my_dict, Mapping))
这里面不用type来检测my_dict而是使用isinstance,因为my_dict可能不是dict而是类型的映射类型
1.1 字典的键必须是可散列的
如果一个对象是可散列的,在对象的生命周期中,散列值不变,对象需要实现__hash__()方法和__eq__()方法
print(hash((1,2,(3,4))))
>3794340727080330424
print(hash((1,2,[3,4])))
>TypeError: unhashable type: 'list'
对于不可变类型是可散列的,不可变类型中含有可变类型的引用也是不可散列的
下面这个类的实例是可散列的,对于Python内部有自己的散列算法,我这里只是举个例子说明实例可以被散列的底层原因,实例本身没有任何实际意义
from uuid import uuid4
class Hashable:
def __hash__(self):
return int(uuid4())
def __eq__(self, other):
return hash(self) == hash(other)
2 字典推导式
"""普通推导式"""
test_list = [("第一","北京"),("第二","上海"),("第三","深圳"),("第四","东京"),("第五","纽约"),("第六","伦敦")]
test_dict = {rank:city for rank, city in test_list }
print(test_dict)
"""带条件的推导式"""
>{'第一': '北京', '第二': '上海', '第三': '深圳', '第四': '东京', '第五': '纽约', '第六': '伦敦'}
test_dict = {rank:city for rank, city in test_list if rank in ['第一','第二','第六']}
print(test_dict)
> {'第一': '北京', '第二': '上海', '第六': '伦敦'}
3 字典实现的魔法方法
d.__contains__(k) # k in d
d.__copy__() # copy.copy
d.__delitem__(k) # del d[k]
d.__getitem__(k) # d[k]
d.__iter__() # 获取键迭代器
d.__len__() # len(d) key-value pairs number
d.__missing__(k) # 当d[k] 找不到键后调用
d.__setitem__(k,v) # d[k] = v
4 字典怎么处理找不到的键
方式一
class MyDict(dict):
pass
test_dict = MyDict(k1=[])
res = test_dict.get('k2', None)
print(res)
> None
方式二
class MyDict(dict):
pass
test_dict = MyDict(k1=[])
try:
res = test_dict['k2']
except KeyError:
res = None
print(res)
> None
方式三
class MyDict(dict):
pass
test_dict = MyDict(k1=[])
test_dict.setdefault('k2',[]).append(1)
print(test_dict)
> {'k1': [], 'k2': [1]}
方式三
class MyDict(dict):
pass
test_dict = MyDict(k1=[])
test_dict.setdefault('k2',[]).append(1)
print(test_dict)
> {'k1': [], 'k2': [1]}
方式四
from collections import defaultdict
class MyDict(defaultdict):
pass
test_dict = MyDict(list)
res = test_dict['k1']
print(res)
res.append(1)
print(res)
"""
[]
[1]
"""
5 __missing__特殊方法
对于字典找不到健值,本质上是执行了__missing__方法
class MyDict(dict):
def __init__(self,default):
super(MyDict,self).__init__()
self.default = default
def __missing__(self, key):
"""
我在这里加一个if判断主要是防止出现无限递归
self[key] -> __getitem__(key) -> 找不到健值 -> self[key] -> 死循环
"""
print(f"{__name__} is running")
if isinstance(key, str):
return self.default # 使用默认值结束递归
self[str[key]]
test_dict = MyDict('默认值')
res = test_dict['k1']
print(res)
"""
__main__ is running
默认值
"""
6 标准库中特殊的字典
import collections
6.1 OrderedDict
对字典的健排序
6.2 ChainMap
多个字典
6.3 Counter
import collections
res = collections.Counter('aabcccddeeff')
print(res)
Counter({'c': 3, 'a': 2, 'd': 2, 'e': 2, 'f': 2, 'b': 1})
print(res.most_common(2))
> [('c', 3), ('a', 2)]
res.update('ggg')
print(res)
Counter({'c': 3, 'g': 3, 'a': 2, 'd': 2, 'e': 2, 'f': 2, 'b': 1})
7 子类化UserDict
Python不建议你继承dict类,提供给你一个UserDict类继承,避免出现意外的递归
UserDict有一个data属性,data是dict的实例,用于存储UserDict的值
import collections
class Mydict(collections.UserDict):
def __missing__(self, key): # 与上面一样避免递归
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def __contains__(self, item):
return str(item) in self.data
def __setitem__(self, key, value):
self.data[str(key)] = value # 将key value 存储到self.data
8 剖析抽象基类-Sized为例
def _check_methods(C, *methods):
mro = C.__mro__
for method in methods:
for B in mro:
if method in B.__dict__:
if B.__dict__[method] is None:
return NotImplemented
break
else:
return NotImplemented
return True
class Sized(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
return _check_methods(C, "__len__")
return NotImplemented
上面这段代码摘自_collections_abc.py 主要实现的是一个抽象基类,这个抽象基类提供__len__()方法的支持
如上所示,我们看到了它实现了__subclasshook__方法,检查子类和子类的mro上所有的类是否有__len__方法,如果没有,返回NotImplemented。当然我们不必继承Sized,而是使用虚拟子类(virtual subclass)技术,只实现__len__协议,隐式继承了Sized。
这个虚拟子类技术就是为什么我们在自定义类中使用魔法方法后能够实现相关功能的原因,这句话比较绕,我们自定义一个抽象基类,来加深理解
import abc
class Base(abc.ABC):
@abc.abstractmethod
def my_protocol(self):
"""自定义协议"""
@classmethod
def __subclasshook__(cls, subclass):
if cls is Base:
if any("my_protocol" in B.__dict__ for B in subclass.__mro__):
return True
return NotImplemented
#并没有显式继承Base
class MyClass:
def my_protocol(self):
pass
if __name__ == '__main__':
k = MyClass()
print(isinstance(k, Base))
#True
print(issubclass(MyClass, Base))
#True