【问题标题】:How to monkeypatch dunder methods to existing instances?如何对现有实例进行monkeypatch dunder方法?
【发布时间】:2020-12-26 12:20:35
【问题描述】:

上下文: 我想在我没有创建的对象上使用 heapq(和其他任何东西),这些对象本身没有 __lt__ 运算符。我可以吗? (没有包装类)。

班级:

class Node:
    def __init__(self, val):
        self.val = val

现在,在解释器运行时,我收到了一些对象集合。我想遍历它们,添加一个 dunder 方法(在我的例子中 lt),例如:

n = Node(4)
m = Node(5)

def myLT(self, other):
    return self.val < other.val

我尝试了什么:

n.__lt__ = types.MethodType(myLT, n)
m.__lt__ = types.MethodType(myLT, m)

还有

n.__lt__ = types.MethodType(myLT, n)
m.__lt__ = types.MethodType(myLT, n)

(如果绑定相同的仿函数会改善问题)

>>> n < m
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Node' and 'Node'

即使:

>>> n.__lt__(m)
True

我可以使用包装类,这在某些方面很糟糕(额外的内存和遍历代码变得更丑,但至少保持原始对象不变):

class NodeWrapper:
    def __init__(self, n):
        self.node = n
    def __lt__(self):
        return self.node.val

我只是想知道我在添加 dunder 方法时是否做错了,或者这在 python 3.x 中是否不起作用。如果这很重要,我正在使用 3.6.9。

【问题讨论】:

  • 老实说,包装器可能是您想要的。如果您想减少内存需求,请使用__slots__。

标签: python python-3.x monkeypatching dynamic-typing


【解决方案1】:

您可以尝试通过更改实例的__class__ 属性来修补dunder。正如文档部分 Special method lookup 中所述:

对于自定义类,特殊方法的隐式调用仅 如果在对象的类型上定义,则保证正常工作,而不是在 对象的实例字典。


def patch_call(instance, func, memo={}):
    if type(instance) not in memo:
        class _(type(instance)):
            def __lt__(self, *arg, **kwargs):
               return func(self, *arg, **kwargs)
        memo[type(instance)] = _

    instance.__class__ = memo[type(instance)]

patch_call(m, myLT)
patch_call(n, myLT)

n < m
# True

Modified from reference.

感谢 @juanpa.arrivilaga 推荐缓存类以提高性能。

【讨论】:

  • 你一定是在做一些不同的事情,否则我误解了。我尝试了上述方法并得到:TypeError: myLT() missing 1 required positional argument: 'other' on onlinegdb
  • @wheelreinventor 我的代码中出现复制粘贴错误,您现在可以再试一次吗?
  • 我将根据type(instance) 进行缓存,否则您将为每个实例创建一个类。这可能非常浪费
  • fwiw,我通过实验观察到,使用这种方法进行猴子修补的速度大约是两倍,并且给定参数与包装类相比,内存增加了约 25%。
  • @juanpa.arrivillaga 很有趣,这似乎解决了 OP 刚刚提到的性能问题。结果将如何被缓存?使用functools.lru_cache?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 2022-01-06
  • 2020-06-15
  • 1970-01-01
相关资源
最近更新 更多