0

Why is it permitted to call a class method from an instance? How is it useful to do this?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • 1
    It's not so much "is there a use case" as "is the behavior defined", and it is: a class method always receives a class as its first argument. When invoked from a class, that class is passed. When invoked from an instance, the type of that instance is passed. – chepner Sep 23 '22 at 21:19
  • If I invoke the class method with the instance, what would be passed in the cls argument of the method, the class or the instance? – Shiva Gupta Sep 23 '22 at 21:24
  • 2
    The class will be passed. The instance is only used to look up the method. The `@classmethod` decorator ensures that the underlying function has the class passed to it; and it knows which class to use because of the lookup process. – Karl Knechtel Sep 23 '22 at 21:25
  • See also: [How do I disallow a classmethod from being called on an instance?](/questions/42322936). – Karl Knechtel Sep 23 '22 at 21:33

3 Answers3

2

Suppose we use @classmethod to create an abstract factory:

class Base:
    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c

    @classmethod
    def from_string(cls, data):
        print(f'Making a {cls.__name__} instance from {data!r}.')
        a, b, *c = data.split()
        return cls(a, b, c)

class Derived(Base):
    pass

Testing it:

>>> d = Derived.from_string('the quick brown fox jumps over the lazy dog')
Making a Derived instance from 'the quick brown fox jumps over the lazy dog'.
>>> d.c
['brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog']

By using an instance, we can create an instance that is known to be of the same type as the one we invoked:

>>> d2 = d.from_string('pack my box with five dozen liquor jugs')
Making a Derived instance from 'pack my box with five dozen liquor jugs'.
>>> assert(type(d2) is type(d)) # passes

Whether looked up directly in the class, or from an instance, @classmethod ensures that the class is passed as cls.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
1

It's useful when you have instances of different classes, and the classes have different implementations of the same class method. Calling instance.method() will call the implementation appropriate for that instance's class. You can think of it as a shortcut for

type(instance).method()

Example:

class Class1:
    @classmethod
    def method(cls):
        return "Class 1"

class Class2:
    @classmethod
    def method(cls):
        return "Class 2"

instance = Class1() if random.randint(1, 2) == 1 else Class2()
print(instance.method()) # Prints Class 1 or Class 2
Barmar
  • 741,623
  • 53
  • 500
  • 612
1

Suppose you've got a class that has six different methods on it. Five of them use self and do something complicated. But the sixth one just returns a constant, because it never changes based on the instance. It would be annoying if I had to remember to call

my_object.get_value()
my_object.frobnicate()
my_object.ping(100)
my_object.foobar(variable)
my_object.add(10)
type(my_object).version_id()

So, for consistency, it makes sense to be able to call that last one (which depends only on the class) on instances as well.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116