Problem overview
A company pays every employee a salary. A manager is an employee too, but earns a bonus on top. You write two classes so that Manager reuses everything Employee has and changes only how its pay is worked out.
Employee(name, salary)keeps the name and salary,pay()returns the salary, anddescribe()returns text likeAna earns 3000.Manager(name, salary, bonus)inherits fromEmployee. Itspay()returns the parent's pay plus the bonus. It does not writedescribe()again.
For Employee("Ana", 3000) the description is Ana earns 3000. For Manager("Bob", 4000, 1000) it is Bob earns 5000, although only Employee contains the code of describe().
Approach: inherit, override one method
Writing class Manager(Employee) makes Manager a child of Employee. A manager object has every method the parent has. A child can override a method by defining one with the same name, and here it overrides only pay.
The clever part is describe. It builds its text from self.pay(), not from the salary attribute. When describe runs on a manager, self is the manager, so self.pay() finds Manager.pay, and the bonus appears in the text without a second describe. On an employee, the same line finds Employee.pay.
The same idea explains Manager.pay. It calls super().pay(), which runs the parent's version, and adds the bonus. The salary stays in the code that owns it. Writing self.pay() inside Manager.pay would be a mistake: it calls the same method again, and again, until Python gives up with a RecursionError.
Step by step
- In
Employee.__init__, store the name inself._name. The underscore marks it as private by convention. - Store the salary in
self._salary. payreturnsself._salary.describereturns the name, then" earns ", thenstr(self.pay()), joined with+.stris needed because+can't join text and a number.- In
Manager.__init__, callsuper().__init__(name, salary). The parent stores the name and the salary on this manager. - Store the bonus in
self._bonus. It is the one thingEmployeedoesn't know about. - Override
payto returnsuper().pay() + self._bonus.
The solution in Python
class Employee:
def __init__(self, name, salary):
self._name = name
self._salary = salary
def pay(self):
return self._salary
def describe(self):
return self._name + " earns " + str(self.pay())
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self._bonus = bonus
def pay(self):
return super().pay() + self._bonus
Using it:
people = [Employee("Ana", 3000), Manager("Bob", 4000, 1000),
Manager("Cleo", 5000, 0), Employee("Dan", 5000)]
for p in people:
print(p.describe())
Ana earns 3000
Bob earns 5000
Cleo earns 5000
Dan earns 5000
Cleo is a manager with a bonus of 0, so she earns exactly her salary, like Dan.
Python details
- The parent's constructor isn't called for you. If
Manager.__init__skippedsuper().__init__(...), a manager would have no_nameor_salary, anddescribe()would fail. super()takes no arguments inside a method. It gives access to the parent's methods.- Private by convention. The underscore doesn't stop a subclass from reading
self._salary. The lesson hasManagerask the parent'spay()instead, so onlyEmployeeneeds to know how an employee is paid.
Edge cases and common mistakes
- Calling
self.pay()insideManager.pay. The method calls itself forever and ends in aRecursionError. Usesuper().pay(). - Reading
self._salaryindescribe. It would work for employees, but a manager's description would leave out the bonus. - Joining text and a number.
self._name + " earns " + self.pay()raises aTypeError. Wrap the number instr. - Writing
describeagain inManager. Not needed: it is inherited, and it already calls the rightpay. - Complexity. Every method is a few operations, so each is O(1).
Testing
Employees and managers can come in any order, and each object keeps its own name and numbers:
Manager("Eve", 7000, 2500).describe()givesEve earns 9500.Employee("Finn", 2800).describe()givesFinn earns 2800.Employee("Gus", 3100).describe()givesGus earns 3100.Manager("Hana", 6000, 500).describe()givesHana earns 6500.
If a manager skips super().__init__, calling describe() fails with AttributeError: 'NoSuper' object has no attribute '_name' (the class name in the message is whatever your class is called).
