Skip to content

Commit

Permalink
Merge pull request #485 from realpython/update_inheritance_composition
Browse files Browse the repository at this point in the history
Add materials for inheritance-and-composition update
  • Loading branch information
KateFinegan authored Jan 12, 2024
2 parents b29ed3d + f32e756 commit e68d0c3
Show file tree
Hide file tree
Showing 18 changed files with 644 additions and 0 deletions.
9 changes: 9 additions & 0 deletions inheritance-and-composition/README.md
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)
41 changes: 41 additions & 0 deletions inheritance-and-composition/choosing/contacts.py
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)
52 changes: 52 additions & 0 deletions inheritance-and-composition/choosing/employees.py
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()
99 changes: 99 additions & 0 deletions inheritance-and-composition/choosing/hr.py
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)
52 changes: 52 additions & 0 deletions inheritance-and-composition/choosing/productivity.py
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)
12 changes: 12 additions & 0 deletions inheritance-and-composition/choosing/program.py
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 inheritance-and-composition/choosing/rectangle_square_demo.py
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!")
19 changes: 19 additions & 0 deletions inheritance-and-composition/choosing/representations.py
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("_")
31 changes: 31 additions & 0 deletions inheritance-and-composition/composition/contacts.py
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
Loading

0 comments on commit e68d0c3

Please sign in to comment.