python - 无法对通用对象进行猴子修补

标签 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

最佳答案

来自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/

相关文章:

Java 泛型 PECS

java - 要使用嵌套的通用集合或自定义中间类?

java - 为什么参数的私有(private)字段在 Java 6 中对泛型方法可见,但在 Java 7 中不可见?

python - 如何包装(猴子补丁)@classmethod

python - 如何猴子补丁Django?

python - 将 Pandas Dataframe 单元格中的嵌套数组值拆分为多行

python - 如何根据文件中的单词创建两个不同的列表?

javascript - 包装一系列闭包

python - 从字符串中删除前缀

python - 属性错误: 'str' object has no attribute 'n' when using dateutil