目 录CONTENT

文章目录

Python(三十) 类的多态详解

Python(三十) 类的多态详解

1. 什么是多态

多态是面向对象编程中的重要思想。

通俗地说:

多态就是同一个操作,作用在不同对象上,可以产生不同的结果。

也可以这样理解:

同一个方法名,不同对象有不同实现。
调用时不用关心对象的具体类型,只关心它能不能完成这个动作。

例如:

不同图形都可以计算面积:
矩形的面积 = 宽 * 高
圆形的面积 = 3.14 * 半径 * 半径
三角形的面积 = 底 * 高 / 2

它们都可以有一个共同的方法:

area()

但是每种图形计算面积的方式不同。

这就是多态。

2. 为什么需要多态

假设我们有矩形和圆形两个类。

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

它们都有 area() 方法。

我们可以写一个函数,专门用来打印面积:

def print_area(shape):
    print(shape.area())

然后传入不同对象:

rect = Rectangle(3, 4)
circle = Circle(5)

print_area(rect)
print_area(circle)

输出:

12
78.5

print_area() 不需要知道传进来的是矩形还是圆形。

它只需要知道:

这个对象有 area() 方法。

这就是多态带来的好处:

写代码时更灵活。
函数可以处理多种不同对象。
新增类型时,原来的调用代码不需要大改。

3. 多态的核心思想

多态的核心思想可以总结为:

同一接口,不同实现。

这里的“接口”可以先简单理解为:

大家约定好的方法名。

例如:

所有图形都提供 area() 方法。
所有支付方式都提供 pay() 方法。
所有消息通知方式都提供 send() 方法。
所有文件导出器都提供 export() 方法。

不同类都使用同一个方法名,但内部实现不同。

示例:

class Alipay:
    def pay(self, money):
        print(f"使用支付宝支付 {money} 元")


class WeChatPay:
    def pay(self, money):
        print(f"使用微信支付 {money} 元")


class BankCardPay:
    def pay(self, money):
        print(f"使用银行卡支付 {money} 元")

它们都有:

pay()

但是具体支付方式不同。

4. 多态和继承的关系

在很多面向对象语言中,多态通常和继承一起出现。

例如:

class Shape:
    def area(self):
        pass


class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

这里:

Shape 是父类。
Rectangle 和 Circle 是子类。
父类中定义 area()。
子类中重写 area()。

当我们调用:

shape.area()

不同子类对象会执行各自的 area() 方法。

这就是继承中的多态。

5. 一个基础多态例子

class Shape:
    def area(self):
        return 0


class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius


def print_area(shape):
    print(shape.area())


rect = Rectangle(3, 4)
circle = Circle(5)

print_area(rect)
print_area(circle)

输出:

12
78.5

这里的 print_area() 就体现了多态:

def print_area(shape):
    print(shape.area())

它不关心 shape 具体是什么类型。

只要 shapearea() 方法,就可以使用。

6. 多态中的方法重写

多态经常依赖方法重写。

方法重写指的是:

子类重新定义父类中已有的方法。

示例:

class Notification:
    def send(self, message):
        print("发送通知:", message)


class EmailNotification(Notification):
    def send(self, message):
        print("发送邮件通知:", message)


class SmsNotification(Notification):
    def send(self, message):
        print("发送短信通知:", message)

父类和子类都有 send() 方法。

子类中的 send() 会覆盖父类中的 send()

email = EmailNotification()
sms = SmsNotification()

email.send("订单已创建")
sms.send("验证码是 123456")

输出:

发送邮件通知: 订单已创建
发送短信通知: 验证码是 123456

7. 用统一函数调用不同对象

多态的常见写法是:

定义一个统一函数,让不同对象传进去执行各自的方法。

示例:

class EmailNotification:
    def send(self, message):
        print("邮件通知:", message)


class SmsNotification:
    def send(self, message):
        print("短信通知:", message)


class AppNotification:
    def send(self, message):
        print("App 通知:", message)


def notify(sender, message):
    sender.send(message)


email = EmailNotification()
sms = SmsNotification()
app = AppNotification()

notify(email, "欢迎注册")
notify(sms, "验证码是 8888")
notify(app, "你有一条新消息")

输出:

邮件通知: 欢迎注册
短信通知: 验证码是 8888
App 通知: 你有一条新消息

notify() 不需要分别判断:

if 是邮件:
    ...
elif 是短信:
    ...
elif 是 App:
    ...

它只统一调用:

sender.send(message)

这就是多态让代码变简洁的地方。

8. 没有多态时的写法

先看没有多态时可能会怎么写。

class EmailNotification:
    def send_email(self, message):
        print("邮件通知:", message)


class SmsNotification:
    def send_sms(self, message):
        print("短信通知:", message)


def notify(sender, message, sender_type):
    if sender_type == "email":
        sender.send_email(message)
    elif sender_type == "sms":
        sender.send_sms(message)

