1、子类应该继承collections.UserDict而非dict
在(用法二)里面讲过,创建新的映射类型或自定义字典类型,最好扩展collections.UserDict,而不是dict。为了确保以str类型存储添加到映射中的键,我们在(用法二)中定义了DemoDict类时就已经提到过一嘴。
子类最好继承UserDict的主要原因是,内置的dict在实现上走了一些捷径,如果继承dict,那就不得不覆盖一些方法,而是继承UserDict则没有这些问题。
注意的是,UserDict没有继承dict,使用的是组合模式:内部有一个dict实例,名为data,用于存放具体的项。与(用法二)里面的案例相比,可以避免__setitem__等特殊方法意外递归,还能简化__contains__的实现。
from collections import UserDict
class DemoDict(UserDict):
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def __contains__(self, key):
return str(key) in self.data
def __setitem__(self, key, value):
self.data[str(key)] = value
2、不可变映射
标准库提供的映射类型都是可变的,不过有时也有防止用户意外更改的映射。types模块提供的MappingProxyType是一个包装类,把传入的映射包装成一个mappingproxy实例,这是原映射的动态代理,只可读取。这意味着,对原映射的更新将体现在mappingproxy实例上,但是不能提供mappingproxy实例更改映射。
from types import MappingProxyType
dict1 = {1: "A", 2: "B"}
dict_proxy = MappingProxyType(dict1)
print(dict_proxy) # {1: 'A', 2: 'B'}
print(dict_proxy[1]) # A
dict_proxy[3] = "C" # 不能提供dict_proxy更改映射,TypeError: 'mappingproxy' object does not support item assignment
dict1[3] = "C"
print(dict_proxy) # {1: 'A', 2: 'B', 3: 'C'}
3、字典视图
dict的实例方法keys(),values()和item()分别返回dict_keys、dict_values和dict_items类的实例。这些字典视图是dict内部实现使用的数据结构的只读投影。Python2中对应的方法返回列表,重复dict中已有的数据,有一定内存开销。另外,视图还取代了返回迭代器的旧方法。
d = dict(a=1, b=2, c=3)
print(d.keys()) # dict_keys(['a', 'b', 'c'])
print(d.values()) # dict_values([1, 2, 3])
print(len(d)) # 3
print(list(d.values())) # [1, 2, 3]
print(reversed(d.values())) # 视图自定义__reversed__方法,返回一个自定义的迭代器 <dict_reversevalueiterator object at 0x000001E561B3E930>
print(d.values()[0]) # TypeError: 'dict_values' object is not subscriptable
4、dict的实现方式对实践的影响
Python 使用哈希表实现 dict,因此字典的效率非常高,不过这种设计对实践也有⼀些影响,不容忽视。
- 键必须是可哈希对象,必须正确实现__hash__和__eq__方法
- 通过键访问项速度非常快,对于一个包含数百万个键的dict对象,Python通过计算哈希码就可以直接定位键,然后找出索引在hash表中的偏移量,稍微尝试几次就可以找到匹配的条目,因此开销不大
- 在CPython3.6中,dict的内存布局更为紧凑,顺带的一个副作用是键的顺序得以保留。Python3.7正式支持保留顺序
- 尽管采用了新的布局方式,但是字典仍然占用大量内存,这是不可避免的。对容器来说,最紧凑的内部数据结构是指向项的指针的数组(元组就是这样存储的)。与之相比,哈希表中的条目存储的数据更多,而且为了保证效率,Python至少需要把哈希表中三分之一的行留空。
- 为了节省内存,不要在__init__方法之外创建实例属性