受"Define @property on function"的启发,我试图覆盖函数的命名空间:
>>> class MyDict(dict):
... def __getitem__(self, item):
... if item == 'k2':
... return 'v2'
... return super().__getitem__(item)
...
>>> def f():
... pass
...
...
>>> f.__dict__ = MyDict({'k1': 'v1'})
>>>
>>> f.k1
'v1'
>>> f.k2
AttributeError: 'function' object has no attribute 'k2'
为什么f.k1
可以解决,但f.k2
不能解决?
答案 0 :(得分:5)
简短版本:因为CPython的PyDict_GetItem
函数不是子类友好的(它不是明确的; PyDict_*
函数都专用于PyDict_Object
本身,而不是广义映射)。
长版:检索属性会调用PyObject_GetAttr
。对于未明确定义自定义tp_getattro
或tp_getattr
(以及PyFunction_Type
does not)的类,最终会calling PyObject_GenericGetAttr
。假设在类本身上找不到任何内容,PyObject_GenericGetAttr
(以及实现它的私有API函数)retrieves __dict__
as normal, then calls PyDict_GetItem
to retrieve the value。 PyDict_GetItem
明确使用dict
的C级内部来执行访问,绕过您可能已定义的任何自定义__getitem__
。因此,您的自定义__getitem__
永远不会被调用;出于所有实际目的,您的dict
子类只是dict
。
我原本希望你可能能够通过the officially supported __missing__
hook使这个特定案例有效,但事实证明只有当__getitem__
({{调用1}}},而不是通过像dict_subscript
这样的C级直接访问API(根本不会通过PyDict_GetItem
)。
基本上,CPython似乎已经选择在此处优先考虑性能而非完全灵活性。任何用作dict_subscript
的{{1}}子类都将被访问,就像它是一个普通的dict
一样(如果子类正在做一些魔术来存储一个值,同时假装存储它可能会有点麻烦)一个不同的值,因为魔法被绕过了),并且所有非__dict__
子类的映射在分配时被拒绝(当你尝试将它们分配给{{1}时,你得到dict
})。
答案 1 :(得分:-2)
__getitem__
文档中的第一句内容为:“被称为实施自我评估[密钥]。”您假设(希望)f.k1
的评估执行显式操作f.__dict__["k1"]
,但事实并非如此。如果在__getitem__
的第一行放置一个print语句,您将看到代码不会调用此函数。
我不确定为什么会这样,但我怀疑这是出于性能原因。在任何情况下,我都无法想到替换函数__dict__
的好用例。有太多方法可能失败。