我在尝试在 Python 3.6 中对通用对象(例如 List[str]
)进行猴子修补时遇到了奇怪的行为。基本上,分配给通用对象的属性会导致该对象的所有实例都被修改。
from typing import List
list_str = List[str]
list_int = List[int]
list_str.foo = 1
list_int.foo = 2
print(list_str.foo) # 2 <-- WHAT?
print(list_int.foo) # 2
为什么会发生这种情况?我可以解决这个问题吗?
这不像 __getitem__
偷偷地返回同一个对象:
print(id(list_str)) # 2007605720376
print(id(list_int)) # 2007622803912
最佳答案
来自source code元类GenericMeta
的,这显然是故意的。在“完全构造”的任何实例(List[str]
而不是 List
)上设置属性时,该属性将被重定向到基类 (List
):
def __setattr__(self, attr, value):
# We consider all the subscripted generics as proxies for original class
if (
attr.startswith('__') and attr.endswith('__') or
attr.startswith('_abc_') or
self._gorg is None # The class is not fully created, see #typing/506
):
super(GenericMeta, self).__setattr__(attr, value)
else:
super(GenericMeta, self._gorg).__setattr__(attr, value)
应该补充的是,即使是普通的类属性,属性设置也会产生别名,而不仅仅是猴子修补。在类方法中设置类属性将为通用父类(super class)及其所有子类设置该属性。
另外,请注意 GenericMeta
将在 Python 3.7 中消失,但看起来 attribute aliasing will remain :
def __setattr__(self, attr, val):
if _is_dunder(attr) or attr in ('_name', '_inst', '_special'):
super().__setattr__(attr, val)
else:
setattr(self.__origin__, attr, val)
无论哪种情况,源代码都提供了一个明确的解决方法——使该属性成为一个dunder:
list_str = List[str]
list_int = List[int]
list_str.__foo__ = 1
list_int.__foo__ = 2
print(list_str.__foo__) # 1
print(list_int.__foo__) # 2
关于python - 无法对通用对象进行猴子修补,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48753816/