diff --git a/inheritance-and-composition/README.md b/inheritance-and-composition/README.md new file mode 100644 index 0000000000..9949feed82 --- /dev/null +++ b/inheritance-and-composition/README.md @@ -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) diff --git a/inheritance-and-composition/choosing/contacts.py b/inheritance-and-composition/choosing/contacts.py new file mode 100644 index 0000000000..5fc329d032 --- /dev/null +++ b/inheritance-and-composition/choosing/contacts.py @@ -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) diff --git a/inheritance-and-composition/choosing/employees.py b/inheritance-and-composition/choosing/employees.py new file mode 100644 index 0000000000..ca4ea55492 --- /dev/null +++ b/inheritance-and-composition/choosing/employees.py @@ -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() diff --git a/inheritance-and-composition/choosing/hr.py b/inheritance-and-composition/choosing/hr.py new file mode 100644 index 0000000000..804614e2b1 --- /dev/null +++ b/inheritance-and-composition/choosing/hr.py @@ -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) diff --git a/inheritance-and-composition/choosing/productivity.py b/inheritance-and-composition/choosing/productivity.py new file mode 100644 index 0000000000..a40e5d7800 --- /dev/null +++ b/inheritance-and-composition/choosing/productivity.py @@ -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) diff --git a/inheritance-and-composition/choosing/program.py b/inheritance-and-composition/choosing/program.py new file mode 100644 index 0000000000..eca2e9cc16 --- /dev/null +++ b/inheritance-and-composition/choosing/program.py @@ -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) diff --git a/inheritance-and-composition/choosing/rectangle_square_demo.py b/inheritance-and-composition/choosing/rectangle_square_demo.py new file mode 100644 index 0000000000..9366fddb9c --- /dev/null +++ b/inheritance-and-composition/choosing/rectangle_square_demo.py @@ -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!") diff --git a/inheritance-and-composition/choosing/representations.py b/inheritance-and-composition/choosing/representations.py new file mode 100644 index 0000000000..ee9e87d7d7 --- /dev/null +++ b/inheritance-and-composition/choosing/representations.py @@ -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("_") diff --git a/inheritance-and-composition/composition/contacts.py b/inheritance-and-composition/composition/contacts.py new file mode 100644 index 0000000000..5ae9ef7d98 --- /dev/null +++ b/inheritance-and-composition/composition/contacts.py @@ -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 diff --git a/inheritance-and-composition/composition/employees.py b/inheritance-and-composition/composition/employees.py new file mode 100644 index 0000000000..21928126b4 --- /dev/null +++ b/inheritance-and-composition/composition/employees.py @@ -0,0 +1,46 @@ +from productivity import ProductivitySystem +from hr import PayrollSystem +from contacts import AddressBook + + +class EmployeeDatabase: + def __init__(self): + self._employees = [ + {"id": 1, "name": "Mary Poppins", "role": "manager"}, + {"id": 2, "name": "John Smith", "role": "secretary"}, + {"id": 3, "name": "Kevin Bacon", "role": "sales"}, + {"id": 4, "name": "Jane Doe", "role": "factory"}, + {"id": 5, "name": "Robin Williams", "role": "secretary"}, + ] + self.productivity = ProductivitySystem() + self.payroll = PayrollSystem() + self.employee_addresses = AddressBook() + + @property + def employees(self): + return [self._create_employee(**data) for data in self._employees] + + def _create_employee(self, id, name, role): + address = self.employee_addresses.get_employee_address(id) + employee_role = self.productivity.get_role(role) + payroll_policy = self.payroll.get_policy(id) + return Employee(id, name, address, employee_role, payroll_policy) + + +class Employee: + def __init__(self, id, name, address, role, payroll): + self.id = id + self.name = name + self.address = address + self.role = role + self.payroll = payroll + + 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() diff --git a/inheritance-and-composition/composition/hr.py b/inheritance-and-composition/composition/hr.py new file mode 100644 index 0000000000..32e628254a --- /dev/null +++ b/inheritance-and-composition/composition/hr.py @@ -0,0 +1,67 @@ +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 diff --git a/inheritance-and-composition/composition/productivity.py b/inheritance-and-composition/composition/productivity.py new file mode 100644 index 0000000000..428b12f668 --- /dev/null +++ b/inheritance-and-composition/composition/productivity.py @@ -0,0 +1,41 @@ +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." diff --git a/inheritance-and-composition/composition/program.py b/inheritance-and-composition/composition/program.py new file mode 100644 index 0000000000..94ef796802 --- /dev/null +++ b/inheritance-and-composition/composition/program.py @@ -0,0 +1,13 @@ +from hr import PayrollSystem, HourlyPolicy +from productivity import ProductivitySystem +from employees import EmployeeDatabase + +productivity_system = ProductivitySystem() +payroll_system = PayrollSystem() +employee_database = EmployeeDatabase() +employees = employee_database.employees +manager = employees[0] +manager.payroll = HourlyPolicy(55) + +productivity_system.track(employees, 40) +payroll_system.calculate_payroll(employees) diff --git a/inheritance-and-composition/inheritance/disgruntled.py b/inheritance-and-composition/inheritance/disgruntled.py new file mode 100644 index 0000000000..cf3f13a950 --- /dev/null +++ b/inheritance-and-composition/inheritance/disgruntled.py @@ -0,0 +1,7 @@ +class DisgruntledEmployee: + def __init__(self, id, name): + self.id = id + self.name = name + + def calculate_payroll(self): + return 1_000_000 diff --git a/inheritance-and-composition/inheritance/employees.py b/inheritance-and-composition/inheritance/employees.py new file mode 100644 index 0000000000..c9a84946fd --- /dev/null +++ b/inheritance-and-composition/inheritance/employees.py @@ -0,0 +1,38 @@ +from hr import SalaryPolicy, CommissionPolicy, HourlyPolicy +from productivity import ManagerRole, SecretaryRole, SalesRole, FactoryRole + + +class Employee: + def __init__(self, id, name): + self.id = id + self.name = name + + +class Manager(Employee, ManagerRole, SalaryPolicy): + def __init__(self, id, name, weekly_salary): + SalaryPolicy.__init__(self, weekly_salary) + super().__init__(id, name) + + +class Secretary(Employee, SecretaryRole, SalaryPolicy): + def __init__(self, id, name, weekly_salary): + SalaryPolicy.__init__(self, weekly_salary) + super().__init__(id, name) + + +class SalesPerson(Employee, SalesRole, CommissionPolicy): + def __init__(self, id, name, weekly_salary, commission): + CommissionPolicy.__init__(self, weekly_salary, commission) + super().__init__(id, name) + + +class FactoryWorker(Employee, FactoryRole, HourlyPolicy): + def __init__(self, id, name, hours_worked, hour_rate): + HourlyPolicy.__init__(self, hours_worked, hour_rate) + super().__init__(id, name) + + +class TemporarySecretary(Employee, SecretaryRole, HourlyPolicy): + def __init__(self, id, name, hours_worked, hour_rate): + HourlyPolicy.__init__(self, hours_worked, hour_rate) + super().__init__(id, name) diff --git a/inheritance-and-composition/inheritance/hr.py b/inheritance-and-composition/inheritance/hr.py new file mode 100644 index 0000000000..c779304cdc --- /dev/null +++ b/inheritance-and-composition/inheritance/hr.py @@ -0,0 +1,35 @@ +class PayrollSystem: + 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()}") + print("") + + +class SalaryPolicy: + def __init__(self, weekly_salary): + self.weekly_salary = weekly_salary + + def calculate_payroll(self): + return self.weekly_salary + + +class HourlyPolicy: + def __init__(self, hours_worked, hour_rate): + self.hours_worked = hours_worked + 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): + super().__init__(weekly_salary) + self.commission = commission + + def calculate_payroll(self): + fixed = super().calculate_payroll() + return fixed + self.commission diff --git a/inheritance-and-composition/inheritance/productivity.py b/inheritance-and-composition/inheritance/productivity.py new file mode 100644 index 0000000000..6f1e28f99a --- /dev/null +++ b/inheritance-and-composition/inheritance/productivity.py @@ -0,0 +1,28 @@ +class ProductivitySystem: + def track(self, employees, hours): + print("Tracking Employee Productivity") + print("==============================") + for employee in employees: + result = employee.work(hours) + print(f"{employee.name}: {result}") + print("") + + +class ManagerRole: + def work(self, hours): + return f"screams and yells for {hours} hours." + + +class SecretaryRole: + def work(self, hours): + return f"expends {hours} hours doing office paperwork." + + +class SalesRole: + def work(self, hours): + return f"expends {hours} hours on the phone." + + +class FactoryRole: + def work(self, hours): + return f"manufactures gadgets for {hours} hours." diff --git a/inheritance-and-composition/inheritance/program.py b/inheritance-and-composition/inheritance/program.py new file mode 100644 index 0000000000..232aa645b7 --- /dev/null +++ b/inheritance-and-composition/inheritance/program.py @@ -0,0 +1,22 @@ +import hr +import employees +import productivity + +manager = employees.Manager(1, "Mary Poppins", 3000) +secretary = employees.Secretary(2, "John Smith", 1500) +sales_guy = employees.SalesPerson(3, "Kevin Bacon", 1000, 250) +factory_worker = employees.FactoryWorker(4, "Jane Doe", 40, 15) +temporary_secretary = employees.TemporarySecretary(5, "Robin Williams", 40, 9) +employees = [ + manager, + secretary, + sales_guy, + factory_worker, + temporary_secretary, +] + +productivity_system = productivity.ProductivitySystem() +productivity_system.track(employees, 40) + +payroll_system = hr.PayrollSystem() +payroll_system.calculate_payroll(employees)