Search code examples
pythonclassobjectself

cls vs. self vs. Class call in python


I am a beginner in Python, and using Lutz's book to understand classmethod, staticmethod and instancemethod. The objective of this code is to understand the difference among cls, self, and direct Class call (Spam1.numInstances) by counting number of instances created.

Here's an example derived from the book. I am unsure why parent class (Spam1) attribute (numInstances) doesn't increment when called through Sub1 and Other1, which are child classes.

Here's my code:

class Spam1:
    numInstances = 0
    def count(cls):
        cls.numInstances += 1
        print("In count -> number of instances: cls, Spam", cls.numInstances, Spam1.numInstances)

    def __init__(self):
        print("-----")
        print("In init, before -> number of instances: self, Spam",self.numInstances,Spam1.numInstances )
        self.count()
        print("In init, after -> number of instances: self, Spam",self.numInstances,Spam1.numInstances )
        print("-----")

    count=classmethod(count)


class Sub1(Spam1):
    numInstances = 0

class Other1(Spam1):
    pass

a=Spam1() #Output after increment: 1,1,1 (self, cls, Spam1)
b=Spam1() #Output after increment: 2,2,2 (self, cls, Spam1)
c=Spam1() #Output after increment: 3,3,3 (self, cls, Spam1)
d=Sub1()  #Output after increment: 1,1,3 (self, cls, Spam1)
e=Sub1()  #Output after increment: 2,2,3 (self, cls, Spam1)
f=Other1() #Output after increment: 4,4,3 (self, cls, Spam1)

I have spent one day trying to debug this code, but I cannot understand how cls.numInstances works because PyCharm would show "no reference" for cls.numInstances in debug mode. Being frustrated, I read a few SO threads: What does cls() function do inside a class method?, What is the 'cls' variable used for in Python classes?, and Python - self, no self and cls, but I cannot understand what's going on.

Specifically, here are my questions:

a) why is it that Spam1.numInstances doesn't increase when d, e, and f are created?

Here's my attempt to answer this question:

a.i) It is my understanding that cls is used to access class attributes. For d and e, self.numInstances is used to access instance attribute, which is zero because Sub1 zeroes the value of inherited attribute numInstances from Spam1. cls accesses class attribute of Sub1, which is also the same as attribute of Sub1 class. Hence, self and cls values we see in the output are of Sub1 instance and class respectively. Is my understanding correct?

a.ii) f inherits numInstances from Spam1. Hence, self.numInstances and cls.numInstances of f take the value from Spam1. Their value is incremented but not of Spam1 because cls refers to Other1 and because self refers to f, which is the object of Other1. Hence, Spam1's numInstances is never touched.

b) Is my understanding about the differences among self.numInstances, cls.numInstances, and Spam1.numInstances correct? If not, can someone please explain it?

I believe that my question is very basic. I hope someone will help me out. I am lost.


Solution

  • You've got a couple misunderstandings here:

    1. There is never an instance attribute named numInstances at any point in this code. self.numInstances checks for an instance attribute, but because nothing assigns to self.numInstances, there is no instance attribute to read, so accesses to self.numInstances end up reading the class attribute.
    2. f doesn't exactly "inherit" the parent class's value. When cls.numInstances += 1 is executed, it tries to look up Other1.numInstances, finds it doesn't exist, and checks superclasses, eventually finding Spam1.numInstances. It increments that value, then assigns back to Other1.numInstances (+= in Python always reassigns even if it does the work in place, and for immutable int, the work is not in place); in the future, accesses to Other1.numInstances won't check for Spam1.numInstances, as Other1's attribute now exists.