2

I'm practicing the following code:

class MITPerson(Person): # a subclass of class Person

    nextIdNum = 0 # identification number

    def __init__(self, name):
        Person.__init__(self, name)
        self.idNum = MITPerson.nextIdNum 
        MITPerson.nextIdNum += 1 

    def getIdNum(self):
        return self.idNum

For a reference, here is the superclass:

class Person(object):  # superclass

    def __init__(self, name):
        """Create a person"""
        self.name = name

I thought that I've already known the answer of this question since I try the following instances:

p1 = MITPerson('Mark Guttag')
p2 = MITPerson('Billy Bob Beaver')
p3 = MITPerson('Billy Bob Beaver')

Not surprisingly, when I type these into console:

In[12]: p1.getIdNum()

Out[12]: 0

In[13]: p3.getIdNum()

Out[13]: 2

I've read this post and checked all the excellent answers here: Static class variables in Python

I saw that nextIdNum is assigned to 0 when the first instance p1 is created. What I feel weird is that why not p2 and p3 also bind nextIdNum to 0 again? In my imagination, the class variable should be reassigned to 0 once a class MITPerson is created without calling the method.

Did I miss something?

By the way, I've also go through the tutorial here: https://docs.python.org/3.6/tutorial/classes.html#class-objects

However, I'm afraid it does not give out the answer :(

cindy50633
  • 144
  • 2
  • 11
  • A *class variable* is an attribute of the class. It's not going to change unless you change the class object itself. – deceze Mar 28 '18 at 15:26

2 Answers2

9

I saw that nextIdNum is assigned to 0 when the first instance p1 is created.

This is wrong. nextIdNum is assigned to 0 when the class is defined.

Daniel
  • 42,087
  • 4
  • 55
  • 81
2

A class statement is a kind of wrapper around a single call to type. The statements in the body are evaluated, then added to a dict that is passed to type.

Yours is roughly equivalent to

def _init_(self, name):
    Person.__init__(self, name)
    self.idNum = MITPerson.nextIdNum
    MITPerson.nextIDNum += 1

def _getIdNum(self):
    return self.idNum

MITPerson = type(
    'MITPerson',
    (Person,),
    {
        '__init__': _init,
        'getIdNum': _getIdNum,
        'nextIdNum': 0
    }
)

del _init, _getIdNum

You can see that nextIdNum gets initialized to 0 immediately, before any instances of MITPerson are created, just like __init__ and getIdNum.


When you create an instance, the following steps occur:

  1. MITPerson('Mark Guttag') resolves to type.__call__(MITPerson, 'Mark Guttag').
  2. __call__ invokes MITPerson.__new__('Mark Guttag'), which creates a new instance of MITPerson.
  3. The new value is passed to MITPerson.__init__, at which point the current value of MITPerson.nextIdNum is used before incrementing the class variable.
  4. Once __init__ returns, __new__ returns that value, at which point it is assigned to p1.

Notice none of the code in the body of the class statement is executed again, although the function __init__ defined there is.

chepner
  • 497,756
  • 71
  • 530
  • 681