python是少数支持多重继承的现代编程语言之一。多重继承是同时从多个基类派生一个类的能力
多重继承的名声很坏,以至于大多数现代编程语言都不支持它。相反,现代编程语言支持接口的概念。在这些语言中,您从单个基类继承,然后实现多个接口,因此您的类可以在不同的情况下重用
这种方法给您的设计带来了一些限制。您只能通过直接派生一个类来继承该类的实现。您可以实现多个接口,但不能继承多个类的实现
这个约束对软件设计是有好处的,因为它迫使您在设计类时减少相互之间的依赖。您将在本文后面看到,您可以通过组合利用多个实现,这使得软件更加灵活。然而,这一节是关于多重继承的,所以让我们来看看它是如何工作的
事实证明,有时临时秘书是在有太多文书工作要做的时候才被雇佣的。临时秘书类在生产力系统的上下文中扮演秘书的角色,但出于工资单的目的,它是HourlyEmployee
派生自Secretary:您可以派生自Secretary,以继承角色的.work()方法,然后覆盖.calculate_payroll()方法,将其实现为HourlyEmployee
从HourlyEmployee派生:您可以从HourlyEmployee派生以继承.calculate_payroll()方法,然后重写.work()方法以将其实现为秘书
# In employees.py
class TemporarySecretary(Secretary, HourlyEmployee):
pass
Python允许您通过在类声明中的括号之间指定它们来从两个不同的类继承
现在,您修改程序以添加新的临时秘书员工
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)
company_employees = [
manager,
secretary,
sales_guy,
factory_worker,
temporary_secretary,
]
productivity_system = productivity.ProductivitySystem()
productivity_system.track(company_employees, 40)
payroll_system = hr.PayrollSystem()
payroll_system.calculate_payroll(company_employees)
运行程序
$ python program.py
Traceback (most recent call last):
File ".\program.py", line 9, in
temporary_secretary = employee.TemporarySecretary(5, 'Robin Williams', 40, 9)
TypeError: __init__() takes 4 positional arguments but 5 were given
您会收到一个TypeError异常,该异常表示应有4个位置参数,但给出了5个
这是因为您首先从秘书中派生了TemporarySecretary,然后从HourlyEmployee中派生了,所以解释器试图使用Secretary .__ init __()来初始化对象。
好吧,我们扭转一下
class TemporarySecretary(HourlyEmployee, Secretary):
pass
运行程序
$ python program.py
Traceback (most recent call last):
File ".\program.py", line 9, in
temporary_secretary = employee.TemporarySecretary(5, 'Robin Williams', 40, 9)
File "employee.py", line 16, in __init__
super().__init__(id, name)
TypeError: __init__() missing 1 required positional argument: 'weekly_salary'
现在看来,您缺少了一个周秘书参数,该参数对于初始化局长是必需的,但是在TemporarySecretary的上下文中该参数没有意义,因为它是HourlyEmployee
也许实现TemporarySecretary .__ init __()会有所帮助
# In employees.py
class TemporarySecretary(HourlyEmployee, Secretary):
def __init__(self, id, name, hours_worked, hour_rate):
super().__init__(id, name, hours_worked, hour_rate)
try it
$ python program.py
Traceback (most recent call last):
File ".\program.py", line 9, in
temporary_secretary = employee.TemporarySecretary(5, 'Robin Williams', 40, 9)
File "employee.py", line 54, in __init__
super().__init__(id, name, hours_worked, hour_rate)
File "employee.py", line 16, in __init__
super().__init__(id, name)
TypeError: __init__() missing 1 required positional argument: 'weekly_salary'
这也不管用。好了,现在是深入研究Python的方法解析顺序(MRO)的时候了,看看发生了什么
当访问类的方法或属性时,Python使用类MRO来查找它。super()还使用MRO来确定调用哪个方法或属性。您可以使用Python super()在Supercharge类中了解关于super()的更多信息
from employees import TemporarySecretary
TemporarySecretary.__mro__
(,
,
,
,
,
)
MRO显示Python查找匹配的属性或方法的顺序。在示例中,这就是我们创建TemporarySecretary对象时发生的情况
调用TemporarySecretary .__ init __(self,id,name,hours_worked,hour_rate)方法
super().__ init __(id,name,hours_worked,hour_rate)调用与HourlyEmployee .__ init __(self,id,name,hour_worked,hour_rate)
HourlyEmployee调用super().__ init __(id,name),MRO将与秘书匹配。秘书.__ init __(),它继承自SalaryEmployee .__ init __(self,id,name,weekly_salary)
由于参数不匹配,因此引发TypeError异常
您可以通过反转继承顺序并直接调用HourlyEmployee .__ init __()来绕过MRO,如下所示
class TemporarySecretary(Secretary, HourlyEmployee):
def __init__(self, id, name, hours_worked, hour_rate):
HourlyEmployee.__init__(self, id, name, hours_worked, hour_rate)
这就解决了创建对象的问题,但是在尝试计算薪资时会遇到类似的问题。您可以运行该程序以查看问题
$ python program.py
Tracking Employee Productivity
==============================
Mary Poppins screams and yells for 40 hours.
John Smith expends 40 hours doing office paperwork.
Kevin Bacon expends 40 hours on the phone.
Jane Doe manufactures gadgets for 40 hours.
Robin Williams expends 40 hours doing office paperwork.
Calculating Payroll
===================
Payroll for: 1 - Mary Poppins
- Check amount: 3000
Payroll for: 2 - John Smith
- Check amount: 1500
Payroll for: 3 - Kevin Bacon
- Check amount: 1250
Payroll for: 4 - Jane Doe
- Check amount: 600
Payroll for: 5 - Robin Williams
Traceback (most recent call last):
File ".\program.py", line 20, in
payroll_system.calculate_payroll(employees)
File "hr.py", line 7, in calculate_payroll
print(f'- Check amount: {employee.calculate_payroll()}')
File "employee.py", line 12, in calculate_payroll
return self.weekly_salary
AttributeError: 'TemporarySecretary' object has no attribute 'weekly_salary'
现在的问题是,由于您颠倒了继承顺序,MRO将在HourlyEmployee中的SalariedEmployee方法之前找到SalariedEmployee的.calculate_payroll()方法。您需要在TemporarySecretary中覆盖.calculate_payroll()并从中调用正确的实现
class TemporarySecretary(Secretary, HourlyEmployee):
def __init__(self, id, name, hours_worked, hour_rate):
HourlyEmployee.__init__(self, id, name, hours_worked, hour_rate)
def calculate_payroll(self):
return HourlyEmployee.calculate_payroll(self)
compute_payroll()方法直接调用HourlyEmployee.calculate_payroll()以确保获得正确的结果。您可以再次运行该程序以查看其是否正常运行
$ python program.py
Tracking Employee Productivity
==============================
Mary Poppins screams and yells for 40 hours.
John Smith expends 40 hours doing office paperwork.
Kevin Bacon expends 40 hours on the phone.
Jane Doe manufactures gadgets for 40 hours.
Robin Williams expends 40 hours doing office paperwork.
Calculating Payroll
===================
Payroll for: 1 - Mary Poppins
- Check amount: 3000
Payroll for: 2 - John Smith
- Check amount: 1500
Payroll for: 3 - Kevin Bacon
- Check amount: 1250
Payroll for: 4 - Jane Doe
- Check amount: 600
Payroll for: 5 - Robin Williams
- Check amount: 360
程序现在可以正常工作了,因为您可以通过显式地告诉解释器我们想要使用哪个方法来强制方法解析顺序。
正如您所看到的,多重继承可能令人困惑,特别是当您遇到diamond问题时
该图显示了当前类设计的diamond问题。TemporarySecretary使用多重继承派生自两个类,这两个类最终也派生自Employee。这将导致两条路径到达Employee基类,这是您希望在设计中避免的
当您使用多重继承并从两个具有公共基类的类派生时,diamond问题就会出现。这可能导致调用方法的错误版本
正如您所看到的,Python提供了一种方法来强制调用正确的方法,并且分析MRO可以帮助您理解问题
Employee派生类由两个不同的系统使用
跟踪员工生产力的生产力系统
计算员工薪资的薪资系统
这意味着与生产力相关的所有内容都应该放在一个模块中,而与工资相关的所有内容都应该放在另一个模块中。您可以开始更改生产力模块
# In productivity.py
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.'
生产力模块实现ProductivitySystem类及其支持的相关角色。这些类实现了系统所需的work()接口,但它们不是从Employee派生的
# In hr.py
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
hr模块实现了PayrollSystem,该系统为员工计算工资。它还实现了工资单的策略类。如您所见,策略类别不再源自Employee
# In employees.py
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)
employees模块从其他模块导入策略和角色,并实现不同的Employee类型。您仍然使用多重继承来继承salary策略类和productivity角色的实现,但是每个类的实现只需要处理初始化
注意,您仍然需要在构造函数中显式地初始化薪水策略。您可能看到Manager和Secretary的初始化是相同的。另外,factory - worker和TemporarySecretary的初始化是相同的
您将不希望在更复杂的设计中使用这种代码重复,因此在设计类层次结构时必须小心
运行程序
$ python program.py
Tracking Employee Productivity
==============================
Mary Poppins: screams and yells for 40 hours.
John Smith: expends 40 hours doing office paperwork.
Kevin Bacon: expends 40 hours on the phone.
Jane Doe: manufactures gadgets for 40 hours.
Robin Williams: expends 40 hours doing office paperwork.
Calculating Payroll
===================
Payroll for: 1 - Mary Poppins
- Check amount: 3000
Payroll for: 2 - John Smith
- Check amount: 1500
Payroll for: 3 - Kevin Bacon
- Check amount: 1250
Payroll for: 4 - Jane Doe
- Check amount: 600
Payroll for: 5 - Robin Williams
- Check amount: 360
领取专属 10元无门槛券
私享最新 技术干货