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

Unlock This Lesson

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

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

Implementing a Class Hierarchy

Give Feedback

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.

Zarata on April 15, 2020

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

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.

PercyBoomer on April 17, 2020

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

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?

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.

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 ;-)

davedanger on Nov. 16, 2020

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

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.

Become a Member to join the conversation.