不能猴子修补通用对象

时间:2018-02-12 19:12:31

标签: python generics monkeypatching

我遇到了奇怪的行为,试图在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

1 个答案:

答案 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