面向对象高级-多态与鸭子类型
面向对象高级-多态与鸭子类型
多态的含义与写法(父类类型参数接收任意子类对象),以及"关注行为不关注类型"的鸭子类型
多态是什么
多态是指同一个方法,具有不同的形态、行为、表现。
class Car: def charge(self): print('正在补充燃料...')
class FuelCar(Car): def charge(self): print('燃油汽车正在加油...')
class ElectricCar(Car): def charge(self): print('电动汽车正在充电...')
# 函数参数类型声明为父类def handle_charge(car: Car): car.charge()
handle_charge(FuelCar()) # 燃油汽车正在加油...handle_charge(ElectricCar()) # 电动汽车正在充电...关键写法:定义函数时参数类型指定为父类类型,执行时传入不同的子类对象,同一句 car.charge() 就表现出不同形态。
多态的价值在于调用方不用关心具体是哪种子类。以后新增”氢能车”,只要它也继承 Car 并实现 charge(),上面的 handle_charge 一行都不用改。
鸭子类型(Duck Typing)
如果它走路像鸭子、叫起来像鸭子,那么它就是鸭子。
在鸭子类型中,我们关注的是对象的行为(它有什么方法),而不是对象的类型(它是什么类)。
class Dog: def __init__(self, name, age): self.name = name self.age = age
def swimming(self): print(f'{self.age} 岁的 {self.name} 正在游泳...')
class Duck: def __init__(self, name, age): self.name = name self.age = age
def swimming(self): print(f'{self.age} 岁的 {self.name} 正在游泳...')
class Pig: def __init__(self, name, age): self.name = name self.age = age
def swimming(self): print(f'{self.age} 岁的 {self.name} 正在游泳...')
def go_swimming(duck): # 参数没写类型,只要"会游泳"就行 duck.swimming()
if __name__ == '__main__': go_swimming(Dog("旺财", 4)) go_swimming(Duck("唐老鸭", 2)) go_swimming(Pig("佩奇", 1))| 多态 | 鸭子类型 | |
|---|---|---|
| 关注点 | 父类 + 子类的继承体系 | 有没有那个方法(行为) |
| 是否需要继承 | 需要(子类继承父类) | 不需要 |
| 好处 | 统一调用入口,便于扩展 | 更灵活,任何对象只要能”做这件事”就能传进来 |
鸭子类型也可以看作多态的一种体现:关注的是对象的行为(有什么方法),而不是对象的类型(无需继承关系)。Python 作为动态类型语言,天然支持鸭子类型。
相关
练习题
一、回忆填空(写完再展开对答案)
- 多态是指同一个方法,具有不同的 ____、行为、表现
- 实现多态的常见写法:函数参数类型指定为 ____ 类型,执行时传入不同的 ____ 对象
- 鸭子类型的说法是”如果它走路像____、叫起来像____,那么它就是鸭子”
- 鸭子类型关注的是对象的 ____(有什么方法),而不是对象的 ____(是什么类)
- 鸭子类型 ____(需要/不需要)存在继承关系
- 多态的好处:调用方不用关心具体的 ____,新增子类时调用代码 ____(要/不用)改
填空答案(做完再点开)
- 形态 2. 父类 / 子类 3. 鸭子、鸭子 4. 行为 / 类型 5. 不需要 6. 子类类型 / 不用
二、裸写题
-
2-1 体会多态 定义
Payment父类(方法pay())和WechatPay、AliPay两个子类各自重写pay();写一个函数checkout(payment: Payment)调用它,分别传入两种支付对象。提示(先自己想,实在想不出再点开)一级 · 思路:父类定义”能支付”,子类各自实现具体支付方式 二级 · 方法:函数形参写父类类型
payment: Payment,函数体只写payment.pay()三级 · 骨架:class WechatPay(Payment):然后重写pay -
2-2 体会鸭子类型 写一个
start_game(device)函数,参数不限制类型,只要对象有play()方法就能传进来——分别传手机、电脑、掌机三个互不继承的类。提示一级 · 思路:三个类毫无血缘关系,但都会 play 二级 · 方法:
def start_game(device): device.play()三级 · 骨架:如果传进去的对象没有play(),会报AttributeError——这正说明它检查的是”有没有方法” -
2-3 两者对比 在 2-2 的基础上,故意传一个没有
play()方法的对象,观察报错;再说出多态与鸭子类型的区别。提示一级 · 思路:报错信息能帮你理解”鸭子类型靠行为兜底” 二级 · 方法:用
try/except AttributeError捕获并打印友好提示 三级 · 骨架:多态靠继承体系统一入口;鸭子类型只要方法名对得上就行
参考答案(做完再点开)
# 2-1 多态:靠继承体系class Payment: def pay(self): print("正在支付...")
class WechatPay(Payment): def pay(self): print("微信支付 100 元")
class AliPay(Payment): def pay(self): print("支付宝支付 100 元")
def checkout(payment: Payment): payment.pay()
checkout(WechatPay())checkout(AliPay())
# 2-2 / 2-3 鸭子类型:不需要继承关系class Phone: def play(self): print("手机:启动手游")
class Laptop: def play(self): print("电脑:启动端游")
class Console: def play(self): print("掌机:启动主机游戏")
class Fridge: # 没有 play 方法 pass
def start_game(device): device.play()
start_game(Phone())start_game(Laptop())start_game(Console())
try: start_game(Fridge())except AttributeError as e: print("启动失败:", e) # 'Fridge' object has no attribute 'play'评论区
如果你喜欢,那么欢迎来到我的世界!
了解更多