调用:

email = EmailNotification()
sms = SmsNotification()

notify(email, "欢迎注册", "email")
notify(sms, "验证码是 8888", "sms")

这样的问题是:

每增加一种通知方式,notify() 函数就要修改。
不同类的方法名也不统一。

9. 使用多态后的写法

class EmailNotification:
    def send(self, message):
        print("邮件通知:", message)


class SmsNotification:
    def send(self, message):
        print("短信通知:", message)


class AppNotification:
    def send(self, message):
        print("App 通知:", message)


def notify(sender, message):
    sender.send(message)

调用:

email = EmailNotification()
sms = SmsNotification()
app = AppNotification()

notify(email, "欢迎注册")
notify(sms, "验证码是 8888")
notify(app, "你有一条新消息")

这样写的好处:

所有通知类都统一使用 send()。
notify() 不需要知道具体通知类型。
新增通知方式时,notify() 通常不用改。

这就是多态非常重要的价值。

10. Python 中的鸭子类型

Python 的多态有一个非常重要的特点:鸭子类型。

鸭子类型的意思是:

如果一个对象能完成某个行为,我们就把它当成能完成这个行为的对象来使用。

在 Python 中,很多时候不关心对象是不是某个父类的子类。

更关心的是:

这个对象有没有我需要的方法?

例如:

class PdfExporter:
    def export(self, data):
        print("导出 PDF:", data)


class ExcelExporter:
    def export(self, data):
        print("导出 Excel:", data)


class CsvExporter:
    def export(self, data):
        print("导出 CSV:", data)


def export_data(exporter, data):
    exporter.export(data)

这些类没有共同父类。

但是它们都有:

export()

所以都可以传给 export_data()

data = "学生成绩表"

export_data(PdfExporter(), data)
export_data(ExcelExporter(), data)
export_data(CsvExporter(), data)

输出:

导出 PDF: 学生成绩表
导出 Excel: 学生成绩表
导出 CSV: 学生成绩表

这就是 Python 中非常常见的多态写法。

11. 鸭子类型和继承多态的区别

继承多态通常是:

子类继承同一个父类。
子类重写父类方法。
统一调用父类约定的方法。

鸭子类型通常是:

不一定有共同父类。
只要对象有需要的方法,就可以使用。

对比表:

对比项 继承中的多态 鸭子类型
是否需要共同父类 通常需要 不一定需要
关注点 对象是不是某类的子类 对象有没有需要的方法
常见写法 子类重写父类方法 不同类实现同名方法
Python 中是否常见 常见 非常常见

示例:

def run(task):
    task.execute()

只要传入的对象有 execute() 方法,就可以使用。

这就是鸭子类型的思路。

12. 多态和 if 判断的关系

多态经常可以减少大量 if...elif...else

没有多态:

def calculate_area(shape):
    if shape["type"] == "rectangle":
        return shape["width"] * shape["height"]
    elif shape["type"] == "circle":
        return 3.14 * shape["radius"] * shape["radius"]
    elif shape["type"] == "triangle":
        return shape["base"] * shape["height"] / 2

使用多态:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius


class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return self.base * self.height / 2


def calculate_area(shape):
    return shape.area()

调用:

shapes = [
    Rectangle(3, 4),
    Circle(5),
    Triangle(6, 8)
]

for shape in shapes:
    print(calculate_area(shape))

输出:

12
78.5
24.0

这样代码更容易扩展。

如果以后新增一个正方形类,只要它有 area() 方法,calculate_area() 不需要修改。

13. 多态的常见结构

多态常见结构如下:

class A:
    def do(self):
        pass


class B(A):
    def do(self):
        print("B 的实现")


class C(A):
    def do(self):
        print("C 的实现")


def run(obj):
    obj.do()

调用:

run(B())
run(C())

结果:

同一个 run() 函数,
传入不同对象,
执行不同的 do() 方法。

记忆:

同名方法,不同表现。

14. 多态案例:支付方式

不同支付方式都可以支付,但具体实现不同。

class Alipay:
    def pay(self, money):
        print(f"支付宝支付 {money} 元")


class WeChatPay:
    def pay(self, money):
        print(f"微信支付 {money} 元")


class BankCardPay:
    def pay(self, money):
        print(f"银行卡支付 {money} 元")


def checkout(payment, money):
    payment.pay(money)


checkout(Alipay(), 100)
checkout(WeChatPay(), 200)
checkout(BankCardPay(), 300)

输出:

支付宝支付 100 元
微信支付 200 元
银行卡支付 300 元

这里:

checkout() 是统一结账函数。
不同支付对象都有 pay() 方法。
checkout() 不关心具体支付方式。

如果新增一种支付方式:

class CreditPay:
    def pay(self, money):
        print(f"信用支付 {money} 元")

直接使用:

checkout(CreditPay(), 500)

不需要修改 checkout()

