6 Inheritance
Methods
Often two classes may share attributes and other things in common. Writing two classes would likely be redundant and inefficient if you fix a bug in one for example. So instead we define a base class that includes the common functionality and then a
Example:
class Employee:
def get_monthly_payment(self) -> float:
"""
Return the amount that this Employee should be
paid in one month.
Round the amount to the nearest cent.
"""
raise NotImplementedError
def pay(self, pay_date: date) -> None:
"""
Pay this Employee on the given date and record the
payment.
(Assume this is called once per month.)
"""
payment = self.get_monthly_payment()
print(f'An employee was paid {payment} on {pay_date}.')
class SalariedEmployee(Employee):
pass
class HourlyEmployee(Employee):
pass
Here since we have not yet completed the get_monthly_payment function, it is called an abstract method also any class with an abstract method is an abstract class.
To finish we have:
class SalariedEmployee(Employee):
"""
An employee whose pay is computed based on an
annual salary.
"""
def get_monthly_payment(self) -> float:
"""
Return the amount that this Employee should be
paid in one month.
Round the amount to the nearest cent.
"""
return round(60000 / 12, 2)
class HourlyEmployee(Employee):
"""
An employee whose pay is computed based on an
hourly rate.
"""
def get_monthly_payment(self) -> float:
"""
Return the amount that this Employee should be
paid in one month.
Round the amount to the nearest cent.
"""
return round(160 * 20, 2)
Now when calling a method, python will look at the lowest subclass first for that function, even if that function is from a superclass,
example:
>>> type(fred)
<class 'employee.SalariedEmployee'>
so fred.get_monthly_payment()
will check methods in the SalariedEmployee class first. If it doesn't find anything it raises and AttributeError
.
Attributes and initializers
Since methods at the subclass level are prioritized, rather than override the superclass __init__
method, we would do the following:
class Employee:
"""
An employee of a company.
This is an abstract class. Only subclasses should be
instantiated.
=== Attributes ===
id_: This employee's ID number.
name: This employee's name.
"""
id_: int
name: str
def __init__(self, id_: int, name: str) -> None:
"""Initialize this employee.
Note: This initializer is meant for internal use
only;
Employee is an abstract class and should not be
instantiated directly.
"""
self.id_ = id_
self.name = name
class SalariedEmployee(Employee):
"""
An employee whose pay is computed based on an
annual salary.
=== Attributes ===
salary: This employee's annual salary
=== Representation invariants ===
- salary >= 0
"""
id_: int
name: str
salary: float
def __init__(self, id_: int, name: str, salary: float) -> None:
"""
Initialize this salaried Employee.
>>> e = SalariedEmployee(14, 'Fred Flintstone', 5200.0)
>>> e.salary
5200
"""
# Note that to call the superclass initializer, we need to use the
# full method name '__init__'. This is the only time you should write
# '__init__' explicitly.
Employee.__init__(self, id_, name)
self.salary = salary # extra attribute
If we use:
super().__int__(id_, name)
We don't have to pass self
So, subclasses only inherit methods; we have to directly call the method from the superclass to pass the attributes. In fact, abstract classes aren't meant to be initialized directly in the first place and are considered private. Therefore, they can be freely overridden so each subclass has a different initializer signature but will all call the superclass' __init__
method. We call the parent class the ancestor. The same thing can occur with other special methods. We can override, adopt, or extend ancestor methods by using them as helpers.
The Employee class represents the shared public interface of SalariedEmployee and HourlyEmployee. The public interface is how client code interacts with the class's methods and attributes. The Employee class is a polymorphism; it takes different forms than other classes. When an instance attribute is associated with another class, it is called composition.
Options for Inherited Method:
- Don't override
- Override abstract methods (mandatory)
- Complete override
- Use and extend
The object
Class and Python Special Methods
An object is not just theoretical but a class in Python. It is a superclass/ancestor of every other class.
Inherited special methods come from this class.