Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Implementing a Class Hierarchy

00:00 Now that you understand the basics of inheritance and composition, it’s time to apply them to a real project. Throughout the rest of this course, you are going to build a simple application that could be used by a human resources department to track employees’ productivity and calculate their payrolls. As you code, you’ll learn about some new object-oriented programming techniques and you’ll get some invaluable practice with inheritance and composition.

00:33 I am here inside of Visual Studio Code, working within a directory called HR, and I’ve got open a blank Python file called hr.py.

00:45 The first class we are going to build is called PayrollSystem. This class will be in charge of calculating the payrolls for all of the employees in this imaginary company.

00:58 The PayrollSystem will not have any attributes. Instead, it will just declare and implement a method called .calculate_payroll(), which will accept a collection of employees as a parameter.

01:15 Now, I’ll just print 'Calculating Payroll' and I’ll insert a divider here to separate the rest of the output.

01:26 I’m going to use a for loop to iterate through the collection of employees, and for each employee I’ll print the f-string f'Payroll: for {employee.id} - {employee.name}'.

01:48 And now, let’s print another f-string that calls the .calculate_payroll() method of each employee object to determine their paycheck amount.

02:01 I’ll print a blank line just to keep things separated. Okay, this is a good start, but we haven’t defined what it means to be an employee. According to this class, employees must have attributes for .name and .id, and they must implement a .calculate_payroll() method.

02:23 That’s because this class utilizes all of those members. The rest of this part of the program will follow from this UML diagram that you’re familiar with by now.

02:37 Any class that implements this IPayrollCalculator is one that can be passed into this PayrollSystem. That’s because the PayrollSystem requires the same members listed in the IPayrollCalculator interface.

02:56 Let’s build each class one by one, starting with the base Employee class.

03:03 I’ll create a new class here called Employee. Because this is the base class of everything else, it won’t explicitly inherit from anything, but remember that it’s still inheriting from that object superclass behind the scenes.

03:21 According to our UML diagram, this Employee must contain two instance methods, .id and .name. I’ll create an .__init__() method with parameters for id and name, and then assign those to the instance attributes.

03:42 Now, let’s create our first specialized employee class. The first class is SalaryEmployee, which will act just like a regular employee, except they will also have a .weekly_salary instance attribute that will be used in their .calculate_payroll() method.

04:02 So first, I will create the class, and we want to make sure we’re inheriting from Employee. At this point, SalaryEmployee is functionally identical to Employee, but that’s pretty boring. Just like before, I’m going to create an .__init__() method, but this time I will also accept a weekly_salary as a parameter.

04:29 Now, we’re accepting an id, a name and a weekly_salary, but we haven’t done anything with it yet. The first thing we should do is initialize the parent class’s .__init__() method.

04:44 This will allow us to access the instance methods declared in the parent from within the child. If we were to omit the .__init__() method in the child, it would automatically inherit everything from the parent.

04:59 But once we declare this .__init__() method in the child, it covers up all of the instance attributes in the parent, and we must set them manually by calling the .__init__() method in the parent.

05:13 We can do that with the super() function, just like this. Now, any SalaryEmployee object can also be used as an Employee object,

05:25 but we still have to deal with this .weekly_salary attribute. After all, this is a specialized version of Employee. That will be a new instance attribute.

05:39 In order for this class to conform to IPayrollCalculator, which is required to be used by the PayrollSystem class, it must also implement a .calculate_payroll() method that defines how a SalaryEmployee is paid. To do that, I’ll create the .calculate_payroll() method, passing in self as a parameter.

06:05 And I will simply return the .weekly_salary instance attribute. This seems kind of trivial now, but later on, you’ll see why it’s important that we returned this instance attribute through a method.

06:21 Every time something asks a SalaryEmployee to calculate their payroll, they will return their .weekly_salary value through this method.

06:31 At this point, you might be wondering, “Why do we need to call the .__init__() method of the parent when we instantiate a child class?” The reason for that is because the child class inherits the attributes of the parent, but it can’t use them unless it initializes them with some value.

06:52 Like I mentioned before, if we didn’t call the parent’s .__init__() method with the required arguments but we did include our own .__init__() method in this child class, then our SalaryEmployee would just have a .weekly_salary attribute, but no .name or .id.

07:09 It wouldn’t really be an Employee, either, and so it wouldn’t conform to the IPayrollCalculator interface. Again, if you don’t declare an .__init__() method in the child class, then it will use its parent’s .__init__() method by default and call that when you try to instantiate the child class.

