Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should this method be a classmethod and why can't it access vars?

I come from java background, so I am slightly confused here.

Consider the code snippet below:

class A():
    def __init__(self, **kwargs):
        self.obj_var = "I am obj var"

    @classmethod
    def class_method(cls):
        print cls.obj_var   # this line is in question here
        cls.cls_obj = "I m class object"
        return cls.cls_obj

This throws an error :

In [30]: a = A()

In [31]: a.obj_var
Out[31]: 'I am obj var'

In [32]: a.class_method()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-32-3dcd9d512548> in <module>()
----> 1 a.class_method()

<ipython-input-29-9c0d341ad75f> in class_method(cls)
      8     @classmethod
      9     def class_method(cls):
---> 10         print cls.obj_var
     11         cls.cls_obj = "I m class object"
     12         return cls.cls_obj

AttributeError: class A has no attribute 'obj_var'

iIf I comment out @classmethod, this will work fine.

My understanding (which is incorrect m sure) :

When I do a = A(), then all the variables(obj_var) created in __init__ can be accessed via this a when passed to any other classmethod of same class.Apparently this is not the case.

Question(s)

  • why am i not able to access __init__ vars in class_method when decorater @classmethod is mentioned in the method but on removing the decorater, it works fine?

  • how python internally process this particular class upon compilation?

  • is there any way i can use @classmethod and the __init__ vars in same method?

like image 837
NoobEditor Avatar asked Jan 15 '16 05:01

NoobEditor


People also ask

What is the purpose of Classmethod in Python?

A class method is a method which is bound to the class and not the object of the class. They have the access to the state of the class as it takes a class parameter that points to the class and not the object instance. It can modify a class state that would apply across all the instances of the class.

What is the difference between @staticmethod and Classmethod?

Class method can access and modify the class state. Static Method cannot access or modify the class state. The class method takes the class as parameter to know about the state of that class. Static methods do not know about class state.

Can a class method access instance variables?

Class methods can access class variables and class methods directly. Class methods cannot access instance variables or instance methods directly—they must use an object reference. Also, class methods cannot use the this keyword as there is no instance for this to refer to.

Can a class method access instance variables Python?

Class methods don't need a class instance. They can't access the instance ( self ) but they have access to the class itself via cls . Static methods don't have access to cls or self . They work like regular functions but belong to the class's namespace.


1 Answers

Let's talk about how Python's methods actually work.

You may have noticed that Python methods are declared just like free-standing functions, but inside a class. That's because Python methods really are free-standing functions that happen to be inside a class. The self/cls argument is not special. It's just the first argument of the function.

Before we go any further, I'd like to point out that you don't appear to be explicitly inheriting from object. If you are working in Python 2.x, there is no object at the root of the graph unless you explicitly inherit from it. That is a bad thing, and you should inherit directly or indirectly from object whenever possible in new code. Inheriting from object in Python 3 is legal and harmless, but unnecessary. The rest of this discussion assumes that you are either working in 3.x or have fixed this.

When you access a variable, function, method, or any other type of object with foo.bar, certain hooks known as the "descriptor protocol" get invoked. You don't have to know the details of this to understand how functions work. All you have to know is this:

  1. First, if there's an instance variable bar directly attached to foo (and foo is not a class), we just return it directly.(*)
  2. If foo is a class and bar is a @classmethod function(**) declared either in foo or in one of its superclasses, then the first argument is set to foo before we return it. Otherwise, it is returned unchanged.(***) If we returned something, we stop here.
  3. We search the "method resolution order" of foo. This consists of foo's class (known as type(foo)), that class's superclass, and so on until we get to object. In cases of multiple inheritance, this gets a little more complicated, but again, you don't need to know that.
  4. Take the bar variable from the first class which has one (call it Baz).
  5. If Baz.bar is a regular, undecorated function, set its first argument to foo and return it.
  6. Otherwise, if Baz.bar is a @classmethod function, set its first argument to type(foo) and return it.
  7. Otherwise, if Baz.bar is a @staticmethod function, or not a function(**) at all, return it unchanged.

As you can see, if the method is declared @classmethod, the first argument is always the class, and never the instance, regardless of how the function is invoked. That means you don't have access to the instance variables of foo, since you don't have access to foo itself. Any variables set in __init__() are instance variables, so they are not visible here.

And here are all the things I lied about:

(*): Python actually does the rest of this work first and then comes back to this step. But that only matters for things like @property, which can actually override local instance variables. Regular methods cannot.

(**): This is a lie. Python also does special processing on @property functions in this case, and on anything else that implements certain special methods.

(***): I am also ignoring unbound methods in Python 2.x because, aside from certain really weird cases (when you try to pass an object of the wrong type as the first argument by hand), they make no difference whatsoever. They do not exist in Python 3.

like image 141
Kevin Avatar answered Sep 27 '22 20:09

Kevin