仅当定义了 __iter__ 时才会调用 __getitem__

pau*_*are 2 python dictionary subclass

我正在对一个字典进行子类化,并且希望获得一些帮助来理解下面的行为(请)[Python 版本:3.11.3]:

class Xdict(dict):
    def __init__(self, d):
        super().__init__(d)
        self._x = {k: f"x{v}" for k, v in d.items()}

    def __getitem__(self, key):
        print("in __getitem__")
        return self._x[key]

    def __str__(self):
        return str(self._x)

    def __iter__(self):
        print("in __iter__")

d = Xdict({"a": 1, "b": 2})
print(d)
print(dict(d))
Run Code Online (Sandbox Code Playgroud)

产生以下输出:

{'a': 'x1', 'b': 'x2'}
in __getitem__
in __getitem__
{'a': 'x1', 'b': 'x2'}
Run Code Online (Sandbox Code Playgroud)

如果我注释掉该__iter__方法,输出会像这样改变:

{'a': 'x1', 'b': 'x2'}
{'a': 1, 'b': 2}
Run Code Online (Sandbox Code Playgroud)

显然该__iter__方法没有被调用,但它的存在正在影响行为。

我只是对为什么会发生这种情况感兴趣。我并不是在寻找替代解决方案来防止它。

谢谢,保罗。

use*_*ica 6

Python 的内部经常直接调用内置类功能的 C 级实现,即使子类可能覆盖了该功能,这会导致许多奇怪的错误,其中方法覆盖不会在您期望的地方被调用是。

许多实现都是这种情况dict,但是当字典在 Python 3.6 中成为保留顺序时,标准库中就会出现这些错误之一x:当是 OrderedDict 时,dict(x)将复制底层dict实现的顺序,而不是OrderedDict顺序(即单独跟踪)。

为了修复这个错误,他们在代码中添加了一个检查,dict_merge来决定是否使用快速路径:

if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == (getiterfunc)dict_iter)) {
Run Code Online (Sandbox Code Playgroud)

dict_merge是负责将另一个映射的内容复制到字典中的底层例程。以前,这一行只是说if (PyDict_Check(b)) {,如果其他映射是任何 dict 实例,它将使用快速路径。现在,它还检查实例是否没有覆盖的__iter__.

如果实例有一个被覆盖的__iter__dict_merge将使用慢速路径,因此您看到了差异。然而,慢速路径实际上并不使用 __iter__. 它使用keys,这就是为什么即使您__iter__不返回迭代器,您的代码也能工作。