这就是多态带来的扩展能力。

15. 多态案例:消息通知

class Email:
    def send(self, message):
        print(f"邮件发送:{message}")


class Sms:
    def send(self, message):
        print(f"短信发送:{message}")


class AppPush:
    def send(self, message):
        print(f"App 推送:{message}")


def send_message(sender, message):
    sender.send(message)


senders = [Email(), Sms(), AppPush()]

for sender in senders:
    send_message(sender, "系统维护通知")

输出:

邮件发送:系统维护通知
短信发送:系统维护通知
App 推送:系统维护通知

这个例子适合课堂讲解:

同一个 send_message()。
传入不同通知对象。
执行不同通知逻辑。

16. 多态案例:文件导出

class PdfExporter:
    def export(self, data):
        print(f"导出 PDF 文件:{data}")


class ExcelExporter:
    def export(self, data):
        print(f"导出 Excel 文件:{data}")


class HtmlExporter:
    def export(self, data):
        print(f"导出 HTML 文件:{data}")


def export_report(exporter, data):
    exporter.export(data)


report = "本月销售报表"

export_report(PdfExporter(), report)
export_report(ExcelExporter(), report)
export_report(HtmlExporter(), report)

输出:

导出 PDF 文件:本月销售报表
导出 Excel 文件:本月销售报表
导出 HTML 文件:本月销售报表

如果新增 JSON 导出:

class JsonExporter:
    def export(self, data):
        print(f"导出 JSON 文件:{data}")

直接调用:

export_report(JsonExporter(), report)

原来的 export_report() 不需要改变。

17. 多态案例:图形面积

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius


class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return self.base * self.height / 2


def total_area(shapes):
    total = 0
    for shape in shapes:
        total += shape.area()
    return total


shapes = [
    Rectangle(3, 4),
    Circle(5),
    Triangle(6, 8)
]

print(total_area(shapes))

输出:

114.5

这里:

shapes 列表中可以放不同类型的图形对象。
这些对象都有 area() 方法。
total_area() 统一调用 shape.area()。
不同对象自己决定面积如何计算。

18. 多态案例:员工薪资

不同员工的工资计算方式可能不同。

class FullTimeEmployee:
    def __init__(self, name, monthly_salary):
        self.name = name
        self.monthly_salary = monthly_salary

    def calculate_salary(self):
        return self.monthly_salary


class PartTimeEmployee:
    def __init__(self, name, hours, hourly_rate):
        self.name = name
        self.hours = hours
        self.hourly_rate = hourly_rate

    def calculate_salary(self):
        return self.hours * self.hourly_rate


class SalesEmployee:
    def __init__(self, name, base_salary, commission):
        self.name = name
        self.base_salary = base_salary
        self.commission = commission

    def calculate_salary(self):
        return self.base_salary + self.commission


def print_salary(employee):
    print(f"{employee.name} 的工资是 {employee.calculate_salary()}")


employees = [
    FullTimeEmployee("张三", 8000),
    PartTimeEmployee("李四", 80, 50),
    SalesEmployee("王五", 5000, 3000)
]

for employee in employees:
    print_salary(employee)

输出:

张三 的工资是 8000
李四 的工资是 4000
王五 的工资是 8000

这里:

不同员工都有 calculate_salary()。
但是计算方式不同。
print_salary() 不关心员工类型。

19. 多态和抽象基类

在教学多态时,还可以了解抽象基类。

抽象基类可以用来规定子类必须实现某些方法。

Python 中可以使用 abc 模块。

示例:

from abc import ABC, abstractmethod


class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

这里:

Shape 是抽象基类。
area() 是抽象方法。
继承 Shape 的子类必须实现 area()。

完整示例:

from abc import ABC, abstractmethod


class Shape(ABC):
    @abstractmethod
    def area(self):
        pass


class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

使用:

shapes = [Rectangle(3, 4), Circle(5)]

for shape in shapes:
    print(shape.area())

输出:

12
78.5

20. 抽象基类的作用

抽象基类主要有两个作用:

1. 规定子类必须实现哪些方法。
2. 让代码结构更清晰。

例如:

class Payment(ABC):
    @abstractmethod
    def pay(self, money):
        pass

意思是:

所有支付类都必须有 pay() 方法。

子类:

class Alipay(Payment):
    def pay(self, money):
        print(f"支付宝支付 {money} 元")

如果子类没有实现 pay()

class BadPayment(Payment):
    pass

创建对象时会报错:

bad = BadPayment()

这可以帮助我们早点发现问题。

21. 抽象方法和普通方法的区别

普通方法:

class Shape:
    def area(self):
        return 0

子类可以不重写。

抽象方法:

from abc import ABC, abstractmethod


class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

子类必须重写。

对比表:

对比项 普通方法 抽象方法
是否必须被子类实现 不必须 必须
是否需要 abc 模块 不需要 需要
适合场景 提供默认行为 规定统一接口

