0

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.

watchtower
  • 4,140
  • 14
  • 50
  • 92
  • 1
    It is important to understand that `self` and `cls` are merely conventional names here. They could be `foo` or `banana`. What makes them special is that they are the first argument, and thus they get implicitly passed the instance when called on the instance (and the class when you use the `classmethod` decorator) – juanpa.arrivillaga Jul 14 '18 at 01:21

2 Answers2

1

When you are working with instances of Sub1, Spam1's numInstances attribute is inaccessible (other than by explicitly writing Spam1.numInstances); cls in count() refers to Sub1, and the attribute is found in that class, so there is no need to look further up the inheritance chain.

When you are working with instances of Other1, the initial read of numInstances does come from Spam1 - but as soon as you assign a value, that goes into Other1 (because cls is Other1 now), and all further references to that name now find that instead of Spam1's version.

There are three distinct class attributes named numInstances in your code: two that exist as soon as the classes Spam1 and Sub1 are defined, and one that exists after the first instance of Other1 is created.

jasonharper
  • 9,450
  • 2
  • 18
  • 42
1

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.
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271