CC++ & Algorithm

Python多态

困难4
语言版本:C++Python
概述:用动物叫声的比喻,教你理解“多态”是什么——同一个指令,不同对象做出不同反应。

多态:同一个指令,不同对象的不同反应

你有没有遇到过这样的场景:老师站在讲台上说“请站起来”,有的同学嗖地站起来,有的慢吞吞地站起来,还有的边站边打个哈欠。虽然都是“站起来”这个指令,但每个人执行的方式都不一样。在Python编程里,这种“同一个方法名,不同对象执行不同操作”的能力,就叫多态

多态到底在说什么?

想象你走进动物学校,对一只猫说“叫一声”,它会“喵喵”;对一只狗说“叫一声”,它会“汪汪”。指令一模一样,但不同的动物给出了完全不同的回答。在代码里,我们经常需要写这样一个函数:它接收一个“动物”对象,然后调用它的“叫”方法。你不用关心这个动物到底是猫还是狗,只要它会叫就行——至于它怎么叫,那是它自己的事。这就是多态的核心理念:同一段代码,可以处理多种不同类型的对象,只要它们都具备相同的方法名

生活中的多态

  • 遥控器:按“开”键,电视打开屏幕,空调开始吹风,电饭煲开始煮饭。同一个“开”指令,每个电器都做出自己的响应。
  • 绘画课:老师说“画一个图形吧”,小明画了圆形,小红画了正方形,小刚画了三角形。都是“画图形”,但结果五花八门。
  • 游戏里的角色:在格斗游戏里,按下“攻击键”,拳击手会出拳,剑士会挥剑,魔法师会念咒语。程序只需要写一个attack()方法,每个角色自己实现攻击方式。
  • 打车App:你叫了“一辆车”,来的可能是轿车、SUV或出租车。它们都能“载你去目的地”,但座位数、舒适度不同。

多态的核心:继承 + 方法重写

在Python中,多态最常用的方式就是继承方法重写。父类定义一个方法(比如make_sound),子类继承父类后,重新编写这个方法的具体内容。这样,当我们写一个函数时,它只关心对象有没有make_sound这个方法,而不关心它到底是哪个子类。

代码来啦:动物叫声

先创建一个动物基类,它有一个“发声”方法,但先不写具体内容(用pass跳过)。

class Animal:
    def make_sound(self):
        pass  # 父类先占个位置,不做事

接下来,让小猫和小狗继承Animal,并重写make_sound方法:

class Cat(Animal):
    def make_sound(self):
        print("喵喵喵")  # 猫的叫声

class Dog(Animal):
    def make_sound(self):
        print("汪汪汪")  # 狗的叫声

现在写一个让动物叫的函数:

def animal_sound(animal):
    animal.make_sound()  # 不管是什么动物,直接让它叫

创建对象并测试:

cat = Cat()  # 创建一只猫对象
dog = Dog()  # 创建一只狗对象

animal_sound(cat)  # 输出:喵喵喵
animal_sound(dog)  # 输出:汪汪汪

看到了吗?同一个animal_sound函数,传入不同的动物对象,它们各自执行了自己的make_sound。这就是多态的典型用法。

多态的另一种形式:鸭子类型

Python有一种更灵活的多态,叫鸭子类型。这句话来自一个谚语:“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。”意思就是:不要求对象必须继承自同一个父类,只要它有这个方法,就能用。

比如,我们有一个“飞行器”的模拟游戏。飞机和鸟都能飞,但它们没有共同的父类。

class Airplane:
    def fly(self):
        print("飞机在天空翱翔")

class Bird:
    def fly(self):
        print("小鸟拍打翅膀飞")

写一个函数让任何能飞的东西飞起来:

def let_it_fly(obj):
    obj.fly()  # 只要对象有 fly 方法就行

创建对象:

plane = Airplane()  # 飞机对象
sparrow = Bird()    # 小鸟对象

let_it_fly(plane)   # 输出:飞机在天空翱翔
let_it_fly(sparrow) # 输出:小鸟拍打翅膀飞

注意:AirplaneBird没有继承关系,但它们都有fly方法,所以能被同一个函数处理。这就是鸭子类型的魅力——代码更加灵活,不需要强行使用继承。

为什么要用多态?

  • 代码更灵活:增加新动物(比如牛、羊、猪)时,不用修改animal_sound函数,只需要创建新类、重写make_sound方法就行。
  • 便于扩展:游戏里有很多角色,只要它们都有“攻击”方法,就可以用同一个循环来指挥所有角色攻击。
  • 减少重复:如果每个动物都单独写一个函数(比如cat_sounddog_sound),代码会又长又乱。用多态,一个函数搞定所有。
  • 维护方便:需要修改“叫”的逻辑时,只需改对应类的make_sound方法,不会影响其他类。

新手容易犯的错误

错误1:忘记在子类中重写方法

如果你创建了一个Pig类,但忘记写make_sound方法,那么它就会继承父类的空方法(pass),调用时什么都不输出。