07:31 But if you do declare an .__init__() method in the child class, that will override the .__init__() method in the parent, so you usually want to call the parent’s .__init__() method manually, either by name or with the super() function, like we did here.

07:48 Next, let’s create the HourlyEmployee, which will also inherit from Employee. This type of employee is paid by the hour, and so it will also need two additional instance attributes: .hours_worked and .hour_rate.

08:07 Just like before, I will call the .__init__() method of the parent so we satisfy the Employee requirements, name and id.

08:18 Now we can create these two new instance attributes, which are unique to the HourlyEmployee objects. Finally, we just need to implement the .calculate_payroll() method.

08:33 Each time the HourlyEmployee object is told to calculate their payroll, they should return the number of hours they’ve worked times their hourly rate.

08:48 The last specialized class we need to create is the CommissionEmployee. This class is a little special, though, and that’s because a commission employee receives both a commission and a weekly salary—lucky them! Because they are technically also a salary employee, we want to make them inherit from SalaryEmployee instead of the base Employee class. They share so much in common, so we might as well.

09:20 This class will need an id, a name, a weekly_salary, and a commission.

09:29 I’m going to initialize the SalaryEmployee class, which we told to also initialize the Employee class. This way, our CommissionEmployee will be a SalaryEmployee and an Employee.

09:46 The .__init__() method for the SalaryEmployee requires an id, a name, and a weekly_salary, so I’ll pass those values in.

09:57 I’m also going to create a new instance attribute for the commission. All that’s left to do now is implement the .calculate_payroll() method.

10:08 A commission employee’s pay should be the sum of their commissions plus the weekly salary that they receive, but the .weekly_salary is not an instance attribute of CommissionEmployee.

10:23 It’s an instance attribute of its parent, SalaryEmployee. We initialized the .weekly_salary, so we can use it, but instead of accessing that attribute directly with the super() function, let’s think about a better way to do this.

10:40 This CommissionEmployee is a SalaryEmployee, and so when it calculates the salary part of its pay, it should do so in the same way as a normal SalaryEmployee.

10:53 That being said, let’s create a new variable called fixed, which will store the .weekly_salary value obtained by calling the parent class’s .calculate_payroll() method. Now, this might seem a little bit strange because right now the parent class’s .calculate_salary() method just returns the .weekly_salary attribute. I mean, we could have just accessed that directly. But think about this: what if the way we calculate the SalaryEmployee salary changes in the future?

11:28 That would mean that we’d change the implementation of its .calculate_payroll() method in the SalaryEmployee class, and because a CommissionEmployee is a SalaryEmployee, we’d want that change to trickle down to the CommissionEmployee too.

11:46 By making the CommissionEmployee obtain its .weekly_salary component through the SalaryEmployee class’s .calculate_payroll() method, we’ve future-proofed this relationship from any changes in the parent.

12:00 Finally, we want to return this fixed salary amount, plus the current .commission value.

12:08 Great. Now, we’ve created a program that models this UML diagram I have onscreen.

12:15 All that’s left to do is test it by creating some objects. To do that, I’m first going to create a new file called program.py. This will be the entry point of our HR program, and so this is the script we should always run.

12:34 The first thing we want to do is bring the other classes into this namespace, which we can do by simply importing hr. Make sure hr.py and program.py are in the same directory, or folder,

12:51 or you might run into some issues. The first kind of employee we’re going to create is the SalaryEmployee. We’ll get that object with hr.SalaryEmployee passing in an id of 1, a name of 'John Smith', and a weekly_salary of 1500 dollars.

13:16 I’ll create the HourlyEmployee object the same way, except this employee will work 40 hours a week at a rate of 15 dollars per hour. For the CommissionEmployee, I want them to get a base salary of 1000 dollars a week, plus 250 dollars in commissions.

13:38 Now, all that’s left to do is to create the PayrollSystem and pass in these Employee objects to its .calculate_payroll() method.

13:48 We need to make sure we pass in these employees in the form of a list, so the PayrollSystem’s .calculate_payroll() method can iterate through them and calculate their payrolls individually.

14:02 I will run this program and we get exactly the output we were expecting.

14:08 Each employee has had their payroll calculated.

Avatar image for Zarata

Zarata on April 15, 2020

Should “weekly_salary” show up in the UML diagram somewhere? (I’m 7 min into video)

Avatar image for Austin Cepalia

