Is it bad form to call a classmethod as a method from an instance?
Asked Answered
P

7

53

Ex.

If I have something like this:

class C(object):
    @classmethod
    def f(cls, x):
       return x + x

This will work:

c = C()

c.f(2)
4

But is that bad form? Should I only call

C.f()

or

c.__class__.f()

Obviously, this would only make sense in cases where f doesn't interact with self/cls expecting it to be class.

?

Punishable answered 28/3, 2009 at 2:21 Comment(1)
OT, but prefer type(c) to c.__class__Salisbarry
H
39

If you are tempted to call a class method from an instance you probably don't need a class method.

In the example you gave a static method would be more appropriate precisely because of your last remark (no self/cls interaction).

class C(object):
    @staticmethod
    def f(x):
       return x + x

this way it's "good form" to do both

c = C()
c.f(2)

and

C.f(2)
Hautboy answered 28/3, 2009 at 7:35 Comment(2)
Yes but what if you really do need a class method?Feverwort
The entire point of the @classmethod decorator is to make it so you can call it as an instance.Klotz
R
18

I don't recall using a classmethod like this from outside the class, but it is certainly ok for an instance method to call a classmethod on itself (e.g. self.foo() where foo is a classmethod). This makes sure that inheritance acts as expected, and will call .foo() in the right subclass instead of the base class.

Razorback answered 28/3, 2009 at 4:28 Comment(0)
I
5

It's mainly just confusing looking. If I were using your class and saw this, it would make me wonder what other surprises are in there, it just looks like bad design.

Is there a reason it's not just a staticmethod?

Inveterate answered 28/3, 2009 at 2:29 Comment(0)
L
3

C.f() is clearer than c_instance.f(), and c_instance.__class__.f() is just ugly. Since clarity and beauty are dearly loved characteristics in the python community, I'd tend to say that C.f() is the best route.

Is there any particular reason you even want to call it in either of the other ways?

Lisandra answered 28/3, 2009 at 2:31 Comment(0)
F
3

I came across this where I was calling some classmethods (that need to be classmethods so that I still have reference to the class) from a non-classmethod, like the following.

class A:
  def a(self, number):
    print("a", self, number)
    self.b(number)

  @classmethod
  def b(cls, number):
    print("b", cls, number + 1)
    cls.c(number)

  @classmethod
  def c(cls, number):
    print("c", cls, number * 2)



b = A()
b.a(3) 

The above code produces the following result:

a <__main__.A object at 0x000001FAC09FE358> 3
b <class '__main__.A'> 4
c <class '__main__.A'> 6

I'm not saying that it's the best convention, but it doesn't break anything in Python 3.6

Farnsworth answered 11/10, 2018 at 16:49 Comment(0)
R
2

If you have an instance of C already, why do you need f() to be a class method? Not only is it bad form, its usually not necessary. Someone on the net says: "This is bad because it creates the impression that some instance variables in the object are used, but this isn't the case."

Although, page 484 of learning python notes that you can call the method either way and it will be exactly the same as long as you pass the same instance in.

Rahmann answered 28/3, 2009 at 2:41 Comment(1)
You don't need classmethod-on-instance for the given example code, but if you started subclassing C, you might.Ahriman
K
0

I think the question is that of style, which is to say, if you want a class method, should you call it as a class method MyClass.my_func(), or as an instance method myinst.my_func(). On of the of documented explinations of the @classmethod decorator is that it will allow you to call the class method as if it was an instance method. I have included an example showing this below.

As for style, I think it should not be confusing to people either way, it really is the method implementation detail. If you are calling the class method in the context of an instance (i.e. as a user of a class instance object , or inside class instance methods), then calling class method as just another instance method is perfectly okay, and maybe even preferred to not highlighting some implementation detail about the method being called.

What would I do? If I was passing an instance attribute into a classmethod, I'd probably call the classmethod in the MyClass.my_func(self.some_value), rather then calling self.my_func(self.some_value) because seeing the latter makes me wonder why my_func isn't reading self.some_value itself, instead of making me pass it along. Otherwise I really don't mind either form.

Of course, classes are meant to be instantiated, and the biggest faux pas is programmers using them as some sort of namespace for stateless methods.

class A:
    value = 2

    def __init__(self, value):
        self.value = value
    
    def add_instance(self, number):
        self.value += number


    @classmethod
    def add_class(cls, number):
        cls.value += number


    def add_class_via_instance(self, number):
        self.add_class(number)

    def add_class_via_class(self, number):
        A.add_class(number)

    def add_class_via_sneak(self, number):
        self.__class__.value += number



a = A(1)
print(f'{A.value=} {a.value=}')
a.add_instance(3)
print(f'{A.value=} {a.value=}')
A.add_class(3)
print(f'{A.value=} {a.value=}')
a.add_class_via_class(3)
print(f'{A.value=} {a.value=}')
a.add_class_via_instance(3)
print(f'{A.value=} {a.value=}')
a.add_class_via_sneak(3)
print(f'{A.value=} {a.value=}')

returns

A.value=2 a.value=1 # expect class and instance have different value values
A.value=2 a.value=4 # adding to the instance
A.value=5 a.value=4 # adding to the class
A.value=8 a.value=4 # calling the function as a class method works on the class attribute
A.value=11 a.value=4 # calling the function as a instance method works on the class attribute as well
A.value=14 a.value=4 # instance methods can access class values and therefore can behave as a classmethod if they want.
Klotz answered 4/7, 2023 at 4:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.