初学阶段可以先掌握普通多态。

抽象基类适合在学生已经理解继承和方法重写后再讲。

22. NotImplementedError 写法

除了抽象基类,还可以在父类方法中主动抛出 NotImplementedError

示例:

class Shape:
    def area(self):
        raise NotImplementedError("子类必须实现 area 方法")

子类:

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

如果某个子类忘记实现 area()

class UnknownShape(Shape):
    pass


shape = UnknownShape()
shape.area()

会报错并提示:

子类必须实现 area 方法

这种写法也能提醒子类实现必要方法。

23. 多态和类型判断

使用多态时,通常不需要频繁写 type() 判断。

不推荐:

def print_area(shape):
    if type(shape) == Rectangle:
        print(shape.area())
    elif type(shape) == Circle:
        print(shape.area())

更推荐:

def print_area(shape):
    print(shape.area())

原因:

多态的重点就是让对象自己决定如何执行。
如果到处判断类型,就失去了多态的意义。

当然,有些场景确实需要类型判断。

但在多态设计中,优先考虑统一方法名,而不是到处写类型分支。

24. isinstance() 在多态中的使用

有时我们想确认对象是否属于某个父类,可以使用 isinstance()

示例:

class Shape:
    def area(self):
        return 0


class Rectangle(Shape):
    def area(self):
        return 12


rect = Rectangle()

print(isinstance(rect, Rectangle))
print(isinstance(rect, Shape))

输出:

True
True

因为:

rect 是 Rectangle 对象。
Rectangle 继承 Shape。
所以 rect 也可以看作 Shape 对象。

但是要注意:

Python 多态不一定要求 isinstance() 判断。
只要对象有需要的方法,通常就可以使用。

25. hasattr() 简单了解

如果想判断一个对象是否有某个方法,可以使用 hasattr()

示例:

class PdfExporter:
    def export(self, data):
        print("导出 PDF:", data)


exporter = PdfExporter()

print(hasattr(exporter, "export"))
print(hasattr(exporter, "save"))

输出:

True
False

可以这样写:

def export_data(exporter, data):
    if hasattr(exporter, "export"):
        exporter.export(data)
    else:
        print("对象没有 export 方法")

不过教学时要提醒:

hasattr() 可以检查能力,但不要滥用。
很多时候让错误自然暴露,反而更容易发现代码问题。

26. 多态的优点

多态的优点主要有:

26.1 减少重复代码

多个类使用同一套调用方式。

def process(obj):
    obj.run()

不用为每种类型都写一个函数。

26.2 提高扩展性

新增类时,只要实现同名方法,原来的调用代码通常不用修改。

例如新增支付方式:

class NewPay:
    def pay(self, money):
        print(f"新支付方式支付 {money} 元")

原来的结账函数仍然可以用:

checkout(NewPay(), 100)

26.3 让代码更灵活

函数可以接收多种不同对象。

def send_message(sender, message):
    sender.send(message)

只要对象有 send() 方法,就可以传入。

26.4 让职责更清楚

每个类负责自己的实现。

调用方只负责调用统一方法。

调用方:调用 pay()
支付宝类:实现支付宝怎么支付
微信类:实现微信怎么支付
银行卡类:实现银行卡怎么支付

27. 多态的适用场景

多态适合这些场景:

多个类有相同的行为名称,但具体实现不同。
希望写统一的函数处理不同对象。
希望新增类型时少改旧代码。
希望减少大量 if 类型判断。

常见例子:

不同图形计算面积
不同支付方式付款
不同通知方式发送消息
不同文件格式导出数据
不同员工类型计算工资
不同任务执行 execute()

28. 多态不适合的场景

不是所有地方都需要多态。

如果只有一个类,或者逻辑非常简单,就不一定需要设计多态。

例如:

class Student:
    def study(self):
        print("学习")

如果程序中只有一种学生对象,并没有多种不同实现,就不需要强行讲多态。

不适合:

为了使用面向对象而强行设计很多类。
为了减少一两个 if 就写复杂继承结构。
不同类之间根本没有统一行为。

教学总结:

多态适合“多种对象,同一行为,不同实现”的场景。

29. 多态和方法重载

有些语言中有方法重载。

方法重载通常指:

同一个类中,方法名相同,但参数不同。

例如在某些语言中可以写:

add(int a, int b)
add(float a, float b)

Python 不支持这种传统意义上的方法重载。

如果在 Python 中写两个同名方法:

class Calculator:
    def add(self, a, b):
        return a + b

    def add(self, a, b, c):
        return a + b + c

后面的 add() 会覆盖前面的 add()

所以:

calc = Calculator()
print(calc.add(1, 2, 3))

可以运行。

但:

print(calc.add(1, 2))

会报错。

Python 中如果想让参数灵活,可以使用默认参数或 *args

class Calculator:
    def add(self, *args):
        return sum(args)


