decorate __call__ with @staticmethod
Asked Answered
T

2

19

Why can't I make a class' __call__ method static using the @staticmethod decorator?

class Foo(object):
    @staticmethod
    def bar():
        return 'bar'

    @staticmethod
    def __call__():
        return '__call__'

print Foo.bar()
print Foo()

outputs

bar
<__main__.Foo object at 0x7fabf93c89d0>

but I would expect it to output

bar
__call__
Throttle answered 7/11, 2014 at 3:32 Comment(0)
T
21

You need to override __call__ on the metaclass. The special methods defined in a class are for its instances, to change a class's special methods you need to change them in its class, i.e metaclass. (When you call Foo() usually the order is: Meta.__call__() --> Foo.__new__() --> Foo.__init__(), only if they return normally)

class Meta(type):
    @staticmethod 
    def __call__():
        return '__call__'


class Foo(object):
    __metaclass__ = Meta

    @staticmethod
    def bar():
        return 'bar'

print Foo()
#__call__

As you're trying to modify class instantiation, another way will be to override __new__ on the class itself and return __call__ from it(when __new__ returns something other than an instance the __init__ method is never called):

class Foo(object):

    def __new__(*args):
        #ignore the args 
        return '__call__'

    @staticmethod
    def bar():
        return 'bar'

print Foo()
#__call__
Tracheostomy answered 7/11, 2014 at 3:39 Comment(3)
Thanks! That clears it up for me. What do you mean by Meta.__call__() --> Foo.__new__() --> Foo.__init__()? Do the arrows mean "calls"?Throttle
@Throttle That's the order followed by Python when a class is instantiated. See: ideone.com/4gxeiNTracheostomy
Could the meta object be skipped by having the desired type inherit from type?Farron
K
2

I figured out this problem after having searched for a while but not finding an answer. Then, as I was about to submit my Q&A post to share, I found this post in the list of similarly asked questions. Since I did the work and it may help others find this post in the future, I'm adding it here as an answer.


QUESTION

Is it possible to make __call__ a class method or static method which can be used to execute the same instance of functionality multiple times? For example:

class MyClass:
  ...
  _phrase: str = None

  @classmethod
  def __call__(cls, phrase: str=None) -> str:
    ...
    if phrase is not None:
      ...# Implement a custom initialization and update
      cls._phrase = phrase

    elif (cls._phrase, phrase) == (None, None): 
      ...# Implement a default initialization
      cls._phrase = "Hello, world!"

    # Do any necessary work here

    return cls._phrase

print(MyClass())                  # Should print "Hello, world!" but prints address
                                  # <__main__.MyClass object at 0x000002069BC5BF70>

print(MyClass("Goodbye, y'all!")) # Should print "Goodbye, y'all!" but raises error
                                  # TypeError: MyClass() takes no arguments

ANSWER

The short answer is to use __new__ in place of __init__ and __call__. __new__ seems to have a couple important qualities which make it the best way to implement a static callable class. First, when __new__ is implemented, any __init__ or __call__ implementations are ignored, making them mutually exclusive. Second, __new__ is automatically a class method which is why implementing it must result in __init__ and __call__ being totally ignored. Here's how to implement it for the functionality I was needing:

class MyClass:
  ...
  _phrase: str = None

  def __new__(cls, phrase: str=None) -> str:
    ...
    if phrase is not None:
      ...# Implement a custom initialization and update
      cls._phrase = phrase

    elif (cls._phrase, phrase) == (None, None): 
      ...# Implement a default initialization
      cls._phrase = "Hello, world!"

    # Do any necessary work here

    return cls._phrase

print(MyClass())                  # Prints "Hello, world!"
print(MyClass("Goodbye, y'all!")) # Prints "Goodbye, y'all!"
print(MyClass())                  # Prints "Goodbye, y'all!"

Essentially, one __new__ class method can be used to do what __init__ and __call__ do for class instances.

Kindred answered 25/6, 2023 at 0:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.