Monkeypatching函数名称空间

时间:2018-05-23 00:19:27

标签: python

"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不能解决?

2 个答案:

答案 0 :(得分:5)

简短版本:因为CPython的PyDict_GetItem函数不是子类友好的(它不是明确的; PyDict_*函数都专用于PyDict_Object本身,而不是广义映射)。

长版:检索属性会调用PyObject_GetAttr。对于未明确定义自定义tp_getattrotp_getattr(以及PyFunction_Type does not)的类,最终会calling PyObject_GenericGetAttr。假设在类本身上找不到任何内容,PyObject_GenericGetAttr(以及实现它的私有API函数)retrieves __dict__ as normal, then calls PyDict_GetItem to retrieve the valuePyDict_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__的好用例。有太多方法可能失败。