从零理解 Python 面向对象:类、继承、多态与魔法方法 引言如果你写过一段时间 Python一定见过这样的代码hello.upper()、[1,2,3].append(4)、{a:1}.get(b)。你有没有想过为什么字符串、列表、字典这些东西后面可以用.调用方法答案是它们都是对象。Python 是一门彻底的面向对象语言——一切皆对象。整数是对象、字符串是对象、函数是对象、甚至类本身也是对象。理解面向对象不仅能让你写出更结构化、更易维护的代码更是读懂 Python 标准库和第三方框架源码的钥匙。本文将从零开始系统讲解 Python 面向对象的四大核心类与对象、继承、多态、魔法方法帮你建立完整的 OOP 知识体系。一、类与对象从现实世界到代码1.1 什么是类什么是对象类Class是对象的蓝图/模板定义了一类事物共同的属性数据和方法行为。对象Object是类的具体实例。用现实世界类比汽车是一个类它有品牌、颜色、速度等属性有启动、加速、刹车等方法而你家楼下那辆红色的特斯拉就是汽车类的一个具体对象。1.2 定义类与创建对象classDog:# 初始化方法创建对象时自动调用def__init__(self,name,age):self.namename# 实例属性self.ageage# 实例方法defbark(self):print(f{self.name}汪汪叫)definfo(self):print(f我叫{self.name}今年{self.age}岁)# 创建对象实例化dog1Dog(旺财,3)dog2Dog(大黄,5)# 调用方法dog1.bark()# 旺财 汪汪叫dog2.info()# 我叫 大黄今年 5 岁# 访问属性print(dog1.name)# 旺财几个关键点__init__是初始化方法创建对象时自动调用用于设置初始属性。self代表当前对象本身是实例方法的第一个参数调用时自动传入不需要手动写。每个对象有独立的属性空间修改dog1.name不影响dog2。1.3 实例属性 vs 类属性classDog:species犬科动物# 类属性所有实例共享def__init__(self,name):self.namename# 实例属性每个实例独立dog1Dog(旺财)dog2Dog(大黄)print(dog1.species)# 犬科动物print(dog2.species)# 犬科动物共享同一个类属性# 修改类属性所有实例都受影响Dog.species哺乳动物print(dog1.species)# 哺乳动物print(dog2.species)# 哺乳动物# 通过实例赋值类属性实际是创建了同名实例属性不影响其他实例dog1.species特殊品种print(dog1.species)# 特殊品种实例属性优先print(dog2.species)# 哺乳动物仍是类属性判断用哪个所有实例共享的常量/状态用类属性每个实例不同的数据用实例属性。1.4 实例方法、类方法、静态方法classMathUtils:PI3.14159# 实例方法第一个参数 self访问实例属性definstance_method(self):print(f实例方法PI {self.PI})# 类方法第一个参数 cls访问类属性可用于替代构造函数classmethoddefclass_method(cls):print(f类方法PI {cls.PI})# 静态方法不需要 self 或 cls就是普通函数放在类里staticmethoddefadd(a,b):returnab# 调用MathUtils.class_method()# 类方法PI 3.14159print(MathUtils.add(1,2))# 3objMathUtils()obj.instance_method()# 实例方法PI 3.14159类方法的经典用途是定义多种构造方式classPerson:def__init__(self,name,age):self.namename self.ageageclassmethoddeffrom_birth_year(cls,name,birth_year):age2026-birth_yearreturncls(name,age)p1Person(小明,18)p2Person.from_birth_year(小红,2005)print(p2.age)# 21二、继承代码复用的核心机制2.1 什么是继承继承让一个类子类/派生类获得另一个类父类/基类的属性和方法实现代码复用和层次化设计。classAnimal:def__init__(self,name):self.namenamedefeat(self):print(f{self.name}在吃东西)defsleep(self):print(f{self.name}在睡觉)# Dog 继承 AnimalclassDog(Animal):defbark(self):print(f{self.name}汪汪叫)# Cat 继承 AnimalclassCat(Animal):defmeow(self):print(f{self.name}喵喵叫)dogDog(旺财)dog.eat()# 旺财 在吃东西继承自 Animaldog.bark()# 旺财 汪汪叫自己的方法catCat(咪咪)cat.sleep()# 咪咪 在睡觉cat.meow()# 咪咪 喵喵叫2.2 方法重写与 super()子类可以重写父类的方法也可以用super()调用父类的实现classAnimal:def__init__(self,name,age):self.namename self.ageagedefmake_sound(self):print(动物发出声音)classDog(Animal):def__init__(self,name,age,breed):super().__init__(name,age)# 调用父类的 __init__self.breedbreed# 子类自己的属性defmake_sound(self):super().make_sound()# 可选调用父类的实现print(汪汪汪)dogDog(旺财,3,金毛)dog.make_sound()# 动物发出声音# 汪汪汪print(dog.breed)# 金毛2.3 多继承与 MROPython 支持多继承一个类可以同时继承多个父类classA:defmethod(self):print(A.method)classB:defmethod(self):print(B.method)classC(A,B):passcC()c.method()# A.methodA 在前面优先当多个父类有同名方法时Python 按MRO方法解析顺序决定调用哪个。可以用__mro__或mro()查看print(C.__mro__)# (class __main__.C, class __main__.A, class __main__.B, class object)MRO 遵循 C3 线性化算法保证每个类只出现一次且子类在父类前面。多继承虽然强大但容易让代码复杂实际开发中应谨慎使用优先用组合代替多继承。2.4 所有类都继承自 objectPython 3 中所有类都隐式继承自object它提供了__init__、__str__、__repr__、__eq__等基础魔法方法。classMyClass:passprint(issubclass(MyClass,object))# True三、多态同一接口不同实现3.1 什么是多态多态Polymorphism指不同类的对象对同一消息方法调用做出不同响应。Python 的多态基于鸭子类型——不关心对象是什么类型只关心它有没有对应的方法。classDog:defmake_sound(self):return汪汪汪classCat:defmake_sound(self):return喵喵喵classDuck:defmake_sound(self):return嘎嘎嘎# 多态同一个函数接受不同对象调用同名方法defanimal_sound(animal):print(animal.make_sound())animals[Dog(),Cat(),Duck()]foranimalinanimals:animal_sound(animal)# 汪汪汪# 喵喵喵# 嘎嘎嘎animal_sound函数不需要知道参数是什么类型只要它有make_sound方法就能工作——这就是鸭子类型“走起来像鸭子叫起来像鸭子那它就是鸭子。”3.2 多态的好处扩展性强新增一个类不需要修改原有函数就能工作。消除条件分支不用写大量if isinstance(...)判断。# 不好的写法用类型判断defbad_sound(animal):ifisinstance(animal,Dog):print(汪汪汪)elifisinstance(animal,Cat):print(喵喵喵)# 每加一种动物都要改这个函数# 好的写法多态defgood_sound(animal):print(animal.make_sound())3.3 抽象基类可选虽然 Python 靠鸭子类型实现多态但有时你想强制子类必须实现某个方法可以用abc模块定义抽象基类fromabcimportABC,abstractmethodclassShape(ABC):abstractmethoddefarea(self):passabstractmethoddefperimeter(self):passclassCircle(Shape):def__init__(self,radius):self.radiusradiusdefarea(self):return3.14*self.radius**2defperimeter(self):return2*3.14*self.radiusclassRectangle(Shape):def__init__(self,width,height):self.widthwidth self.heightheightdefarea(self):returnself.width*self.heightdefperimeter(self):return2*(self.widthself.height)# shape Shape() # 报错抽象类不能实例化shapes[Circle(5),Rectangle(3,4)]forsinshapes:print(f面积:{s.area()}, 周长:{s.perimeter()})如果子类没有实现所有抽象方法实例化时就会报错起到了接口约束的作用。四、封装保护内部状态4.1 访问控制Python 没有真正的私有属性靠命名约定实现classBankAccount:def__init__(self,owner,balance):self.ownerowner# 公开属性self._balancebalance# 约定为内部使用但仍可外部访问self.__password123456# 名称改写外部难以直接访问accountBankAccount(小明,1000)print(account.owner)# 小明print(account._balance)# 1000仍可访问只是约定# print(account.__password) # AttributeError名称被改写了print(account._BankAccount__password)# 123456其实还是能访问_单下划线约定为内部属性提醒不要随便动但不强制。__双下划线触发名称改写name mangling变成_类名__属性名防止子类意外覆盖但不是真正的私有。Python 哲学“我们都是成年人”靠约定而不是强制来保护内部状态。4.2 property把方法变成属性property让你可以用属性访问的语法调用方法实现 getter/setter同时保持接口简洁classCircle:def__init__(self,radius):self._radiusradiuspropertydefradius(self):获取半径returnself._radiusradius.setterdefradius(self,value):设置半径带校验ifvalue0:raiseValueError(半径必须大于 0)self._radiusvaluepropertydefarea(self):只读属性面积return3.14*self._radius**2cCircle(5)print(c.radius)# 5像访问属性一样调用 getterprint(c.area)# 78.5只读属性c.radius10# 调用 setterprint(c.area)# 314.0# c.radius -1 # ValueError: 半径必须大于 0# c.area 100 # AttributeError只读属性不能赋值property的好处是外部代码始终用c.radius这种简洁语法内部可以随时加上校验、计算逻辑不需要改调用方代码。五、魔法方法让对象拥有超能力魔法方法Magic Methods是 Python 中以双下划线开头和结尾的特殊方法它们让自定义类可以支持运算符、内置函数、上下文管理等语言级行为。5.1 字符串表示str和reprclassPoint:def__init__(self,x,y):self.xx self.yy# 面向用户的友好字符串表示print() 和 str() 调用def__str__(self):returnf点({self.x},{self.y})# 面向开发者的精确表示repr() 和交互式环境调用def__repr__(self):returnfPoint(x{self.x}, y{self.y})pPoint(3,4)print(p)# 点(3, 4)调用 __str__print(repr(p))# Point(x3, y4)调用 __repr__经验法则__repr__的结果应该尽量能用来重新创建这个对象即eval(repr(obj)) obj。5.2 运算符重载classVector:def__init__(self,x,y):self.xx self.yydef__repr__(self):returnfVector({self.x},{self.y})# 加法def__add__(self,other):returnVector(self.xother.x,self.yother.y)# 减法-def__sub__(self,other):returnVector(self.x-other.x,self.y-other.y)# 相等比较def__eq__(self,other):returnself.xother.xandself.yother.y# 模长自定义方法defmagnitude(self):return(self.x**2self.y**2)**0.5v1Vector(1,2)v2Vector(3,4)print(v1v2)# Vector(4, 6)print(v2-v1)# Vector(2, 2)print(v1Vector(1,2))# Trueprint(v2.magnitude())# 5.0类似的还有__mul__乘法、__lt__小于、__le__小于等于等定义后自定义对象就能像内置类型一样使用运算符。5.3 容器行为len、getitem、iterclassPlaylist:def__init__(self,songs):self.songssongs# len() 调用def__len__(self):returnlen(self.songs)# 下标访问playlist[0]def__getitem__(self,index):returnself.songs[index]# 迭代for song in playlistdef__iter__(self):returniter(self.songs)# 成员判断song in playlistdef__contains__(self,item):returniteminself.songs playlistPlaylist([稻香,晴天,七里香])print(len(playlist))# 3print(playlist[1])# 晴天forsonginplaylist:print(song)# 稻香 晴天 七里香print(晴天inplaylist)# True定义了这些魔法方法你的自定义类就能像列表一样被len()、下标、for循环、in操作使用——这就是 Python 的协议思想不需要继承特定基类实现对应方法即可。5.4 可调用对象call定义__call__后对象可以像函数一样被调用classAdder:def__init__(self,n):self.nndef__call__(self,x):returnxself.n add5Adder(5)print(add5(10))# 15对象像函数一样被调用print(add5(100))# 105这其实就是用类实现闭包的效果状态更清晰。装饰器也可以用__call__实现。5.5 上下文管理器enter和exit定义这两个方法后对象可以用在with语句中classFileManager:def__init__(self,filename,mode):self.filenamefilename self.modemode self.fileNonedef__enter__(self):self.fileopen(self.filename,self.mode,encodingutf-8)returnself.file# 返回的值赋给 as 后面的变量def__exit__(self,exc_type,exc_val,exc_tb):ifself.file:self.file.close()# 返回 True 表示吞掉异常False/None 表示正常传播returnFalsewithFileManager(test.txt,w)asf:f.write(Hello, 魔法方法)# 离开 with 块时自动调用 __exit__文件自动关闭Python 内置的open()、线程锁、数据库连接等都实现了上下文管理器协议with语句是资源管理的最佳实践。六、常见坑点与最佳实践坑点 1可变对象作为类属性classStudent:courses[]# 类属性所有实例共享def__init__(self,name):self.namenamedefadd_course(self,course):self.courses.append(course)s1Student(小明)s2Student(小红)s1.add_course(数学)print(s2.courses)# [数学]小红也有了数学课共享了同一个列表正确做法把可变对象放在__init__里作为实例属性。坑点 2继承时忘记调用 super().initclassAnimal:def__init__(self,name):self.namenameclassDog(Animal):def__init__(self,name,breed):# 忘记 super().__init__(name)self.name 不会被设置self.breedbreed dogDog(旺财,金毛)# print(dog.name) # AttributeError子类重写__init__时一定要记得调用super().__init__(...)初始化父类部分。坑点 3过度使用继承继承是is-a关系组合是has-a关系。很多初学者滥用继承导致层次过深、耦合过重。优先使用组合# 不好为了复用引擎代码让 Car 继承 Engine# class Car(Engine): ...# 好Car 有一个 Engine组合classEngine:defstart(self):print(引擎启动)classCar:def__init__(self):self.engineEngine()# 组合defstart(self):self.engine.start()print(汽车出发)最佳实践总结类名用大驼峰MyClass方法和属性用小写下划线my_method。公开属性优先需要保护时用_单下划线避免__双下划线。用property管理需要校验或计算的属性。优先组合谨慎继承多继承尤其要少用。魔法方法按需实现让类支持 Python 惯用语法len()、迭代、上下文管理等。抽象基类用于接口约束普通场景靠鸭子类型即可。结语面向对象不是玄学而是一种组织代码的思维方式类与对象让你把数据和行为打包模拟现实世界的实体继承让你复用代码、构建层次化的类族多态让你用统一接口处理不同对象提升扩展性封装让你保护内部状态对外暴露简洁接口魔法方法让你的自定义类融入 Python 的语言生态像内置类型一样自然。掌握了这些你不仅能写出结构清晰的代码更能读懂 Django、Flask、NumPy 等框架的设计思路。