How to monkey patch a `__call__` method?

只愿长相守 提交于 2021-02-07 02:42:17

问题


I don't seem to be able to monkey patch a __call__ method of class instance (and yes, I want to patch just single instances, not all of them).

The following code:

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())

Outputs this:

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

While I'd like it to output:

...
example
Explicit call: example
test

How do I monkeypatch __call__()? Why I can't patch it the same way as I patch other methods?

While this answer tells how to do it (supposedly, I haven't tested it yet), it doesn't explain the why part of the question.


回答1:


So, as J.J. Hakala commented, what Python really does, is to call:

type(a).__call__(a)

as such, if I want to override the __call__ method, I must override the __call__ of a class, but if I don't want to affect behaviour of other instances of the same class, I need to create a new class with the overriden __call__ method.

So an example of how to override __call__ would look like this:

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)))

Running it produces following output:

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:


For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary. That behaviour is the reason why the following code raises an exception:

>>> 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()

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



来源:https://stackoverflow.com/questions/38541015/how-to-monkey-patch-a-call-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!