Search code examples
pythonmonkeypatching

How to monkey patch a `__call__` method?


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.


Solution

  • 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