VPfB
VPfB

Reputation: 17247

What does classmethod do except changing self to cls?

There is an answered question about classmethod and property combined together: Using property() on classmethods

I still don't understand the cause of the problem, please help.

My understanding of classmethod was that it simply replaces self with cls. With this in mind I wrote several classmethods during the past few years and now I see I was wrong all that time.

So what is the difference between @classmethod and @cm from the code below?

def cm(func):
    def decorated(self, *args, **kwargs):
        return func(self.__class__, *args, **kwargs)
    return decorated

class C:
    V = 0 

    @property
    @classmethod
    def inc1(cls):
        cls.V += 1
        print("V1 =", cls.V)

    @property
    @cm 
    def inc3(cls):
        cls.V += 3
        print("V3 =", cls.V)

c = C() 
#c.inc1  # fails with:  TypeError: 'classmethod' object is not callable
c.inc3   # works

inc3 with cm works, but inc1 with classmethod does not.

Upvotes: 2

Views: 363

Answers (3)

Kir Chou
Kir Chou

Reputation: 3080

what is the difference between @classmethod and @cm from the code below?

decorator is calling during class creation time before an instance is created.

In your case, since @cm returns func(self.__class__, *args, **kwargs), which is relied on self, it should be used as a instance method.

On the other hand, @classmethod is able to use before an instance is created.

def cm(func):
    def decorated(self, *args, **kwargs):
        return func(self.__class__, *args, **kwargs)
    return decorated

class C:
    @classmethod
    def inc1(cls):
        (blablabla)
    @cm 
    def inc3(cls):
        (blablabla)

C().inc1() # works as a instance method
C.inc1()   # works as a classmethod
C().inc3() # works as a instance method
C.inc3()   # TypeError: unbound method decorated() must be called with C instance as first argument (got nothing instead)

For a combination of classmethod and property, it could be done by return an customized object. Reference

class ClassPropertyDescriptor(object):   
    def __init__(self, f):
        self.f = f
    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        return self.f.__get__(obj, klass)()

def classproperty(func):
    if not isinstance(func, (classmethod, staticmethod)):
        func = classmethod(func)    
    return ClassPropertyDescriptor(func)

class C:
    @classproperty
    def inc1(cls):
        (blablabla)

C.inc1   # works as a classmethod property

[Edit]

Q. What does the classmethod() call do with the method it decorates to achieve that?

The implementation can be done by using descriptor

class ClassMethodDescriptor(object):    
    def __init__(self, f):
        self.f = f
    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

def myclassmethod(func):
    return ClassMethodDescriptor(func)  

class C:
    @myclassmethod
    def inc1(cls):
        (blablabla)

C.inc1()   # works as a classmethod

Q. Why is the result not callable?

Because the implementation of ClassMethodDescriptor does not define __call__ function. Once using @property, it will return ClassMethodDescriptor which is not callable.

Upvotes: 2

Simon
Simon

Reputation: 424

The difference is that classmethod is not callable, and cm method is callable. This means that when the property(class) makes a call to the inputed func(which it is supposed to do), it works as you'll except for cm, but will not work for classmethod since classmethod does not have a call implemented.

Upvotes: 0

Alex
Alex

Reputation: 1151

class method does not know anything about instance and does not require it. instance method knows about it's instance and it's class.

class Foo:
    some = 'some'

class Bar(Foo):
    def __init__(self):
        self.some = 'not some'
    @classmethod
    def cls_some(cls):
        print(cls.some)
    def instance_some(self):
        print(self.some)



Bar.cls_some()
>>>some
Bar().instance_some()
>>>not some

Also as you can see you don't need an instance to call classmethod.

Upvotes: -1

Related Questions