calc = Calculator()

print(calc.add(1, 2))
print(calc.add(1, 2, 3))

输出:

3
6

注意:

多态不是方法重载。
多态强调不同对象对同一方法有不同实现。

30. 多态和方法重写

方法重写是多态的重要基础。

方法重写:

子类重新实现父类中已有的方法。

多态:

调用同一个方法时,不同对象表现出不同结果。

示例:

class Report:
    def export(self):
        print("导出通用报表")


class PdfReport(Report):
    def export(self):
        print("导出 PDF 报表")


class ExcelReport(Report):
    def export(self):
        print("导出 Excel 报表")


def export_report(report):
    report.export()


export_report(PdfReport())
export_report(ExcelReport())

输出:

导出 PDF 报表
导出 Excel 报表

这里:

PdfReport 和 ExcelReport 重写 export()。
export_report() 调用 export() 时产生不同结果。
这就是多态。

31. 多态中的参数一致性

为了方便统一调用,不同类中的同名方法,参数最好保持一致。

推荐:

class Email:
    def send(self, message):
        print("邮件:", message)


class Sms:
    def send(self, message):
        print("短信:", message)

统一调用:

def notify(sender, message):
    sender.send(message)

不推荐:

class Email:
    def send(self, title, content):
        print(title, content)


class Sms:
    def send(self, message):
        print(message)

这样 notify() 很难统一调用。

教学总结:

要实现多态,同名方法的参数设计最好保持一致。

32. 多态中的返回值一致性

除了参数,返回值的含义也最好一致。

例如,所有 area() 都返回数字。

class Rectangle:
    def area(self):
        return 12


class Circle:
    def area(self):
        return 78.5

不推荐:

class Rectangle:
    def area(self):
        return 12


class Circle:
    def area(self):
        print("面积是 78.5")

一个返回数字,一个直接打印,会让调用方很难统一处理。

推荐:

def print_area(shape):
    print(shape.area())

前提是每个 area() 都返回面积。

教学总结:

同一个多态方法,返回值含义最好一致。

33. 多态中的统一接口

多态依赖统一接口。

这里的接口可以简单理解为:

统一的方法名、参数和返回值约定。

例如导出器接口:

方法名:export
参数:data
返回值:可以无返回值,也可以返回导出路径

所有导出器都遵守这个约定:

class PdfExporter:
    def export(self, data):
        print("PDF:", data)


class ExcelExporter:
    def export(self, data):
        print("Excel:", data)

统一调用:

def export_data(exporter, data):
    exporter.export(data)

如果某个类不遵守约定:

class BadExporter:
    def save(self, data):
        print("保存:", data)

那么它不能直接传给:

export_data(BadExporter(), "数据")

因为它没有 export() 方法。

34. 多态和开放封闭原则

多态可以帮助我们写出更容易扩展的代码。

有一个常见设计原则叫开放封闭原则。

简单理解:

对扩展开放,对修改关闭。

意思是:

需要新增功能时,尽量新增代码。
尽量少修改已经稳定的旧代码。

看支付案例:

def checkout(payment, money):
    payment.pay(money)

如果新增一种支付方式:

class GiftCardPay:
    def pay(self, money):
        print(f"礼品卡支付 {money} 元")

直接调用:

checkout(GiftCardPay(), 100)

checkout() 不需要修改。

这就是多态带来的扩展优势。

35. 常见错误 1:方法名不统一

错误示例:

class Email:
    def send_email(self, message):
        print(message)


class Sms:
    def send_sms(self, message):
        print(message)

这样很难写统一函数。

更推荐:

class Email:
    def send(self, message):
        print(message)


class Sms:
    def send(self, message):
        print(message)

统一调用:

def notify(sender, message):
    sender.send(message)

36. 常见错误 2:方法参数不一致

错误示例:

class PdfExporter:
    def export(self, data):
        print(data)


class ExcelExporter:
    def export(self, data, sheet_name):
        print(data, sheet_name)

统一调用时会很麻烦:

def export_data(exporter, data):
    exporter.export(data)

PdfExporter 可以用。

ExcelExporter 会因为少传 sheet_name 而报错。

解决办法:

class ExcelExporter:
    def export(self, data):
        print("默认工作表:", data)

或者使用默认参数:

class ExcelExporter:
    def export(self, data, sheet_name="Sheet1"):
        print(sheet_name, data)

37. 常见错误 3:返回值不一致

错误示例:

class Rectangle:
    def area(self):
        return 12


class Circle:
    def area(self):
        print(78.5)

如果统一计算总面积:

total += shape.area()

Circle.area() 没有返回数字,就会出问题。

正确做法:

class Circle:
    def area(self):
        return 78.5

38. 常见错误 4:过度依赖 type 判断

不推荐:

def pay(payment, money):
    if type(payment) == Alipay:
        payment.pay(money)
    elif type(payment) == WeChatPay:
        payment.pay(money)