Austin Cepalia RP Team on April 17, 2020

@Zarata it should! weekly_salary is an instance attribute of SalaryEmployee, so it should be on the UML diagram in that class. That looks to be a small error in the original article, which unfortunately trickled down to my course.

Avatar image for Percy Boomer

Percy Boomer on April 17, 2020

@Zarata,it would seem we have scope creep for this project. I think you’re right.

Avatar image for KoJans

KoJans on May 11, 2020

If I run the code for hr.py as in ‘Implementing a Class Hierarchy’ on my system I get an error: TypeError: calculate_payroll() missing 1 required positional argument: 'employees' If I leave out the ‘self’ argument in calculate_payroll in the class PayrollSystem: def calculate_payroll(employees) the code works as expected. So I think the ‘self’ argument should not be in the method?

Avatar image for Roy Telles

Roy Telles on Aug. 12, 2020

@KoJans I just received the same error. I fixed it, but the implementation that gave me the error was this:

class CommissionEmployee(SalaryEmployee):
    def __init__(self, id, name, weekly_salary, commission):
        super().__init__(self, id, name, weekly_salary)
        self.commission = commission

    def calculate_payroll(self):
        fixed = super().calculate_payroll()
        return fixed + self.commission

Notice how the CommissionEmployee class passes self into the super().__init__ call. The error is generated because if you recall, that with super() all we need to pass are the necessary arguments to the parent function. self is essentially “extra”, and is already “implemented” in the parent’s __init__ constructor method.

TL;DR: Yes, self should be left off of the argument list when making super() calls.

Avatar image for Dan B

Dan B on Nov. 13, 2020

What motivates the decision to create a separate PayrollSystem class rather than putting the calculate_payroll method into the Employee class?

Asking for a friend ;-)

Avatar image for davedanger

davedanger on Nov. 16, 2020

Are you perhaps actually an AI? Explanations are decelerated just the right amount, understandibly methodical, and communicated perfectly. Thanks!

Avatar image for davedanger

davedanger on Nov. 16, 2020

The above was stated in the spirit of humor. These tutorials in particular, clear up a lot of the mystery concerning classes and inheritance.

Avatar image for pyglet

pyglet on Jan. 6, 2021

I’m a bit late to the party, but have the exact same issue as @KoJans: My program.py only works if I delete self from the def calculate_payroll() in the PayrollSystem class.

Does anyone mind explaining why? (it’s unclear to me whether @Roy Telles’ TL;DR meant yes, you need to delete self in super().calculate_payroll() or in the PayrollSystem class method definition.)

Avatar image for pyglet

pyglet on Jan. 7, 2021

Oh boy, found the mistake I was making: I instantiated hr.PayrollSystem instead of hr.PayrollSystem().

Avatar image for alazejha

alazejha on May 23, 2021

I work on Jupyter notebook, and save both hr.py and program.py in one virtual directory. when I run the program.py it stops at the first line, with error; no module named hr.

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on May 24, 2021

@alazejha What do you mean by a “virtual” directory?

Avatar image for alazejha

alazejha on May 24, 2021

Sorry if I’m not correctly decribing this, I run in shell:

python3 -m venv env
source env/bin/activate
jupyter notebook

and then I work in popped up jupyter notebook. I saved hr.py, and wanted to import it, within program.py, and it says that there’s no module hr.

Avatar image for alazejha

alazejha on May 24, 2021

Ah, I see that it’s automatically saved as hr.ipynb. How can I save it as .py?

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on May 25, 2021

@alazejha You can’t. Jupyter notebooks aren’t the same as Python source files. If you take a look at what’s inside, then you’ll see a JSON document with your Jupyter Notebook cells. You can create regular Python files separately and import them to your notebook, though.

Avatar image for bob

bob on Oct. 24, 2021

It is confusing when the UML diagram does not match the implementation.

Please update the UML diagram to include the following attributes,

* SalaryEmployee: weekly_salary
* HourlyEmployee: hours_worked
* HourlyEmployee: hour_rate
* CommissionEmployee: commission

and also include the PayrollSystem class.

Avatar image for rafalcode

rafalcode on Jan. 11, 2022

@Dan B: What motivates the decision to create a separate PayrollSystem class rather than putting the calculate_payroll method into the Employee class?

I expect it’s the big boss of the HR department. He/She arrives and says I want you to build me a payroll calculator, so it makes sense to looking like you’re making quick progress by starting with that class first.

Avatar image for muondude

