8

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?

smci
  • 32,567
  • 20
  • 113
  • 146
NoobEditor
  • 15,563
  • 19
  • 81
  • 112
  • 1
    Why are you using `classmethod` if you want an instance method? – user2357112 Jan 15 '16 at 06:17
  • What you are calling "`__init__` vars" are instance attributes. The reason a classmethod is called a classmethod is that it only has access to the class, not the instance. Why are you using `classmethod` at all? – BrenBarn Jan 15 '16 at 06:21
  • @BrenBarn : i am taking `__init__` as a constructor, wherein, once set, values can be used anywhere in the class methods... – NoobEditor Jan 15 '16 at 06:24
  • @NoobEditor: *Why are you using* `classmethod`? What do you think the purpose of `classmethod` is? It sounds like you should read [the Python tutorial](https://docs.python.org/tutorial) to understand how to use classes in Python. – BrenBarn Jan 15 '16 at 06:25
  • @user2357112 : might sound super dumb...but what the difference between `classmethod` and `instance` method? Accessing method via `object` is instance and doing `A(). class_method()` is class s method? – NoobEditor Jan 15 '16 at 06:26
  • @BrenBarn : i did go through the documentation mate, thats always Step 1, what i am trying to do is come out of _java_ _shell_, in java `a=A()` `a.method()` `A().method()` is more or less same, but apparently not in python...this is what had me confused....makes sense? – NoobEditor Jan 15 '16 at 06:29
  • 3
    @NoobEditor: Those are more or less the same, but again, you have not in any way explained why you are using `classmethod`. You seem to be thinking that a classmethod is just any method you define in a class, but that is not the case. You might want to look at [this question](http://stackoverflow.com/questions/38238/what-are-class-methods-in-python-for) and [this question](http://stackoverflow.com/questions/11788195/module-function-vs-staticmethod-vs-classmethod-vs-no-decorators-which-idiom-is). If you want access to instance variables, don't use `classmethod`. That's pretty much it. – BrenBarn Jan 15 '16 at 06:42
  • 1
    Python classmethod is akin to Java static method – David Heffernan Jan 15 '16 at 06:42
  • 1
    @DavidHeffernan: *No they are not*. Java static methods do not participate in polymorphism. They cannot override or be overridden by other methods, static or otherwise, and cannot be polymorphically called at runtime without a whole lot of reflection. Python has none of these restrictions. – Kevin Jan 15 '16 at 07:02
  • 4
    @Kevin I said "akin to", not "the same as". Remember that asker is a novice who thinks that classmethod is an instance method. In terms of a simple and helpful analogy, my comment was reasonable. A novice has no chance of making any sense of what you just said. Whilst I know all of what you said, do you think the asker can understand that yet? Think about the learning process. Your answer is pitched at totally the wrong level for the asker. – David Heffernan Jan 15 '16 at 07:20
  • **Because you can't just access instance variables as if they were class variables; otherwise if you had 2+ instances, how would the class know which instance you meant to reference?!** b) You can't do this in Java classmethod either. c) Code smell: so if you can't implement a method as classmethod, then it's an instance method, not a classmethod... – smci Dec 15 '20 at 02:51

2 Answers2

6

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.

Kevin
  • 28,963
  • 9
  • 62
  • 81
  • late...i know!! – NoobEditor Jan 19 '19 at 18:39
  • I'd preface this with the one-liner "because you can't just access instance variables as class variables; otherwise if you had 2+ instances, how would the clas know which instance you meant to reference?!". Anyway this answer is overkill, the caveat "in Python 2.x, classes scould inherit from `object`, you should move that to a footnote. And the long part about MRO is overkill. OP is simply confused that they can't access instance variables as if they were class variables. – smci Dec 15 '20 at 02:50
6

If you want to access an instance variable from a class method you need to create an instance of the class where the instance variable is present. Hope below code should work.

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

@classmethod
def class_method(cls):
    self = cls() # Here you are creating an instance of the class (in this case it is class A)
    print(self.obj_var) 
    cls.cls_obj = "I m class object"
    return cls.cls_obj

 print(A.class_method())
  • Good solution, but you can't use the __init__ variables like below the code. `class A(): def __init__(self, ff, **kwargs): self.ff = ff self.obj_var = "I am obj var" @classmethod def class_method(cls): self = cls() # Here you are creating an instance of the class (in this case it is class A) print(self.obj_var) print("FF", self.ff) cls.cls_obj = "I m class object" return cls.cls_obj print(A.class_method())` – muthukumar Feb 12 '23 at 12:27
  • **Output:** `Traceback (most recent call last): File "", line 14, in File "", line 8, in class_method TypeError: A.__init__() missing 1 required positional argument: 'ff' > ` – muthukumar Feb 12 '23 at 12:28
  • I did something else, which might be stupid. It works: ```@classmethod def class_method(cls,obj,var): self=obj return self.var+var res = A(A,6) – Louis Jun 21 '23 at 13:25