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