一、核心概念
面向对象编程(OOP)是一种编程范式,核心思想是将程序抽象为 “对象” 的集合,每个对象包含属性(数据)和方法(行为)。Python 作为多范式语言,原生支持 OOP,且语法简洁灵活。
OOP 三大特性:
- 封装:隐藏对象内部细节,仅暴露必要接口;
- 继承:子类复用父类的属性和方法,实现代码复用;
- 多态:不同对象对同一方法做出不同响应,提高代码灵活性。
二、类与对象的基础
1. 类的定义与实例化
类是对象的 “模板”,对象是类的 “实例”。
python
运行
# 定义类
class Person:
# 类属性(所有实例共享)
species = "Human"
# 初始化方法(构造函数)
def __init__(self, name, age):
# 实例属性(每个实例独有)
self.name = name
self.age = age
# 实例方法
def introduce(self):
return f"我是{self.name},今年{self.age}岁"
# 实例化对象
p1 = Person("Alice", 25)
p2 = Person("Bob", 30)
# 访问属性和方法
print(p1.name) # Alice
print(p1.introduce()) # 我是Alice,今年25岁
print(Person.species) # Human(类属性)
2. 封装:隐藏与访问控制
Python 通过命名约定和语法机制实现封装,核心是区分 “外部可访问” 和 “内部私有” 属性 / 方法:
(1)单下划线属性(_attr):约定俗成的 “受保护属性”
- 语法规则:无特殊改写,仅通过命名约定标识;
- 访问限制:无语法层面限制,外部可直接访问;
- 设计意图:提示开发者 “这是内部属性,子类可继承 / 修改,外部尽量避免直接访问”。
python
运行
class Parent:
def __init__(self):
self._protected = "受保护属性" # 单下划线:受保护
class Child(Parent):
def __init__(self):
super().__init__()
self._protected = "子类覆盖受保护属性" # 可直接覆盖
c = Child()
print(c._protected) # 子类覆盖受保护属性(语法上允许访问)
(2)双下划线属性(__attr):语法级 “私有属性”(名称改写)
- 语法规则:Python 自动将__attr改写为_类名__attr(名称改写机制);
- 访问限制:直接访问__attr会报错,需通过改写后的名称访问(不推荐);
- 设计意图:避免子类意外覆盖父类私有属性,确保类内部逻辑不被破坏。
python
运行
class Parent:
def __init__(self):
self.__private = "父类私有属性" # 双下划线:私有,触发名称改写
class Child(Parent):
def __init__(self):
super().__init__()
self.__private = "子类私有属性" # 改写为_Child__private,不覆盖父类
c = Child()
# print(c.__private) # 报错:AttributeError
print(c._Parent__private) # 父类私有属性(改写后的名称,超级规操作)
print(c._Child__private) # 子类私有属性(改写后的名称)
print(c.__dict__) # {'_Parent__private': '父类私有属性', '_Child__private': '子类私有属性'}
(3)封装的实现方式
- 基础方式:通过get_xxx()/set_xxx()方法控制属性访问,包含逻辑校验;
- 优雅方式:使用property装饰器,将方法伪装为属性访问,兼顾控制与简洁。
python
运行
class Student:
def __init__(self, name, score):
self.name = name
self.__score = score # 私有属性
# getter方法:控制读取逻辑
def get_score(self):
return self.__score
# setter方法:控制写入逻辑(数据验证)
def set_score(self, new_score):
if 0 <= new_score <= 100:
self.__score = new_score
else:
raise ValueError("分数无效")
# 或使用property装饰器(更优雅)
class Student:
def __init__(self, name, score):
self.name = name
self.__score = score
@property
def score(self): # 相当于getter,通过s.score访问
return self.__score
@score.setter
def score(self, new_score): # 相当于setter,通过s.score = ...赋值
if 0 <= new_score <= 100:
self.__score = new_score
else:
raise ValueError("分数无效")
s = Student("Alice", 85)
print(s.score) # 85(看似属性访问,实则调用getter)
s.score = 90 # 调用setter,验证数据
# s.score = 101 # 报错:ValueError
三、继承:代码复用与扩展
1. 单继承
子类继承父类的属性和方法,可重写或扩展父类逻辑。
python
运行
class Teacher(Person): # 继承Person类
def __init__(self, name, age, subject):
# 调用父类构造方法
super().__init__(name, age)
self.subject = subject
# 重写父类方法
def introduce(self):
return f"我是{self.name},教{self.subject},今年{self.age}岁"
t = Teacher("David", 35, "数学")
print(t.introduce()) # 我是David,教数学,今年35岁
2. 多继承与 MRO
Python 支持多继承,通过MRO(方法解析顺序) 解决冲突:
- 使用类名.mro()或类名.__mro__查看 MRO 顺序;
- MRO 遵循 “子类优先、声明顺序优先、无重复” 原则(C3 线性化算法)。
python
运行
class A:
def func(self): print("A.func")
class B(A):
def func(self): print("B.func")
class C(A):
def func(self): print("C.func")
class D(B, C): # 多继承,先继承B,再继承C
pass
print(D.mro()) # [D, B, C, A, object]
d = D()
d.func() # B.func(按MRO顺序调用)
3.super()的使用
super()基于 MRO 动态调用父类方法,而非固定指向某一父类:
- 单继承中:super()指向直接父类;
- 多继承中:super()指向 MRO 中当前类的下一个类。
python
运行
class D(B, C):
def func(self):
super().func() # 调用MRO中下一个类(B)的func
super(B, self).func() # 调用B之后的类(C)的func
d = D()
d.func() # 输出:B.func → C.func
4. 属性查找机制
访问obj.attr时,Python 的查找顺序:
- 实例obj的命名空间(obj.__dict__);
- 实例所属类的命名空间(obj.__class__.__dict__);
- 按 MRO 顺序查找父类的命名空间。
- 普通属性:重名时 “先找到的覆盖后找到的”;
- 双下划线属性:因名称改写成为不同属性,避免覆盖,实现父子类私有属性共存。
四、多态:灵活的行为扩展
多态指 “同一方法在不同对象上表现不同行为”,Python 通过动态类型天然支持:
python
运行
class Cat:
def make_sound(self):
print("喵喵喵")
class Dog:
def make_sound(self):
print("汪汪汪")
# 统一接口
def animal_sound(animal):
animal.make_sound()
animal_sound(Cat()) # 喵喵喵
animal_sound(Dog()) # 汪汪汪
五、魔术方法(特殊方法)
魔术方法以__开头和结尾,用于实现对象的特殊行为(如运算符重载、字符串表明等)。
|
方法 |
作用 |
示例 |
|
__init__ |
初始化对象 |
def __init__(self, x): |
|
__str__ |
自定义对象的字符串表明 |
def __str__(self): return f”Obj({self.x})” |
|
__repr__ |
自定义对象的官方字符串表明(调试用) |
def __repr__(self): return f”Obj({self.x})” |
|
__add__ |
重载+运算符 |
def __add__(self, other): return self.x + other.x |
|
__iter__ |
让对象可迭代 |
def __iter__(self): return iter(self.data) |
示例:
python
运行
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
六、抽象类与接口
1. 抽象类(abc 模块)
抽象类包含抽象方法(仅声明,无实现),不能直接实例化,子类必须实现抽象方法:
python
运行
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self): # 抽象方法
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self): # 必须实现抽象方法
return 3.14 * self.radius **2
# s = Shape() # 报错:无法实例化抽象类
c = Circle(5)
print(c.area()) # 78.5
2. 接口:鸭子类型
Python 无显式接口,通过 “鸭子类型” 实现:对象只要具备指定方法 / 属性,即视为符合 “接口”。
python
运行
# 无需继承,只要有area方法即视为“Shape接口”
class Square:
def __init__(self, side):
self.side = side
def area(self):
return self.side** 2
def print_area(shape):
print(f"面积:{shape.area()}")
print_area(Square(4)) # 面积:16
七、常见注意事项
- self的含义:self代表实例本身,是实例方法的第一个参数(名称约定俗成,可自定义,但不推荐);
- 类属性与实例属性:类属性属于类,所有实例共享;实例属性属于单个实例,互不影响;
- 避免多继承的 “菱形问题”:通过 MRO 和super()解决,优先使用组合而非多继承;
- 封装的本质:Python 无真正私有属性,__attr的名称改写是 “约定层面的私有”,而非 “语法强制禁止访问”;
- 动态特性:Python 支持动态给对象添加属性 / 方法,灵活但需谨慎(避免破坏封装);
- 名称改写的目的:不是 “禁止访问”,而是避免子类意外覆盖父类私有属性,确保类内部逻辑安全。
八、FAQ:初学者常见误解与疑问
1.self到底是什么?可以省略吗?
self是实例方法的第一个参数,指向当前实例对象本身,Python 通过self让实例能访问自己的属性和方法。
- 实例方法必须显式声明self(或其他名称,如this,但约定用self),否则调用时会报错(参数数量不匹配);
- 类方法用cls取代self,指向类本身;静态方法无需self/cls。
python
运行
class Test:
def func(self): # 必须有self
print(self)
t = Test()
t.func() # <__main__.Test object at 0x...>(打印实例本身)
2. 类属性和实例属性有什么区别?为什么修改类属性会影响所有实例?
- 类属性:定义在类中、方法外,属于类,所有实例共享(存在类的命名空间里);
- 实例属性:定义在__init__等方法中,以self.xxx声明,属于单个实例(存在实例的命名空间里)。
修改类属性会影响所有未单独赋值的实例,而实例属性仅影响自己:
python
运行
class Person:
count = 0 # 类属性
p1 = Person()
p2 = Person()
Person.count = 10 # 修改类属性
print(p1.count, p2.count) # 10 10(所有实例共享)
p1.count = 20 # 给p1添加实例属性count,覆盖类属性
print(p1.count, p2.count) # 20 10(p2仍用类属性)
3.super()只能调用直接父类吗?多继承时怎么工作?
super()不是 “指向直接父类”,而是根据 MRO 顺序调用当前类的下一个类。
多继承时,super()会严格按照类名.mro()的顺序找下一个类,而非仅直接父类:
python
运行
class A: pass
class B(A): pass
class C(A): pass
class D(B, C):
def func(self):
super().func() # 按MRO[D,B,C,A],调用B的func;若B没有,调用C的
print(D.mro()) # [D,B,C,A,object]
4. 为什么__attr不是真正的私有?名称改写的目的是什么?
Python 的__attr只是 “名称改写”(变成_类名__attr),并非真正禁止访问 —— 这是 Python “信任开发者” 的设计哲学(We are all consenting adults)。
名称改写的核心目的是避免子类意外覆盖父类的私有属性,而非 “防黑客”:
python
运行
class Parent:
def __init__(self):
self.__x = 1 # 改写为_Parent__x
class Child(Parent):
def __init__(self):
super().__init__()
self.__x = 2 # 改写为_Child__x(不会覆盖父类的_Parent__x)
c = Child()
print(c._Parent__x, c._Child__x) # 1 2(父子类的__x共存)
5. 鸭子类型和抽象类有什么区别?该怎么选?
|
特性 |
鸭子类型 |
抽象类(abc 模块) |
|
约束方式 |
运行时检查方法 / 属性是否存在(弱约束) |
编译时强制子类实现抽象方法(强约束) |
|
继承要求 |
无需继承任何类 |
必须继承ABC并实现抽象方法 |
|
适用场景 |
轻量级场景、快速开发、第三方对象适配 |
大型项目、团队协作、需要严格规范 |
选择原则:
- 小项目 / 脚本:用鸭子类型(灵活、简洁);
- 大型框架 / 团队协作:用抽象类(强制规范,避免遗漏方法)。
6. 多态在 Python 中为什么不需要像 Java 那样声明接口?
Python 是动态类型语言,变量无类型约束 —— 调用方法时只关心对象 “有没有这个方法”,而非 “是不是某个类型”(鸭子类型)。
因此 Python 无需显式接口,直接通过方法存在性实现多态,比静态语言更灵活。
7. 为什么要使用property装饰器?直接用get/set方法不行吗?
property的优势是让属性访问更自然(像普通属性一样用obj.attr,而非obj.get_attr()),同时保留get/set的逻辑控制:
python
运行
class Student:
@property
def score(self):
return self.__score
s = Student()
s.score = 90 # 像普通属性赋值,实则调用setter;若没写setter,就是只读属性
直接用get/set方法也能实现,但语法繁琐(s.set_score(90)),property是更优雅的封装方式。
8. 什么是名称改写?由谁执行?如何触发?
名称改写(Name Mangling)是 Python 的一种语法机制:当类中定义以双下划线开头、且不以双下划线结尾的属性 / 方法时(如__attr),Python 解释器会自动将其重命名为_类名__属性名(如_ClassName__attr)。
- 执行者:Python 解释器(编译 / 运行阶段自动处理);
- 触发条件:属性 / 方法名以__开头且不以__结尾(注意:魔术方法如__init__不会触发,由于它以__结尾);
- 目的:避免子类意外覆盖父类的私有属性 / 方法,确保类内部逻辑的独立性。
示例:
python
运行
class Test:
def __init__(self):
self.__x = 1 # 触发名称改写,变为_Test__x
self.__y__ = 2 # 不触发(以__结尾,视为魔术方法)
t = Test()
print(t._Test__x) # 1(改写后的名称可访问)
print(t.__y__) # 2(未改写,直接访问)
9. 何为 MRO?具体规则是什么?
MRO(Method Resolution Order,方法解析顺序)是 Python 在多继承时,查找属性 / 方法的优先级顺序。它通过C3 线性化算法生成,核心规则如下:
- 子类优先于父类:子类的优先级高于任何父类;
- 声明顺序优先:多继承时,括号中先写的父类优先级更高;
- 深度优先调整:避免 “钻石继承”(子类继承两个父类,而两个父类继承同一基类)中的重复查找,确保基类最后被查找;
- 无重复、无环:最终的 MRO 列表中每个类仅出现一次,且无循环依赖。
可通过类名.mro()或类名.__mro__查看 MRO 顺序:
python
运行
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.mro()) # [D, B, C, A, object](符合子类优先、声明顺序优先)
C3 算法的核心目标是:保证继承的单调性(子类优先级始终高于父类)和局部优先级(声明顺序),避免多继承时的属性查找冲突。
九、总结
Python 的 OOP 兼具简洁性和灵活性:
- 基础语法简单,易于上手;
- 支持 OOP 三大特性,满足复杂项目需求;
- 通过命名约定(_/__)和property实现封装,兼顾控制与易用性;
- 结合动态类型和鸭子类型,无需严格的接口约束,适配快速开发场景;
- 通过abc模块支持抽象类,兼顾大型项目的规范需求。















- 最新
- 最热
只看作者