子类化字典:UserDict、dict还是ABC?
在 UserDict
、dict
和 ABC
之间有什么区别?哪个更推荐使用?文档里好像不推荐 UserDict
了?
还有,UserDict
的 update()
方法会使用我的 setitem
方法,而 dict
不会?如果我想自定义 setitem
和 getitem
方法,哪些方法是必须重写的?
使用 ABC
的话,我需要实现所有的方法,因为它没有默认的实现吗?
我想做一个 dict
,它要完成两个功能:
- 对所有的键和值进行
intern()
处理 - 把一些值存储到 SQLite 数据库中
那么在 UserDict
、dict
和 ABC
中,哪个最适合我实现这个功能呢?
5 个回答
我找到了一些关于 dict
和 userdict
之间区别的例子,来源于这里: https://dev.to/0xbf/customize-your-own-dictionary-python-tips-5b47
如果你重写了 dict
的 __delitem__
方法,这个改变只会影响到 del
这个操作,而不会影响到 pop
。
之所以会这样,是因为 Python 内置的字典(dict)有一些内部优化,这导致
pop
不会调用 delitem 方法。
这点可能不太好理解。
但是,如果你重写了 userdict
的 __delitem__
方法,那么 del
和 pop
都会受到影响。
UserDict
通常是你需要自定义字典时最简单的选择。
正确地重写 dict
是有点棘手,而 UserDict
则让这件事变得简单。曾经有人讨论要把它从 Python3 中移除,但我相信它之所以被保留就是因为这个原因。举个例子:
class MyDict(dict):
def __setitem__(self, key, value):
super().__setitem__(key, value * 10)
d = MyDict(a=1, b=2) # Oups MyDict.__setitem__ not called
d.update(c=3) # Oups MyDict.__setitem__ not called
d['d'] = 4 # Good!
print(d) # {'a': 1, 'b': 2, 'c': 3, 'd': 40}
UserDict
继承自 collections.abc.MutableMapping
,所以没有那些缺点:
class MyDict(collections.UserDict):
def __setitem__(self, key, value):
super().__setitem__(key, value * 10)
d = MyDict(a=1, b=2) # Good: MyDict.__setitem__ correctly called
d.update(c=3) # Good: MyDict.__setitem__ correctly called
d['d'] = 4 # Good
print(d) # {'a': 10, 'b': 20, 'c': 30, 'd': 40}
直接从 collections.abc.MutableMapping
子类化的话,除了要重写 __len__
、__iter__
等等,难度会比较大,而从 UserDict
子类化就简单多了。
不过有一点需要注意的是,UserDict
是一个 MutableMapping
,而不是一个 dict
:
assert not isinstance(collections.UserDict(), dict)
assert isinstance(collections.UserDict(), collections.abc.MutableMapping)
如果你想要一个自定义的集合来存储数据,可以通过继承 dict 来实现。这特别适合你想要 扩展 接口(比如,添加一些方法)的时候。
不过,内置的方法不会调用你自定义的 __getitem__
和 __setitem__
。如果你需要完全控制这些功能,建议创建一个自定义类,去实现 collections.MutableMapping
这个抽象基类。
这个抽象基类并不提供存储实际数据的方法,只是提供了一些方法的默认实现接口。不过,这些默认实现会调用你自定义的 __getitem__
和 __setitem__
。你需要使用一个内部的 dict
来保存数据,并实现所有的抽象方法:__len__
、__iter__
、__getitem__
、__setitem__
和 __delitem__
。
在 collections
模块中有一个类叫 UserDict
(在 Python 2 中,这个模块也叫 UserDict
),它是一个包装器,内部使用一个 dict
,并实现了 MutableMapping
抽象基类。如果你想自定义 dict
的行为,这个实现可以作为一个起点。
总结一下:
- MutableMapping 定义了接口。你可以继承它来创建一个像
dict
一样的东西。数据的存储方式完全由你决定。 - UserDict 是一个使用内部“真实”
dict
作为存储的MutableMapping
的实现。如果你想要一个类似dict
的存储集合,但又想重写一些dict
提供的方法,这可能是一个不错的起点。不过一定要阅读代码,了解基本方法是如何实现的,以便在重写方法时保持一致。 - dict 是“真正的东西”。如果你想要 扩展 接口,可以继承它。重写方法来做自定义的事情可能会有风险,因为通常有多种方式访问数据,这样可能会导致 API 不一致。