0 字典特殊场景例子:
- 我们要写一个程序,在文件系统里管理社交网络账号中的图片。
- 这个程序应该用字典把这些图片的路径名跟相关的文件句柄(file handle)关联起来,
在图片文件中写一些二进制信息
with open(path, ‘wb’) as f: f.write(b’image data here 1234’)
if (handle := pictures.get(path)) is None: # get + 赋值表达式 try: handle = open(path, ‘a+b’) # 二进制 追加形式 except OSError: print(f’Failed to open path {path}’) raise else: pictures[path] = handle
handle.seek(0) # 0 代表起始位置,1 代表当前位置,2 代表末位 image_data = handle.read()
print(pictures) print(image_data)
- 如果字典里已经有这个 handle 了,那么这种写法只需要进行一次字典访问。
- 如果没有,那么它会通过 get 方法访问一次字典,然后在 try/except/else 结构的 else 分支中做一次赋值(带 finally 块的结构也可以这样做,参见第 65 条)。
- 读取数据的代码与打开文件并处理异常的代码可以分开写。

<a name="als1t"></a>
# 2 用 setdefault 实现(有问题)
```python
pictures = {}
path = 'profile_1234.png'
# 在图片文件中写一些二进制信息
with open(path, 'wb') as f:
f.write(b'image data here 1234')
try:
handle = pictures.setdefault(path, open(path, 'a+b'))
except OSError:
print(f'Failed to open path {path}')
raise
else:
handle.seek(0)
image_data = handle.read()
print(pictures)
print(image_data)
- 这样写有很多问题。
- 首先,即便图片的路径名已经在字典里了,程序也还是得调用内置的 open 函数创建文件句柄,于是导致这个程序要给已经创建过 handle 的那份文件再度创建 handle(两者可能相互冲突)。
另外,如果 try 块抛出异常,那我们可能无法判断这个异常是 open 函数导致的,还是 setdefault 方法导致的,因为这两次调用全都写在了同一行代码里(其他一些类似字典的实现方案或许可以做到,参见第 43 条)。
3 用 defaultdict 实现(有问题)
下面用 defaultdict 类实现相同的逻辑,只不过这次得专门写一个辅助函数。 ```python from collections import defaultdict
pictures = {} path = ‘profile_1234.png’
在图片文件中写一些二进制信息
with open(path, ‘wb’) as f: f.write(b’image data here 1234’)
def open_picture(profile_path): try: return open(profile_path, ‘a+b’) except OSError: print(f’Failed to open path {profile_path}’) raise
pictures = defaultdict(open_picture) # 传的参数是类型 handle = pictures[path] handle.seek(0) image_data = handle.read()
print(pictures) print(image_data)

- 程序出错的原因在于,传给 defaultdict 的那个函数只能是不需要参数的函数,而**我们写的辅助函数却要求调用方传入一个参数**。defaultdict 并不知道当前要访问的这个键叫什么名字,所以没办法给辅助函数传递这个参数,这也意味着我们没办法用这个参数去调用 open。
<a name="X20vs"></a>
# 4 使用 "__**missing__" **方法
```python
path = 'profile_1234.png'
# 在图片文件中写一些二进制信息
with open(path, 'wb') as f:
f.write(b'image data here 1234')
def open_picture(profile_path):
try:
return open(profile_path, 'a+b')
except OSError:
print(f'Failed to open path {profile_path}')
raise
class Pictures(dict): # 注意继承了 dict
def __missing__(self, key):
value = open_picture(key)
self[key] = value
return value
pictures = Pictures()
handle = pictures[path]
handle.seek(0)
image_data = handle.read()
print(pictures)
print(image_data)
- 访问 pictures[path] 时,如果 pictures 字典里没有 path 这个键,那就会调用
__missing__
方法。 - 这个方法必须根据
key
参数创建一份新的默认值,系统会把这个默认值插入字典并返回给调用方。 - 以后再访问
pictures[path]
,就不会调用__missing__
了,因为字典里已经有了对应的键与值(类似的机制还体现在__getattr__
中,参见第 47 条)