-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #485 from realpython/update_inheritance_composition
Add materials for inheritance-and-composition update
- Loading branch information
Showing
18 changed files
with
644 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Inheritance and Composition | ||
|
||
This folder provides the code examples for the Real Python tutorial [Inheritance and Composition: A Python OOP Guide](https://realpython.com/inheritance-composition-python/). | ||
|
||
Each of the folders contains the state of the code at the end of the three respective sections: | ||
|
||
- **`inheritance/`**: [An Overview of Inheritance in Python](https://realpython.com/inheritance-composition-python/#an-overview-of-inheritance-in-python) | ||
- **`composition/`**: [Composition in Python](https://realpython.com/inheritance-composition-python/#composition-in-python) | ||
- **`choosing/`**: [Choosing Between Inheritance and Composition in Python](https://realpython.com/inheritance-composition-python/#choosing-between-inheritance-and-composition-in-python) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from representations import AsDictionaryMixin | ||
|
||
|
||
class Address(AsDictionaryMixin): | ||
def __init__(self, street, city, state, zipcode, street2=""): | ||
self.street = street | ||
self.street2 = street2 | ||
self.city = city | ||
self.state = state | ||
self.zipcode = zipcode | ||
|
||
def __str__(self): | ||
lines = [self.street] | ||
if self.street2: | ||
lines.append(self.street2) | ||
lines.append(f"{self.city}, {self.state} {self.zipcode}") | ||
return "\n".join(lines) | ||
|
||
|
||
class _AddressBook: | ||
def __init__(self): | ||
self._employee_addresses = { | ||
1: Address("121 Admin Rd.", "Concord", "NH", "03301"), | ||
2: Address("67 Paperwork Ave", "Manchester", "NH", "03101"), | ||
3: Address("15 Rose St", "Concord", "NH", "03301", "Apt. B-1"), | ||
4: Address("39 Sole St.", "Concord", "NH", "03301"), | ||
5: Address("99 Mountain Rd.", "Concord", "NH", "03301"), | ||
} | ||
|
||
def get_employee_address(self, employee_id): | ||
address = self._employee_addresses.get(employee_id) | ||
if not address: | ||
raise ValueError(employee_id) | ||
return address | ||
|
||
|
||
_address_book = _AddressBook() | ||
|
||
|
||
def get_employee_address(employee_id): | ||
return _address_book.get_employee_address(employee_id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
from productivity import get_role | ||
from hr import get_policy | ||
from contacts import get_employee_address | ||
from representations import AsDictionaryMixin | ||
|
||
|
||
class _EmployeeDatabase: | ||
def __init__(self): | ||
self._employees = { | ||
1: {"name": "Mary Poppins", "role": "manager"}, | ||
2: {"name": "John Smith", "role": "secretary"}, | ||
3: {"name": "Kevin Bacon", "role": "sales"}, | ||
4: {"name": "Jane Doe", "role": "factory"}, | ||
5: {"name": "Robin Williams", "role": "secretary"}, | ||
} | ||
|
||
@property | ||
def employees(self): | ||
return [Employee(id_) for id_ in sorted(self._employees)] | ||
|
||
def get_employee_info(self, employee_id): | ||
info = self._employees.get(employee_id) | ||
if not info: | ||
raise ValueError(employee_id) | ||
return info | ||
|
||
|
||
class Employee(AsDictionaryMixin): | ||
def __init__(self, id): | ||
self.id = id | ||
info = employee_database.get_employee_info(self.id) | ||
self.name = info.get("name") | ||
self.address = get_employee_address(self.id) | ||
self._role = get_role(info.get("role")) | ||
self._payroll = get_policy(self.id) | ||
|
||
def work(self, hours): | ||
duties = self._role.perform_duties(hours) | ||
print(f"Employee {self.id} - {self.name}:") | ||
print(f"- {duties}") | ||
print("") | ||
self._payroll.track_work(hours) | ||
|
||
def calculate_payroll(self): | ||
return self._payroll.calculate_payroll() | ||
|
||
def apply_payroll_policy(self, new_policy): | ||
new_policy.apply_to_policy(self._payroll) | ||
self._payroll = new_policy | ||
|
||
|
||
employee_database = _EmployeeDatabase() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
class _PayrollSystem: | ||
def __init__(self): | ||
self._employee_policies = { | ||
1: SalaryPolicy(3000), | ||
2: SalaryPolicy(1500), | ||
3: CommissionPolicy(1000, 100), | ||
4: HourlyPolicy(15), | ||
5: HourlyPolicy(9), | ||
} | ||
|
||
def get_policy(self, employee_id): | ||
policy = self._employee_policies.get(employee_id) | ||
if not policy: | ||
return ValueError(employee_id) | ||
return policy | ||
|
||
def calculate_payroll(self, employees): | ||
print("Calculating Payroll") | ||
print("===================") | ||
for employee in employees: | ||
print(f"Payroll for: {employee.id} - {employee.name}") | ||
print(f"- Check amount: {employee.calculate_payroll()}") | ||
if employee.address: | ||
print("- Sent to:") | ||
print(employee.address) | ||
print("") | ||
|
||
|
||
class PayrollPolicy: | ||
def __init__(self): | ||
self.hours_worked = 0 | ||
|
||
def track_work(self, hours): | ||
self.hours_worked += hours | ||
|
||
|
||
class SalaryPolicy(PayrollPolicy): | ||
def __init__(self, weekly_salary): | ||
super().__init__() | ||
self.weekly_salary = weekly_salary | ||
|
||
def calculate_payroll(self): | ||
return self.weekly_salary | ||
|
||
|
||
class HourlyPolicy(PayrollPolicy): | ||
def __init__(self, hour_rate): | ||
super().__init__() | ||
self.hour_rate = hour_rate | ||
|
||
def calculate_payroll(self): | ||
return self.hours_worked * self.hour_rate | ||
|
||
|
||
class CommissionPolicy(SalaryPolicy): | ||
def __init__(self, weekly_salary, commission_per_sale): | ||
super().__init__(weekly_salary) | ||
self.commission_per_sale = commission_per_sale | ||
|
||
@property | ||
def commission(self): | ||
sales = self.hours_worked / 5 | ||
return sales * self.commission_per_sale | ||
|
||
def calculate_payroll(self): | ||
fixed = super().calculate_payroll() | ||
return fixed + self.commission | ||
|
||
|
||
class LTDPolicy: | ||
def __init__(self): | ||
self._base_policy = None | ||
|
||
def track_work(self, hours): | ||
self._check_base_policy() | ||
return self._base_policy.track_work(hours) | ||
|
||
def calculate_payroll(self): | ||
self._check_base_policy() | ||
base_salary = self._base_policy.calculate_payroll() | ||
return base_salary * 0.6 | ||
|
||
def apply_to_policy(self, base_policy): | ||
self._base_policy = base_policy | ||
|
||
def _check_base_policy(self): | ||
if not self._base_policy: | ||
raise RuntimeError("Base policy missing") | ||
|
||
|
||
_payroll_system = _PayrollSystem() | ||
|
||
|
||
def get_policy(employee_id): | ||
return _payroll_system.get_policy(employee_id) | ||
|
||
|
||
def calculate_payroll(employees): | ||
_payroll_system.calculate_payroll(employees) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
class _ProductivitySystem: | ||
def __init__(self): | ||
self._roles = { | ||
"manager": ManagerRole, | ||
"secretary": SecretaryRole, | ||
"sales": SalesRole, | ||
"factory": FactoryRole, | ||
} | ||
|
||
def get_role(self, role_id): | ||
role_type = self._roles.get(role_id) | ||
if not role_type: | ||
raise ValueError("role_id") | ||
return role_type() | ||
|
||
def track(self, employees, hours): | ||
print("Tracking Employee Productivity") | ||
print("==============================") | ||
for employee in employees: | ||
employee.work(hours) | ||
print("") | ||
|
||
|
||
class ManagerRole: | ||
def perform_duties(self, hours): | ||
return f"screams and yells for {hours} hours." | ||
|
||
|
||
class SecretaryRole: | ||
def perform_duties(self, hours): | ||
return f"does paperwork for {hours} hours." | ||
|
||
|
||
class SalesRole: | ||
def perform_duties(self, hours): | ||
return f"expends {hours} hours on the phone." | ||
|
||
|
||
class FactoryRole: | ||
def perform_duties(self, hours): | ||
return f"manufactures gadgets for {hours} hours." | ||
|
||
|
||
_productivity_system = _ProductivitySystem() | ||
|
||
|
||
def get_role(role_id): | ||
return _productivity_system.get_role(role_id) | ||
|
||
|
||
def track(employees, hours): | ||
_productivity_system.track(employees, hours) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from hr import calculate_payroll, LTDPolicy | ||
from productivity import track | ||
from employees import employee_database | ||
|
||
employees = employee_database.employees | ||
|
||
sales_employee = employees[2] | ||
ltd_policy = LTDPolicy() | ||
sales_employee.apply_payroll_policy(ltd_policy) | ||
|
||
track(employees, 40) | ||
calculate_payroll(employees) |
32 changes: 32 additions & 0 deletions
32
inheritance-and-composition/choosing/rectangle_square_demo.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
class Rectangle: | ||
def __init__(self, length, height): | ||
self._length = length | ||
self._height = height | ||
|
||
@property | ||
def area(self): | ||
return self._length * self._height | ||
|
||
def resize(self, new_length, new_height): | ||
self._length = new_length | ||
self._height = new_height | ||
|
||
|
||
class Square(Rectangle): | ||
def __init__(self, side_size): | ||
super().__init__(side_size, side_size) | ||
|
||
|
||
rectangle = Rectangle(2, 4) | ||
assert rectangle.area == 8 | ||
|
||
square = Square(2) | ||
assert square.area == 4 | ||
|
||
rectangle.resize(3, 5) | ||
assert rectangle.area == 15 | ||
|
||
square.resize(3, 5) | ||
print(f"Square area: {square.area}") | ||
|
||
print("OK!") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
class AsDictionaryMixin: | ||
def to_dict(self): | ||
return { | ||
prop: self._represent(value) | ||
for prop, value in self.__dict__.items() | ||
if not self._is_internal(prop) | ||
} | ||
|
||
def _represent(self, value): | ||
if isinstance(value, object): | ||
if hasattr(value, "to_dict"): | ||
return value.to_dict() | ||
else: | ||
return str(value) | ||
else: | ||
return value | ||
|
||
def _is_internal(self, prop): | ||
return prop.startswith("_") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
class Address: | ||
def __init__(self, street, city, state, zipcode, street2=""): | ||
self.street = street | ||
self.street2 = street2 | ||
self.city = city | ||
self.state = state | ||
self.zipcode = zipcode | ||
|
||
def __str__(self): | ||
lines = [self.street] | ||
if self.street2: | ||
lines.append(self.street2) | ||
lines.append(f"{self.city}, {self.state} {self.zipcode}") | ||
return "\n".join(lines) | ||
|
||
|
||
class AddressBook: | ||
def __init__(self): | ||
self._employee_addresses = { | ||
1: Address("121 Admin Rd.", "Concord", "NH", "03301"), | ||
2: Address("67 Paperwork Ave", "Manchester", "NH", "03101"), | ||
3: Address("15 Rose St", "Concord", "NH", "03301", "Apt. B-1"), | ||
4: Address("39 Sole St.", "Concord", "NH", "03301"), | ||
5: Address("99 Mountain Rd.", "Concord", "NH", "03301"), | ||
} | ||
|
||
def get_employee_address(self, employee_id): | ||
address = self._employee_addresses.get(employee_id) | ||
if not address: | ||
raise ValueError(employee_id) | ||
return address |
Oops, something went wrong.