muondude on Jan. 13, 2022

If I want to reference the parent class through

super().__init__(...)

in say the salaried employee subclass, but only cared about the employee ID, can I just use:

super().__init__(id)

or am I required to grab all the parent class attribute when using super()?

Another question: Let’s say I have a hierarchy where I want to compute a total as the sum of quantities computed by a method in each of the subclasses. Can I put the method to compute this sum in the parent class? It seems like I can’t since the parent isn’t aware of the children, whereas the children know their parent, or is this just my confusion? Or can the parent reference the children much like the car and engine example given?

I am asking since I’m currently working on a project and trying to determine a good class hierarchy for my code. My project this hierarchy is an example of aggregation. Each subclass can exist independently of the parent class, but the parent can’t exist without at least one subclass.

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on Jan. 14, 2022

@muondude When you use super() to call a method defined in your parent class, then you have to pass all the required arguments even if you only care about one of them. When doing multiple inheritance in Python, it’s a common practice to declare your method signatures with the additional *args and **kwargs arguments that can work as a proxy, for example:

class Dad:
    def __init__(self, eyes, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.eyes = eyes

class Mom:
    def __init__(self, hair, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.hair = hair

class Child(Dad, Mom):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

child = Child(eyes="blue", hair="blonde")

As to your second question, it sounds like you’re trying to use the wrong tool. Inheritance models the relationship between types, while you probably want to keep a register of all the instances created at runtime and calculate their total sum. You can take advantage of Python’s duck typing to do so:

>>> class Foo:
...     def subtotal(self):
...         return 42

>>> class Bar:
...     def subtotal(self):
...         return 24

>>> register = [Foo(), Bar(), Foo(), Foo()]
>>> total = sum(x.subtotal() for x in register)
>>> total
150

It doesn’t matter if Foo and Bar belong to the same type hierarchy. They could be entirely unrelated types like in the example above.

Avatar image for muondude

muondude on Jan. 17, 2022

Wow. Thank you. That really helps. I appreciate these examples. As you can tell I’m winding my way through trying to learn OOP.

Regarding the total example, the context is I’m modeling noise in a sensor. There are different types of noise, each with its own unique model. I want to keep the models for each type separate. I’m trying to figure out if I need a parent class (Noise) for this set of specific noise classes. The parameters that define each noise model are read in from a JSON file using a mixin class that provides the method to read this file and create a parent object that holds all these parameters. I was thinking that the total method would be provided by the Noise parent class. I’m working on a UML diagram for this so I can post it if you think that might help.

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on Jan. 18, 2022

@muondude You’re welcome! Thanks to duck typing in Python, you most likely don’t need a parent class, but it might depend on what you’re trying to achieve. The UML diagrams would help me better understand your objective. Anyway, it sounds to me as if you’re coming from another object-oriented programming language, and you’re now trying to project its patterns onto Python, which may not be the most Pythonic or the most straightforward way to go.

Avatar image for muondude

muondude on Feb. 23, 2022

Bartosz: Are you on Slack (if yes ping me there)? Perhaps that might be a better place to continue the discussion. If you have another approach (e.g., email) I’m open. I can share my UML diagrams and more details on what I am trying to accomplish.

Sorry for the late reply. – Sam

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on Feb. 23, 2022

@muondude I am on Slack, but we don’t do 1:1 support. You’re better off asking your question and providing the context to a wider audience using one of the channels on Slack. Someone else may be able to give you a better answer or give it to you sooner.

Avatar image for rschuster3

rschuster3 on Dec. 20, 2022

Obviously it wasn’t intentional, but as a femme techie I found it darkly humorous that even in this hypothetical example the woman gets paid less.

Avatar image for Martin Breuss

Martin Breuss RP Team on Jan. 2, 2023

Ugh… Good point rschuster3 😢 Art imitating life or life imitating art?

Avatar image for jchurchi

jchurchi on Nov. 17, 2023

Please review my code comment here below. If I commented out the first indented line and uncommented the second it works the same and still gets the weekly salary from the same parent class so why call super again when I’ve already set it in the __init__ function. Am I wrong to assume it works the exact same way. Nothing gained using super (nothing lost either I suppose). I’m just curious.

def calculate_payroll(self): fixed = super().calculate_payroll() # fixed = self.weekly_salary # The above works too so I'm not sure what he has gained # by doing it using "super().calculate_payroll()". return fixed + self.commission

Become a Member to join the conversation.