我遇到了奇怪的行为,试图在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
答案 0 :(得分:1)
来自元类GenericMeta
的{{3}},这显然是故意的。在“完全构造”(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)
应该补充一点,即使使用普通的class属性,属性设置也会别名,而不仅仅是猴子修补。在类方法中设置class属性将设置泛型超类及其所有子类的属性。
另请注意,{3.7}中的GenericMeta
将会消失,但看起来source code:
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