Python面向对象:多态特性与鸭子类型技术详解
一、开篇:同一个接口,不同的行为
多态(Polymorphism)是OOP的四大特性之一。“poly”=多,“morph”=形态——同一段代码可以作用于不同类型的对象,只要它们有相同的接口。
⌨️ Python中多态无处不在:
# len()函数对不同类型都有效——这就是多态print(len("hello"))# 5 —— 字符串print(len([1,2,3]))# 3 —— 列表print(len({"a":1}))# 1 —— 字典print(len(range(100)))# 100 —— range对象# len()不关心你是什么类型——只要你实现了__len__方法二、两种多态实现方式
2.1 继承多态
# 通过继承实现多态——经典的OOP方式classAnimal:defspeak(self):raiseNotImplementedErrorclassDog(Animal):defspeak(self):return"汪汪!"classCat(Animal):defspeak(self):return"喵喵!"classDuck(Animal):defspeak(self):return"嘎嘎!"# 同一个函数——处理不同类型的动物defanimal_chorus(animals):"""动物合唱团——不关心具体是什么动物"""foranimalinanimals:print(animal.speak())animal_chorus([Dog(),Cat(),Duck(),Dog()])# 汪汪! 喵喵! 嘎嘎! 汪汪!2.2 鸭子类型——Python的"真·多态"
# 💡 鸭子类型:如果它走路像鸭子,叫起来像鸭子——那它就是鸭子# 不关心对象的类型,只关心它有没有需要的方法/属性classDog:defspeak(self):return"汪汪!"classCat:defspeak(self):return"喵喵!"classRobot:"""机器人——不继承任何动物类,但有speak方法"""defspeak(self):return"哔哔!你好!"classPerson:"""人——同样不继承动物类"""defspeak(self):return"大家好!"# animal_chorus对它们都有效!# 不需要继承自Animal——只要有speak()方法就行animal_chorus([Dog(),Cat(),Robot(),Person()])# 汪汪! 喵喵! 哔哔!你好! 大家好!# 💡 这就是鸭子类型的精髓:# Python不检查类型——只检查方法是否存在# 这比严格的继承多态更灵活三、鸭子类型的实际应用
3.1 文件类对象
# Python中许多API接受"file-like object"——任何有read/write方法的对象# 真正的文件defprocess_data(source):"""处理数据源——不关心是文件、字符串还是网络流"""data=source.read()print(f"读取了{len(data)}字节")returndata.upper()# 文件对象# with open("data.txt") as f:# result = process_data(f)# StringIO——在内存中模拟文件fromioimportStringIO fake_file=StringIO("hello world")result=process_data(fake_file)print(result)# HELLO WORLD# 任何有read()方法的对象都可以!classMockSource:"""模拟数据源——测试用"""defread(self):return"mock data"result=process_data(MockSource())print(result)# MOCK DATA3.2 迭代器协议
# for循环不要求你继承特定类——只需要实现__iter__和__next__classCountdown:"""倒计时——实现了迭代器协议"""def__init__(self,start):self.count=startdef__iter__(self):returnselfdef__next__(self):ifself.count<=0:raiseStopIteration self.count-=1returnself.count+1# for循环对这个自定义类同样有效forninCountdown(5):print(n,end=" ")# 5 4 3 2 1print()# 列表、字典、生成器、文件...都实现了迭代器协议# for循环通过鸭子类型支持所有这些类型3.3 isinstance vs 鸭子类型
# 两种风格对比classPaymentProcessor:"""支付处理器"""defprocess(self,payment_method,amount):"""处理支付"""# 风格一:类型检查(不推荐)# if not isinstance(payment_method, (CreditCard, WeChatPay, Alipay)):# raise TypeError("不支持的支付方式")# 风格二:鸭子类型(推荐)# 不检查类型——直接调用,不行就报错try:payment_method.pay(amount)return"支付成功"exceptAttributeError:raiseTypeError("支付方式必须有pay()方法")exceptExceptionase:returnf"支付失败:{e}"# 各支付方式——不需要继承基类classCreditCard:defpay(self,amount):print(f"信用卡支付¥{amount}")classWeChatPay:defpay(self,amount):print(f"微信支付¥{amount}")classAlipay:defpay(self,amount):print(f"支付宝支付¥{amount}")processor=PaymentProcessor()processor.process(CreditCard(),100)processor.process(WeChatPay(),200)# 新增支付方式——不需要修改PaymentProcessor!classBitcoinPay:defpay(self,amount):print(f"比特币支付¥{amount}")processor.process(BitcoinPay(),500)# 直接能用!四、ABC抽象基类:在鸭子类型和类型检查间平衡
fromabcimportABC,abstractmethod# ABC提供了"可选的类型约束"# 它不像Java的interface那样强制——但能提供更清晰的错误信息classPaymentMethod(ABC):"""支付方式——抽象基类"""@abstractmethoddefpay(self,amount):pass@abstractmethoddefrefund(self,amount):pass# 继承ABC——如果缺少抽象方法,实例化时会报错classCreditCard(PaymentMethod):defpay(self,amount):print(f"信用卡支付¥{amount}")defrefund(self,amount):print(f"退款¥{amount}到信用卡")# 不继承ABC的类——仍然可以通过鸭子类型使用classWeChatPay:# 没有继承PaymentMethoddefpay(self,amount):print(f"微信支付¥{amount}")defrefund(self,amount):print(f"退款¥{amount}到微信")# 两者都能用formethodin[CreditCard(),WeChatPay()]:method.pay(100)method.refund(50)五、总结
多态让代码灵活而通用。Python通过鸭子类型将多态发挥到了极致——不需要继承,不需要接口,只要对象有对应的方法就能工作。
💡核心要点:
- 继承多态:子类重写父类方法——经典的OOP方式
- 鸭子类型:只看方法不看类型——Python的默认做法
- “如果它走路像鸭子”——有
read()就是文件,有__iter__就能遍历 - EAFP(求原谅比求许可容易):
try用方法,不行就报错,而不是提前检查isinstance
✅Python多态的精髓:写代码时不要问"你是什么类型?“,而要问"你能做什么?”。