3

Take this as an example:

class Foo(object):
   def __init__(self, msg):
      self._msg = msg
   def __call__(self):
      return self._msg

foo = Foo('hello')
print(foo()) # Prints 'hello'
foo.__call__ = lambda _: 'bye'
print(foo()) # Prints 'hello'

I can reproduce this on both Python 2.x and Python 3.x

I was not able to find any relevant information on the documentation regarding this behavior.

This totally looks like a valid use case for me, specially when monkeypatching stuff.

Is there a reason this is not allowed?

Matias Cicero
  • 25,439
  • 13
  • 82
  • 154

3 Answers3

2

When you call an object with (), it executes the __call__ method defined on the object's type. So __call__ is defined on the class Foo, not on your instance foo. If you reassigned Foo.__call__, it would work.

Foo.__call__ = lambda _: 'bye'
print(foo()) # prints 'bye'
khelwood
  • 55,782
  • 14
  • 81
  • 108
  • I'm not sure what you mean by "instance methods", but the key issue here is that it's a "special" (dunder) method and `()` looks up `__call__` on the class. You can certainly override an individual instance's implementation of a "regular" method. – jedwards Jun 12 '18 at 12:26
1

Try this:

class Foo(object):
    def __init__(self, msg):
        self._msg = msg
    def __call__(self):
        return self._msg

foo = Foo('hello')
print(foo()) # Prints 'hello'

Foo.__call__ = lambda _: 'bye' #Notice the capital F, this is the class not the instance
print(foo()) # Prints 'bye'

The last call should print 'bye' like you expect. When you call an instance's functions, it's actually referring to the class functions (where they are defined)

Kewl
  • 3,327
  • 5
  • 26
  • 45
1

Typically, you can do this. Override a single instance's implementation of a given method without affecting the rest.

The problem here is that you're attempting to override a "special" method. The () call syntax looks up the __call__ method on the class, not the instance.

The following code shows that you can override a single instance's method implementation, as well as serves as sort of an ugly workaround to your problem:

class Foo(object):
    def __init__(self, msg):
        self._msg = msg
    def __call__(self):
        return self.call()      # Delegate call to instance
    def call(self):
        return self._msg

foo = Foo('hello')
other = Foo('hi')

print(foo()) # Prints 'hello'

def new_call(self):
    return "bye"

foo.call = new_call.__get__(foo, Foo)
print(foo()) # Prints 'bye'

print(other()) # Prints 'hi' (unaffected by override)

Note: the following also would work as you expect:

foo.call = (lambda _: "bye").__get__(foo, Foo)

But I prefer the explicit definition.

jedwards
  • 29,432
  • 3
  • 65
  • 92