in123321
in123321

Reputation: 103

Mutable class variables and the behavior of super().__init__

I'm trying to understand why my code is behaving the way it is. Basically I've created a parent and child class in two ways. What I'd like to happen is that on each instance that is created for the two classes, the init method will update the count of num_of_emps for each respective class. So if I have something like:

class Employee:

    num_of_emps = 0

    def __init__(self, first, last, pay, division):
        self.first = first
        self.last = last
        self.pay = pay
        self.division = division
        Employee.num_of_emps += 1

class Manager(Employee):

    num_of_emps = 0

    def __init__(self, first, last, pay, division):
        self.first = first
        self.last = last
        self.pay = pay
        self.division = division
        Manager.num_of_emps += 1

emp1 = Employee("Dallas", "Mat", 100000, "Sales")
emp2 = Employee("Mark", "Doe", 50000, "Marketing")
emp3 = Employee("Jane", "Doe", 50000, "Advertisement")
man1 = Manager("Bob", "Ben", 200000, "Marketing")
man2 = Manager("Ryan", "DS", 200000, "Advertisement")

# Employee.num_of_emps returns 3
# Manager.num_of_emps returns 2

This is what I want. However, if I add a super.init() method rather than hard coding the attributes of the Manager class, something else happens:

class Employee:

    num_of_emps = 0

    def __init__(self, first, last, pay, division):
        self.first = first
        self.last = last
        self.pay = pay
        self.division = division
        Employee.num_of_emps += 1

class Manager(Employee):

    num_of_emps = 0

    def __init__(self, first, last, pay, division):
        super().__init__(first, last, pay, division)
        Manager.num_of_emps += 1

emp1 = Employee("Dallas", "Mat", 100000, "Sales")
emp2 = Employee("Mark", "Doe", 50000, "Marketing")
emp3 = Employee("Jane", "Doe", 50000, "Advertisement")
man1 = Manager("Bob", "Ben", 200000, "Marketing")
man2 = Manager("Ryan", "DS", 200000, "Advertisement")

# Employee.num_of_emps returns 5
# Manager.num_of_emps returns 2

What's confusing me is the parent classes' num_of_emps is also getting updated each time a manager instance is being created. I know the super().init method is causing this but can someone explain why?

Upvotes: 0

Views: 522

Answers (2)

EmilianoJordan
EmilianoJordan

Reputation: 121

It's because super().init() calls the super init method. And in that method you increment the Manager count. This is a problem with inheritance. A better way to do this might be to segment your classes better.

Additionally two other suggestions were given in another answer that are better structured than the code sample I'm giving to try and show how to compartmentalize your problem as simply as possible.

class Person:

    def __init__(self):
        # Some stuff
        pass


class Employee(Person):

    counter = 0

    def __init__(self):
        super().__init__()
        # Some stuff
        Employee.counter += 1
        pass


class Manager(Person):

    counter = 0

    def __init__(self):
        super().__init__()
        # Some stuff
        Manager.counter += 1
        pass

This will avoid triggering the counter attribute on the base class more than once.

Upvotes: 1

bruno desthuilliers
bruno desthuilliers

Reputation: 77912

You already had the answer to the "why" part so I won't come back to it.

For the solution, in your case, just avoiding hardcoding the class name (using type(self) instead to get the current instance's class) solves the issue (nb: if you consider that updating the Employee counter is an issue):

>>> class Base(object):
...     counter = 0
...     def __init__(self):
...         print("base.__init__")
...         type(self).counter += 1
... 
>>> class Child(Base):
...     pass
... 
>>> c = Child()
base.__init__
>>> Base.counter
0
>>> Child.counter
1

A cleaner solution would be to factor out the counter incrementation in a classmethod so you can both have the expected default yet override it if needed, ie:

>>> class Base(object):
...     counter = 0
...     def __init__(self):
...         self.inc_counter()
...     @classmethod
...     def inc_counter(cls):
...         cls.counter += 1
... 
>>> class Child(Base):
...     pass
... 
>>> c = Child()
>>> Child.counter
1
>>> Base.counter
0

Upvotes: 1

Related Questions