资讯动态

python基础之多态

发布时间:2026/10/13 13:05:38 来源:尧图企业网站定制
前言多态polymorphism的字面意思是多种形态同一个调用、同一个方法名作用在不同类型的对象上会各自产生对应的行为。它解决的核心问题是把调用方和实现方解耦——调用方只说给我算个面积不必知道手里拿的到底是圆、矩形还是三角形。关于多态有两个特别常见的误解第一以为在 Python 里必须先有继承、甚至必须先有抽象基类才能实现多态。实际情况是Python 的多态主要靠鸭子类型duck typing只要对象有同名的方法调用就能成功跟它继承自谁没有必然关系。继承只是组织代码的众多手段之一不是多态的前提。第二以为多态就是写一长串if isinstance(x, A): ... elif isinstance(x, B): ...。这恰恰是反模式真正的多态是把差异下沉到各个类自己的方法实现里调用方只写一次x.area()。本文先讲多态在 Python 里的落地方式再讲它和isinstance的关系最后给一个可以直接复制运行的例子。一、多态的本质调用方只认方法名多态描述的是一个调用点obj.area()。这套机制成立只需要一个条件——obj所属的类里能找到area这个方法。至于这个方法是怎么实现的、这个类继承自哪里、有没有实现某个接口调用点一概不关心。# 适用于 Python 3.8class Circle:def __init__(self, r):self.r rdef area(self):return 3.14159 * self.r ** 2class Rectangle:def __init__(self, w, h):self.w wself.h hdef area(self):return self.w * self.hdef total_area(shapes):return sum(s.area() for s in shapes)shapes [Circle(1), Rectangle(2, 3)]print(total_area(shapes))Circle和Rectangle之间没有任何继承关系total_area照样能工作。这就是鸭子类型它管的是你能不能响应area这个请求而不是你是谁的孩子。Python 官方词汇表对这种风格的描述是——不看对象的类型而是直接调用方法或使用属性用接口而不是具体类型来获得灵活性。二、继承式多态把公共接口写进基类鸭子类型灵活但缺少约束。当一组类确实共享一整套接口时用继承把公共方法名固定下来可读性和可维护性更好也更方便静态类型检查器工作。# 适用于 Python 3.8class Shape:def area(self):raise NotImplementedError(子类必须实现 area())class Circle(Shape):def __init__(self, r):self.r rdef area(self):return 3.14159 * self.r ** 2class Rectangle(Shape):def __init__(self, w, h):self.w wself.h hdef area(self):return self.w * self.h基类Shape.area故意只抛NotImplementedError这是声明接口的常见写法。但要注意这种约束是软约束它只在运行时调用到才会报错而且如果某个子类压根不定义area只要不去调它程序也不会崩。想要编译期准确说是导入/实例化时就拦住需要抽象基类abc.ABC加abstractmethod那是更强的一种约束。三、多态与 isinstance什么时候真需要类型检查多态和isinstance并不冲突它们是两个层面的工具。多态用于分发行为isinstance用于做决策比如选择要不要把对象交给某个函数。isinstance(x, C)会检查x是不是C的实例包括直接、间接和虚拟子类通过register()注册的类。它的第二个参数还可以是类型元组只要是其中之一就返回True。# 适用于 Python 3.8def describe(value):if isinstance(value, (int, float)):return f数值{value}if isinstance(value, str):return f文本长度{len(value)}return 未知类型这里用isinstance是合理的因为函数确实要按类型分支。但不要用type(value) int替代它type()不看继承链子类实例会被判为不相等也会漏掉虚拟子类。四、运算符多态与函数多态多态不限于普通方法。Python 的运算符也是靠特殊方法special method也就是常说的 dunder 方法实现多态的会去调__add__len()会去调__len__for会去调__iter__。# 适用于 Python 3.8class Money:def __init__(self, amount):self.amount amountdef __add__(self, other):if not isinstance(other, Money):return NotImplementedreturn Money(self.amount other.amount)def __repr__(self):return fMoney({self.amount})print(Money(3) Money(4)) # Money(7)注意__add__里返回NotImplemented不是NotImplementedError这是告诉解释器我不认识这个类型你去试试对方对象的__radd__是运算符多态的协作约定。常见坑点1. 用type()判等代替isinstance()❌if type(obj) Shape:—— 子类的实例会被判为False。✅if isinstance(obj, Shape):—— 会沿着继承链正确识别子类与虚拟子类。2. 以为没有继承就没有多态❌ 认为必须写class Circle(Shape)才能让total_area工作。✅ 只要对象有area()方法即可鸭子类型不要求继承关系。3. 用一长串isinstance分支代替多态❌if isinstance(s, Circle): ... elif isinstance(s, Rectangle): ...—— 每加一种形状都要改这个函数。✅ 让每个类自己实现area()调用方只写s.area()。4. 基类抛NotImplementedError却指望一实例化就报错❌Shape()能正常创建直到调area()才炸。✅ 需要更早拦截时改用abc.ABCabstractmethod这类类根本不允许实例化。5. 定义__eq__后对象突然不可哈希❌ 只写了__eq__结果对象放进set报TypeError: unhashable type。✅ Python 3 中一旦类定义了__eq__而没有定义__hash____hash__会被置为None需要可哈希时显式补上__hash__。6.__add__里返回了NotImplementedError而不是NotImplemented❌return NotImplementedError—— 这是把异常类当成值返回运算结果会变成一个异常类对象完全错误。✅return NotImplemented—— 这是值告诉解释器转去尝试反向运算。7. 鸭子类型下用hasattr判断时拼错方法名❌if hasattr(obj, arae):—— 拼写错误永远返回False还会掩盖真正的实现问题。✅ 名字要与实现严格一致或用try/except AttributeErrorEAFP 风格更稳妥。8. 覆盖父类方法时把签名改了❌ 父类def render(self, width)子类写成def render(self, width, height)多态调用时传一个参数直接TypeError。✅ 覆盖方法保持与父类兼容的调用签名多出来的能力用带默认值的参数补齐。总结多态的实现方式关键机制典型场景鸭子类型只要求对象有同名方法松耦合的工具函数、脚本继承 方法覆盖子类重写父类方法有明确层次关系的类型族抽象基类abc.ABCabstractmethod需要强制实现接口运算符多态特殊方法如__add__、__len__自定义数值/容器类型参数多态函数对入参类型不做限制泛型风格的通用函数多态的目的不是用上某个语法而是让调用方只依赖行为、不依赖具体类型。在 Python 里鸭子类型是默认风格继承和抽象基类是在需要约束时叠加的更强工具。判断一段代码是不是用了多态看的是调用点是否只认方法名而不是看它写没写class X(Y)。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