更推荐:

def pay(payment, money):
    payment.pay(money)

原因:

多态就是为了让不同对象自己实现同一行为。
如果每次都判断类型,扩展性会变差。

39. 常见错误 5:父类方法只写 pass,子类忘记重写

示例:

class Shape:
    def area(self):
        pass


class Rectangle(Shape):
    pass

调用:

rect = Rectangle()
print(rect.area())

输出:

None

这可能不是我们想要的结果。

更清晰的写法:

class Shape:
    def area(self):
        raise NotImplementedError("子类必须实现 area 方法")

这样子类忘记实现时,会直接报错提醒。

40. 常见错误 6:以为所有多态都必须继承

Python 中不一定必须继承才能实现多态。

例如:

class PdfExporter:
    def export(self, data):
        print("PDF", data)


class ExcelExporter:
    def export(self, data):
        print("Excel", data)

它们没有共同父类,但都有 export() 方法。

所以:

def export_data(exporter, data):
    exporter.export(data)

仍然可以工作。

教学总结:

Python 多态既可以基于继承,也可以基于鸭子类型。

41. 常见错误 7:为了多态强行设计复杂类

不推荐为了一个很小的需求强行写很多类。

例如,只有一种支付方式时:

class Payment:
    pass

可能没有必要。

多态适合:

已经有多种类型。
或者很明显将来会扩展多种类型。

教学中可以说:

多态是解决复杂变化的工具。
不是所有简单问题都需要它。

42. 常见错误 8:统一方法名但含义不同

不推荐:

class Printer:
    def run(self):
        print("打印文件")


class Calculator:
    def run(self):
        print("计算工资")

虽然都有 run(),但含义差别很大。

如果放在一起统一调用,可能让代码含义变得不清楚。

多态中的同名方法,最好表达同一种行为。

例如:

export()
send()
pay()
area()
calculate_salary()

这些方法名比较明确。

43. 注意事项 1:统一方法名

想让多个类支持多态,首先要统一方法名。

例如:

支付统一叫 pay()
发送统一叫 send()
导出统一叫 export()
计算面积统一叫 area()
执行任务统一叫 execute()

这样调用方才能写统一代码。

44. 注意事项 2:统一参数

同名方法的参数最好保持一致。

推荐:

def send(self, message):
    ...

所有通知类都这样写。

不推荐有的写:

send(self, message)

有的写:

send(self, title, content)

这样统一调用会变困难。

45. 注意事项 3:统一返回值含义

同一个多态方法,返回值最好表达同一种含义。

例如:

area() 返回面积数字。
pay() 返回支付是否成功。
export() 返回导出文件路径。

不要有的返回数字,有的打印字符串,有的返回 None

除非调用方不关心返回值。

46. 注意事项 4:让每个类负责自己的实现

多态的重点是:

调用方统一调用。
具体类自己实现。

例如:

def checkout(payment, money):
    payment.pay(money)

checkout() 不应该关心:

支付宝怎么支付
微信怎么支付
银行卡怎么支付

这些细节应该放在各自类的 pay() 方法中。

47. 注意事项 5:新增类型时尽量不修改旧函数

如果代码设计得好,新增类型时通常只需要新增类。

例如新增一种导出方式:

class XmlExporter:
    def export(self, data):
        print("导出 XML:", data)

原来的函数不变:

def export_data(exporter, data):
    exporter.export(data)

这说明多态设计比较自然。

48. 注意事项 6:抽象基类适合中后期学习

抽象基类很有用,但对初学者来说概念稍微多一些。

教学顺序建议:

1. 先讲同名方法。
2. 再讲统一函数调用不同对象。
3. 再讲继承中的方法重写。
4. 最后介绍抽象基类。

这样学生更容易理解。

49. 注意事项 7:错误提示要清楚

如果父类方法要求子类必须实现,可以使用:

raise NotImplementedError("子类必须实现 xxx 方法")

比单纯写:

pass

更容易发现问题。

示例:

class Task:
    def execute(self):
        raise NotImplementedError("子类必须实现 execute 方法")

50. 注意事项 8:多态不等于随便传对象

多态不是说任何对象都能传进去。

比如:

def print_area(shape):
    print(shape.area())

这个函数要求传入的对象必须有 area() 方法。

如果传入:

print_area("hello")

会报错。

原因:

字符串没有 area() 方法。

所以多态的前提是:

对象必须具备调用方需要的能力。

51. 综合案例:任务执行器

class EmailTask:
    def execute(self):
        print("执行邮件发送任务")


class BackupTask:
    def execute(self):
        print("执行数据备份任务")


class CleanTask:
    def execute(self):
        print("执行缓存清理任务")


def run_task(task):
    task.execute()


tasks = [
    EmailTask(),
    BackupTask(),
    CleanTask()
]

for task in tasks:
    run_task(task)

