Python Monkeypatching函数名称空间

Python Monkeypatching函数名称空间,python,Python,受此启发,我尝试重写函数的命名空间: >>> class MyDict(dict): ... def __getitem__(self, item): ... if item == 'k2': ... return 'v2' ... return super().__getitem__(item) ... >>> def f(): ... pass ... ... >&

受此启发,我尝试重写函数的命名空间:

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

文档中关于
\uu getitem\uuuuuuuuu
的第一句话是:“调用以实现对self[key]的求值。”您假设(希望)对
f.k1
的求值执行显式操作
f.\uu dict\uuuuuu[“k1”
。如果在
\uuu getitem\uuu
的第一行中放置一条print语句,您将看到此函数从未被代码调用


我不确定这是为什么,但我怀疑这是出于性能原因。无论如何,我想不出一个好的用例来替换函数的
\uuu dict\uuu
。有太多的方法可能会失败。

简短版本:因为CPython的
PyDict\u GetItem
函数不是子类友好的(说得清楚,它不是有意的;
PyDict\u*
函数都专门用于
PyDict\u对象本身,而不是通用映射)

长版本:检索属性将调用
PyObject\u GetAttr
。对于没有显式定义自定义
tp_getattro
tp_getattr
(and)的类,这将结束。假设在类本身上找不到任何东西,
PyObject\u GenericGetAttr
(实现它的私有API函数)
PyDict\u GetItem
明确使用
dict
的C级内部结构来执行访问,绕过您可能定义的任何自定义
\u GetItem\uuuuuuuu
。因此,您的自定义
\uuuu getitem\uuuu
永远不会被调用;实际上,您的
dict
子类只是一个
dict

我最初希望您能够通过使这个特定的案例工作,但结果表明,只有在调用了与
\uuu getitem\uuuuuuu
dict\u subscript
)相当的
\uu getitem\uuuuuuuuuuuuuuuuuuuuuuuuu)时,才调用它,而不是通过C级直接访问API,如
PyDict\u getitem
(它们根本不经过
dict\u subscript


基本上,CPython似乎已经做出了一个选择,将性能置于完全灵活性之上。任何用作
\uuuu dict\uuuu
dict
子类都将被访问,就好像它是一个普通的
dict
(如果子类正在做一些魔术来存储一个值,同时假装它存储一个不同的值,这可能会变得有点古怪,因为魔术被绕过了),所有不是
dict
子类的映射在赋值时都会被拒绝(当您尝试将它们赋值给
f.\uu dict\uu
时,会出现
TypeError
)。

为什么不重载呢?另外,除非我弄错了,否则
\uu getitem\uu
会重载索引
[]
操作符;我想你要找的是
\uuu getattribute\uu
@SeanBreckenridge-我不太确定。函数的
\uuuu getattribute\uuuu
(实际上,它的
tp\u getattro
槽)应该在dict上执行一个getitem。@SeanBreckenridge:要使(大多数)特殊方法(以
\uuuuu
开始和结束)工作,它们必须在类本身上定义,而不是每个实例。正如OP的链接问题的答案所指出的,函数本身就是共享的、不可变的类
函数
;您根本无法向类中添加自定义的
\uuuuuuGetAttr\uuuuuu
,即使添加
\uuuuuuGetAttr\uuuuuu
对每个实例都有效,也有相当大的机会在后续版本中得到修复以提高性能(因为特殊方法被记录为类级,而不是实例级,这不是一个大的后台兼容性问题).@ShadowRanger我明白了,这是有道理的。谢谢你的解释。两位反对者有什么反馈吗?我想这是因为
类型(f)
类型。FunctionType
和特殊方法没有在实例的字典中查找,而是在实例的类中查找。@martineau:这里没有涉及函数本身的特殊方法;我们用
dict
子类替换实例的
\uuu dict
,并且
dict
子类具有特殊的方法(在类上正确定义,而不是在实例上)。保罗走上了正确的道路;执行的操作大致类似于
dict.\uuuu getitem\uuuuuu(f.\uuu dict\uuuuu,'k1')
(这将绕过
\uu getitem\uuuuuu
重载),除了它不是真正的
\u getitem\u
它更像是
\u cpython\u internal\u getitem\u不能被覆盖\u并且不调用\u missing\u hook\u
。这并没有真正回答这个问题。这似乎是一个cpython的古怪
f.k2
在pypy上正常工作。在我看来,这似乎是CPython中的错误。他们使用PyDict_*API调用来访问dict,而不是抽象的PyMapping_*调用-因此,他们首先不应该允许将
\uuu dict\uuu
设置到子类。@wim如果他们使用
PyMapping*
调用,他们将允许任何任意映射,而不仅仅是
dict
子类。允许
dict
子类的目的是允许像
OrderedDict
defaultdict
Counter
这样的东西,它们不会覆盖
\uu getitem\uuuuuu
@wim:Agreed。一个可能证明这一点的怪癖是
PyDict\u CheckExact
(它将禁止子类)也将禁止
集合。OrderedDict
(它是一个子类,但从3.5开始与
dict
完全兼容,当时它是用C重新实现的,并且在很大程度上与
dict
兼容)。对
\uuu dict\uuuu
使用
OrderedDict
有点不错,因为这意味着属性按定义的顺序迭代(事实上,在3.6/3.7中,使
dict
本身有序是一个合理的选择,因为此功能有多好)。阻止
dict
子类将阻止3.5代码具有有序属性。@abarnert:也就是说,
defaultdict