子类化字典:UserDict、dict还是ABC?

55 投票
5 回答
19419 浏览
提问于 2025-04-17 00:06

UserDictdictABC 之间有什么区别?哪个更推荐使用?文档里好像不推荐 UserDict 了?

还有,UserDictupdate() 方法会使用我的 setitem 方法,而 dict 不会?如果我想自定义 setitemgetitem 方法,哪些方法是必须重写的?

使用 ABC 的话,我需要实现所有的方法,因为它没有默认的实现吗?

我想做一个 dict,它要完成两个功能:

  • 对所有的键和值进行 intern() 处理
  • 把一些值存储到 SQLite 数据库中

那么在 UserDictdictABC 中,哪个最适合我实现这个功能呢?

5 个回答

2

我找到了一些关于 dictuserdict 之间区别的例子,来源于这里: https://dev.to/0xbf/customize-your-own-dictionary-python-tips-5b47

如果你重写了 dict__delitem__ 方法,这个改变只会影响到 del 这个操作,而不会影响到 pop

之所以会这样,是因为 Python 内置的字典(dict)有一些内部优化,这导致 pop 不会调用 delitem 方法。

这点可能不太好理解。

但是,如果你重写了 userdict__delitem__ 方法,那么 delpop 都会受到影响。

4

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)
73

如果你想要一个自定义的集合来存储数据,可以通过继承 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 不一致。

撰写回答