输出:

执行邮件发送任务
执行数据备份任务
执行缓存清理任务

这个案例中:

不同任务都有 execute()。
run_task() 统一调用 execute()。
每个任务自己决定如何执行。

52. 综合案例:报表导出系统

class PdfReport:
    def export(self, data):
        return f"PDF 报表:{data}"


class ExcelReport:
    def export(self, data):
        return f"Excel 报表:{data}"


class CsvReport:
    def export(self, data):
        return f"CSV 报表:{data}"


def export_all(reports, data):
    results = []

    for report in reports:
        result = report.export(data)
        results.append(result)

    return results


reports = [
    PdfReport(),
    ExcelReport(),
    CsvReport()
]

data = "学生成绩数据"

results = export_all(reports, data)

for item in results:
    print(item)

输出:

PDF 报表:学生成绩数据
Excel 报表:学生成绩数据
CSV 报表:学生成绩数据

这个案例适合讲:

同一个列表中可以放不同对象。
只要这些对象都有 export() 方法,就可以统一处理。

53. 综合案例:订单优惠计算

不同优惠规则有不同计算方式。

class NoDiscount:
    def calculate(self, price):
        return price


class FixedDiscount:
    def __init__(self, discount):
        self.discount = discount

    def calculate(self, price):
        return price - self.discount


class PercentDiscount:
    def __init__(self, percent):
        self.percent = percent

    def calculate(self, price):
        return price * self.percent


def final_price(price, discount_strategy):
    return discount_strategy.calculate(price)


price = 100

print(final_price(price, NoDiscount()))
print(final_price(price, FixedDiscount(20)))
print(final_price(price, PercentDiscount(0.8)))

输出:

100
80
80.0

这里:

不同优惠策略都有 calculate() 方法。
final_price() 不关心具体优惠类型。

54. 综合案例:表单字段校验

class RequiredValidator:
    def validate(self, value):
        return value != ""


class LengthValidator:
    def __init__(self, min_length):
        self.min_length = min_length

    def validate(self, value):
        return len(value) >= self.min_length


class NumberValidator:
    def validate(self, value):
        return value.isdigit()


def check_value(value, validators):
    for validator in validators:
        if not validator.validate(value):
            return False
    return True


validators = [
    RequiredValidator(),
    LengthValidator(6),
    NumberValidator()
]

print(check_value("123456", validators))
print(check_value("abc123", validators))
print(check_value("", validators))

输出:

True
False
False

这个案例中:

不同校验器都有 validate() 方法。
check_value() 统一调用 validate()。
每个校验器负责自己的规则。

55. 课堂练习 1:图形面积

要求:

  1. 定义 Rectangle 类,有 area() 方法。
  2. 定义 Circle 类,有 area() 方法。
  3. 定义 print_area(shape) 函数,打印面积。
  4. 分别传入矩形对象和圆形对象。

参考答案:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius


def print_area(shape):
    print(shape.area())


print_area(Rectangle(3, 4))
print_area(Circle(5))

56. 课堂练习 2:支付方式

要求:

  1. 定义 Alipay 类,有 pay() 方法。
  2. 定义 WeChatPay 类,有 pay() 方法。
  3. 定义 checkout(payment, money) 函数。
  4. 传入不同支付对象,完成支付。

参考答案:

class Alipay:
    def pay(self, money):
        print(f"支付宝支付 {money} 元")


class WeChatPay:
    def pay(self, money):
        print(f"微信支付 {money} 元")


def checkout(payment, money):
    payment.pay(money)


checkout(Alipay(), 100)
checkout(WeChatPay(), 200)

57. 课堂练习 3:消息通知

要求:

  1. 定义 Email 类,有 send() 方法。
  2. 定义 Sms 类,有 send() 方法。
  3. 定义 send_message(sender, message) 函数。
  4. 使用列表统一发送通知。

参考答案:

class Email:
    def send(self, message):
        print("邮件:", message)


class Sms:
    def send(self, message):
        print("短信:", message)


def send_message(sender, message):
    sender.send(message)


senders = [Email(), Sms()]

for sender in senders:
    send_message(sender, "系统通知")

58. 课堂练习 4:文件导出

要求:

  1. 定义 PdfExporter 类,有 export() 方法。
  2. 定义 ExcelExporter 类,有 export() 方法。
  3. 定义 export_data(exporter, data) 函数。
  4. 传入不同导出器对象。

参考答案:

class PdfExporter:
    def export(self, data):
        print("导出 PDF:", data)


class ExcelExporter:
    def export(self, data):
        print("导出 Excel:", data)


def export_data(exporter, data):
    exporter.export(data)


export_data(PdfExporter(), "成绩表")
export_data(ExcelExporter(), "成绩表")

59. 课堂练习 5:员工工资

