标准库里所有的映射类型都是可变的,但有时候你会有这样的需求,比如不能让用户错误地修改某个映射。3.4.2 节提到过 Pingo.io,它里面就有个现成的例子。Pingo.io 里有个映射的名字叫作 board.pins,里面的数据是 GPIO 物理针脚的信息,我们当然不希望用户一个疏忽就把这些信息给改了。因为硬件方面的东西是不会受软件影响的,所以如果把这个映射里的信息改了,就跟物理上的元件对不上号了。

从 Python 3.3 开始,types 模块中引入了一个封装类名叫 MappingProxyType。如果给这个类一个映射,它会返回一个只读的映射视图。虽然是个只读视图,但是它是动态的。这意味着如果对原映射做出了改动,我们通过这个视图可以观察到,但是无法通过这个视图对原映射做出修改。示例 3-9 简短地对这个类的用法做了个演示。

示例 3-9 用 MappingProxyType 来获取字典的只读实例 mappingproxy

>>> from types import MappingProxyType
>>> d = {1:'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1]  ➊
'A'
>>> d_proxy[2] = 'x'  ➋
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2] = 'B'
>>> d_proxy  ➌
mappingproxy({1: 'A', 2: 'B'})
>>> d_proxy[2]
'B'
>>>

d 中的内容可以通过 d_proxy 看到。

➋ 但是通过 d_proxy 并不能做任何修改。

d_proxy 是动态的,也就是说对 d 所做的任何改动都会反馈到它上面。

因此在 Pingo.io 中我们是这样用它的:Board 的具体子类会提供一个包含针脚信息的私有映射成员,然后通过公开属性 .pins 把这个映射暴露给 API 的客户,而 .pins 属性其实就是用 mappingproxy 实现的。一旦这样写好了,客户就不能对这个映射进行任何意外的添加、移除或者修改操作。6

6为了照顾 Python 2.7,现实中的 Pingo.io 没有借用 MappingProxyType 来实现这个功能,因为它只在 Python 3.3 里才有。

到了这里,我们对标准库中的大多数映射类型都有了一些了解,下面让我们移步到集合类型。