【问题标题】:How to monkey patch a `__call__` method?如何猴子修补`__call__`方法?
【发布时间】:2016-11-27 05:13:04
【问题描述】:

我似乎无法修补类实例的 __call__ 方法(是的,我只想修补单个实例,而不是所有实例)。

以下代码:

class A(object):
    def test(self):
        return "TEST"

    def __call__(self):
        return "EXAMPLE"

a = A()
print("call method: {0}".format(a.__call__))
print("test method: {0}".format(a.test))
a.__call__ = lambda : "example"
a.test = lambda : "test"
print("call method: {0}".format(a.__call__))
print("test method: {0}".format(a.test))

print(a())
print("Explicit call: {0}".format(a.__call__()))
print(a.test())

输出这个:

call method: <bound method A.__call__ of <__main__.A object at 0x7f3f2d60b6a0>>
test method: <bound method A.test of <__main__.A object at 0x7f3f2d60b6a0>>
call method: <function <lambda> at 0x7f3f2ef4ef28>
test method: <function <lambda> at 0x7f3f2d5f8f28>
EXAMPLE
Explicit call: example
test

虽然我希望它输出:

...
example
Explicit call: example
test

我如何猴子补丁__call__()?为什么我不能像修补其他方法一样修补它?

虽然this answer 告诉了怎么做(据说我还没有测试过),但它并没有解释问题的为什么部分。

【问题讨论】:

  • 您能否让输出之间的 2 个字符的区别更明显一些?我凝视了三分钟,试图找出差异,但大多数人的大脑会自动纠正小错误。
  • answer 告诉我们“a() 不调用 a.__call__。它调用 type(a).__call__(a)”

标签: python monkeypatching


【解决方案1】:

所以,正如J.J. Hakala 评论的那样,Python 真正做的是调用:

type(a).__call__(a)

因此,如果我想覆盖 __call__ 方法,我必须覆盖一个类的 __call__,但如果我不想影响其他实例的行为同一个类,我需要用重写的__call__ 方法创建一个新类。

因此,如何覆盖 __call__ 的示例如下所示:

class A(object):
    def test(self):
        return "TEST"

    def __call__(self):
        return "EXAMPLE"

def patch_call(instance, func):
    class _(type(instance)):
        def __call__(self, *arg, **kwarg):
           return func(*arg, **kwarg)
    instance.__class__ = _

a = A()
print("call method: {0}".format(a.__call__))
print("test method: {0}".format(a.test))
patch_call(a, lambda : "example")
a.test = lambda : "test"
print("call method: {0}".format(a.__call__))
print("test method: {0}".format(a.test))

print("{0}".format(a()))
print("Explicit a.__call__: {0}".format(a.__call__()))
print("{0}".format(a.test()))

print("Check instance of a: {0}".format(isinstance(a, A)))

运行它会产生以下输出:

call method: <bound method A.__call__ of <__main__.A object at 0x7f404217a5f8>>
test method: <bound method A.test of <__main__.A object at 0x7f404217a5f8>>
call method: <bound method patch_call.<locals>._.__call__ of <__main__.patch_call.<locals>._ object at 0x7f404217a5f8>>
test method: <function <lambda> at 0x7f404216d048>
example
Explicit a.__call__: example
test
Check instance of a: True 

【讨论】:

    【解决方案2】:

    对于自定义类,特殊方法的隐式调用只有在对象类型上定义时才能保证正常工作,而不是在对象的实例字典中。这种行为是以下代码引发异常的原因:

    >>> class C:
    ...     pass
    ...
    >>> c = C()
    >>> c.__len__ = lambda: 5
    >>> len(c)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: object of type 'C' has no len()
    

    来源:https://docs.python.org/3/reference/datamodel.html#special-lookup

    【讨论】:

    • 那么,要覆盖 __call__ 我需要更改实例的 type 吗?即使在__call__ 被覆盖之后,有没有办法使isinstance(a, A) == True
    • 是的,你可以继承A,比如class B(A): def __call__(self): return example。然后,您可以只创建B 的实例而不是A,或者,如果您足够勇敢,可以创建a.__class__ = B
    • 嗯,a.__class__ = B 可能确实有效(我只需要在 some 上覆盖 call 而不是基类的所有实例),让我测试。
    • 如果 A._call_ 真的转身并 return self.mycall() 会有什么不同吗?我有时会这样做,当我后来才决定让类直接可调用时。然后你的猴子补丁就变成了标准的。
    • @JLPeyret 如果我正在使用mycall(),我也可以在我使用它的地方调用mycall()。我认为它也会更难阅读,前提是您了解 Python 的魔术方法的行为
    猜你喜欢
    • 2021-03-10
    • 2017-02-12
    • 2015-03-23
    • 2016-07-24
    • 2012-03-13
    • 2013-12-26
    • 2012-06-14
    • 2012-03-29
    • 2016-10-30
    相关资源
    最近更新 更多