要求:

  1. 定义 FullTimeEmployee 类,有 calculate_salary() 方法。
  2. 定义 PartTimeEmployee 类,有 calculate_salary() 方法。
  3. 定义 print_salary(employee) 函数。
  4. 传入不同员工对象。

参考答案:

class FullTimeEmployee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def calculate_salary(self):
        return self.salary


class PartTimeEmployee:
    def __init__(self, name, hours, hourly_rate):
        self.name = name
        self.hours = hours
        self.hourly_rate = hourly_rate

    def calculate_salary(self):
        return self.hours * self.hourly_rate


def print_salary(employee):
    print(f"{employee.name} 的工资是 {employee.calculate_salary()}")


print_salary(FullTimeEmployee("张三", 8000))
print_salary(PartTimeEmployee("李四", 80, 50))

60. 课堂练习 6:使用抽象基类

要求:

  1. 使用 ABC 定义抽象类 Payment
  2. Payment 中定义抽象方法 pay()
  3. 定义 Alipay 类继承 Payment 并实现 pay()

参考答案:

from abc import ABC, abstractmethod


class Payment(ABC):
    @abstractmethod
    def pay(self, money):
        pass


class Alipay(Payment):
    def pay(self, money):
        print(f"支付宝支付 {money} 元")


payment = Alipay()
payment.pay(100)

61. 课堂练习 7:判断输出

阅读代码,判断输出结果:

class PdfExporter:
    def export(self, data):
        print("PDF", data)


class ExcelExporter:
    def export(self, data):
        print("Excel", data)


def export_data(exporter, data):
    exporter.export(data)


export_data(PdfExporter(), "报表")
export_data(ExcelExporter(), "报表")

答案:

PDF 报表
Excel 报表

解释:

两个对象都有 export() 方法。
export_data() 统一调用 export()。
不同对象执行不同实现。

62. 课堂练习 8:找错误

下面代码有什么问题?

class Email:
    def send_email(self, message):
        print(message)


class Sms:
    def send(self, message):
        print(message)


def notify(sender, message):
    sender.send(message)


notify(Email(), "你好")

答案:

Email 类中没有 send() 方法,只有 send_email() 方法。
notify() 调用的是 sender.send(message),所以会报错。

修改方式:

class Email:
    def send(self, message):
        print(message)

63. 课堂练习 9:补全代码

补全代码,让不同任务都能被 run_task() 执行。

题目:

class PrintTask:
    def execute(self):
        print("打印任务")


class SaveTask:
    # 请补全这里


def run_task(task):
    task.execute()


run_task(PrintTask())
run_task(SaveTask())

参考答案:

class PrintTask:
    def execute(self):
        print("打印任务")


class SaveTask:
    def execute(self):
        print("保存任务")


def run_task(task):
    task.execute()


run_task(PrintTask())
run_task(SaveTask())

64. 课堂练习 10:判断是否适合多态

下面哪些场景适合使用多态?

1. 不同支付方式都要付款。
2. 不同图形都要计算面积。
3. 只有一个学生类,只保存姓名和年龄。
4. 不同文件格式都要导出数据。
5. 不同通知方式都要发送消息。

参考答案:

适合:
1. 不同支付方式都要付款。
2. 不同图形都要计算面积。
4. 不同文件格式都要导出数据。
5. 不同通知方式都要发送消息。

不太需要:
3. 只有一个学生类,只保存姓名和年龄。

原因:

多态适合多种对象拥有同一行为,但具体实现不同的场景。

65. 教学总结

多态是 Python 面向对象编程中的重要思想。

一句话总结:

多态就是同一个方法名,作用在不同对象上,产生不同结果。

核心知识:

  1. 多态强调同一接口,不同实现。
  2. 多态常和继承、方法重写一起使用。
  3. Python 中也常通过鸭子类型实现多态。
  4. 多态可以减少类型判断。
  5. 多态可以提高代码扩展性。
  6. 不同类的方法名要统一。
  7. 同名方法的参数最好统一。
  8. 同名方法的返回值含义最好统一。
  9. 抽象基类可以规定子类必须实现的方法。
  10. 不要为了多态强行设计复杂类。

常用代码模板:

class A:
    def do(self):
        print("A 的实现")


class B:
    def do(self):
        print("B 的实现")


def run(obj):
    obj.do()


run(A())
run(B())

继承多态模板:

class Base:
    def do(self):
        raise NotImplementedError("子类必须实现 do 方法")


class ChildA(Base):
    def do(self):
        print("ChildA 的实现")


class ChildB(Base):
    def do(self):
        print("ChildB 的实现")


def run(obj):
    obj.do()

课堂记忆口诀:

同名方法,不同表现。
统一调用,各自实现。
少写判断,多靠对象。
参数统一,返回统一。
新增类型,少改旧码。

最后记住:

多态的重点不是写很多类,
而是让不同对象用统一方式被调用,
并让每个对象自己负责自己的具体实现。
0
博主关闭了当前页面的评论