Matias Cicero
Matias Cicero

Reputation: 26301

Cannot overwrite implementation for __call__

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?

Upvotes: 3

Views: 332

Answers (3)

khelwood
khelwood

Reputation: 59146

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'

Upvotes: 2

jedwards
jedwards

Reputation: 30230

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.

Upvotes: 1

Kewl
Kewl

Reputation: 3417

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)

Upvotes: 1

Related Questions