10

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.

Community
  • 1
  • 1
Hubert Kario
  • 21,314
  • 3
  • 24
  • 44
  • Could you make the 2-character difference between the outputs a little more obvious? I stared for three minutes trying to see the difference, but most peoples' brains does autocorrect on small errors. – cat Jul 23 '16 at 11:08
  • [Special method lookup](https://docs.python.org/3/reference/datamodel.html#special-lookup) – GingerPlusPlus Jul 23 '16 at 11:14
  • 2
    [answer](http://stackoverflow.com/a/34443595/5781248) tells that "a() does not call a.__call__. It calls type(a).__call__(a)" – J.J. Hakala Jul 23 '16 at 11:22

2 Answers2

12

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 
Community
  • 1
  • 1
Hubert Kario
  • 21,314
  • 3
  • 24
  • 44
5

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

GingerPlusPlus
  • 5,336
  • 1
  • 29
  • 52
  • so, to override a `__call__` I need to change the *type* of the instance? Is there a way to make `isinstance(a, A) == True` even after `__call__` is overridden? – Hubert Kario Jul 23 '16 at 11:19
  • yes, you can subclass `A`, like `class B(A): def __call__(self): return example`. Then, you can just create instance of `B` instead of `A`, or, if you're brave enough, `a.__class__ = B`. – GingerPlusPlus Jul 23 '16 at 11:22
  • Hmm, the `a.__class__ = B` may actually work (I need to override __call__ only on *some* not all instances of the base class), let me test. – Hubert Kario Jul 23 '16 at 11:24
  • Would it make a difference if A.\__call\__ actually turned around and did *return self.mycall()*? I sometimes do that when I only later decide to make classes callable directly. Then your monkey patch becomes a standard one. – JL Peyret Jul 23 '16 at 14:48
  • @JLPeyret If I'm doing a `mycall()` I could as well just call `mycall()` where I use it. I think it would also be harder to read, provided you know about the behaviour of Python with magic methods – Hubert Kario Jul 23 '16 at 18:05
  • Nope. What I mean is you implement call through a mycall method. Then you can just shift mycall around without worrying overmuch about patching a magic. At the class level, you can might even do **__call__ = mycall**. There is nothing really bizarre with a magic method calling something else to do its work, that might be a way for Strategy Pattern. Anyways, just a suggestion, though I'll keep in mind your problems with monkeypatching magics if I have to do that someday - regular patching is already enough of a hassle ;-) – JL Peyret Jul 23 '16 at 20:25
  • @JLPeyret Strategy Pattern won't help for my problem - I have a few runtime generators, and I want to dynamically change the objects after they are generated by the generators, but I need both unchanged and changed results at the same time. Moreover, I want the same modification for multiple generators. – Hubert Kario Jul 24 '16 at 10:48