class Pig(Animal):
    pass  # 忘了重写,所以猪不会叫

pig = Pig()
animal_sound(pig)  # 输出空,啥也没有

解决:记得在子类中重写父类的方法,并写上具体内容。

错误2:方法名写错(单词拼错)

比如把make_sound写成make_sond,这样就不是重写父类方法,而是新增了一个方法。调用animal_sound(pig)时仍然会调用父类的空方法。

class Pig(Animal):
    def make_sond(self):  # 拼写错误!不是重写
        print("哼哼哼")

解决:确保子类方法名与父类完全一致(大小写也要相同)。

错误3:认为多态必须基于继承

有些同学觉得只有继承才能用多态,实际上Python的鸭子类型让多态更灵活。只要你传入的对象有对应的方法,就能工作,即使它们并没有共同的父类。

错误4:在继承关系中写了错误的参数

如果父类方法有参数,子类重写时参数必须一致(或者更灵活),否则调用时会报错。

完整示例:动物世界大联欢

下面是一段完整可运行的代码,包含猫、狗、猪,以及一个测试函数。你可以直接复制到Python环境中运行。

# 父类:动物
class Animal:
    def make_sound(self):
        pass  # 父类先占位,不做事

# 子类:猫
class Cat(Animal):
    def make_sound(self):
        print("喵喵喵")  # 猫的叫声

# 子类:狗
class Dog(Animal):
    def make_sound(self):
        print("汪汪汪")  # 狗的叫声

# 子类:猪
class Pig(Animal):
    def make_sound(self):
        print("哼哼哼")  # 猪的叫声

# 让动物叫的函数(多态核心)
def animal_sound(animal):
    print(f"我听到:", end="")  # 先打个招呼
    animal.make_sound()         # 调用对象的方法

# 创建各种动物对象
cat = Cat()   # 一只猫
dog = Dog()   # 一只狗
pig = Pig()   # 一只猪

# 分别让它们叫
animal_sound(cat)   # 输出:我听到:喵喵喵
animal_sound(dog)   # 输出:我听到:汪汪汪
animal_sound(pig)   # 输出:我听到:哼哼哼

小练习

  1. 创建一个Cow(牛)类,让牛的叫声是“哞哞哞”,然后用animal_sound函数测试。
  2. 创建一个Duck(鸭子)类,叫声是“嘎嘎嘎”,测试多态。
  3. 想一想:如果有一个Robot(机器人)类,它也有make_sound方法(比如“滴滴滴”),但机器人不是动物,能直接传给animal_sound函数吗?试试看。

相关知识点指引

  • 继承:子类拥有父类的属性和方法,可以在此基础上扩展。多态常常依赖继承。
  • 方法重写(覆盖):子类重新实现父类中已有的方法,是达成多态的关键。
  • 鸭子类型:不检查类型,只关心对象有没有需要的方法,Python中的多态常以此形式出现。
  • 抽象基类:在更复杂的项目中,可以用abc模块强制子类必须实现某些方法,避免忘记重写。
  • isinstance()函数:有时需要判断对象是不是某个类的实例,但多态一般不需要这个函数——直接调用方法更优雅。

多态是面向对象编程的三大特性之一(封装、继承、多态),掌握它之后,你的代码会变得更灵活、更易扩展。下次写程序时,试着先定义一个通用的方法名,然后让不同的对象用自己的方式去实现它吧!

例题精讲

1单选题

下列Python代码中,最能体现多态特性的是?

A使用if-else语句根据对象类型调用不同方法
B定义多个同名函数但参数不同
C通过继承让不同子类重写同一方法,并统一调用
D使用@property装饰器封装属性访问
2单选题

执行以下代码,输出结果是?\nclass Duck:\n def quack(self):\n return \"呱呱\"\nclass Person:\n def quack(self):\n return \"假装我是鸭子\"\ndef make_sound(obj):\n print(obj.quack())\nduck = Duck()\nperson = Person()\nmake_sound(duck)\nmake_sound(person)

A呱呱\n假装我是鸭子
B假装我是鸭子\n呱呱
C报错:Person对象没有quack方法
D报错:类型不匹配
3判断题

在Python中,多态只能通过继承和重写父类方法来实现。

4填空题
请补全以下代码,使程序能利用多态调用不同动物的叫声。\nclass Animal:\n    def speak(self):\n        pass\nclass Dog(Animal):\n    def speak(self):\n        return \"汪汪\"\nclass Cat(Animal):\n    def speak(self):\n        return \"喵喵\"\ndef animal_sound(___):\n    print(animal.speak())\nanimal_sound(Dog())\nanimal_sound(Cat())
5填空题
下面的代码尝试使用鸭子类型实现多态,但缺少关键一行。请补全。\ndef start(device):\n    ___\nclass USB:\n    def connect(self):\n        print(\"USB已连接\")\nclass HDMI:\n    def connect(self):\n        print(\"HDMI已连接\")\nstart(USB())\nstart(HDMI())