面向对象编程(Object-Oriented Programming,OOP)是现代编程语言的基石,它通过封装、继承、多态三大特性,让代码更易维护、复用和扩展。本文将深入讲解这三大特性在 Python 中的具体实现,并结合实际代码示例,帮助你彻底掌握 OOP 的核心思想。
一、封装(Encapsulation)
封装是 OOP 的第一大特性,它隐藏对象的内部状态和实现细节,只对外提供有限的访问接口。这就像一台电视机,你只需要知道按哪个按钮换台、调音量,而不需要了解内部的电路原理。
1.1 封装的核心价值
- 提高安全性:防止外部代码随意修改对象的内部状态。
- 简化接口:用户只需关注“做什么”,而不必知道“怎么做”。
- 易于维护:内部实现可以自由修改,只要接口不变,外部代码无需调整。
1.2 Python 中的访问控制
Python 没有严格的 private、protected、public 关键字,而是通过命名约定来实现访问控制。
1.2.1 单下划线 _(受保护的)
以单下划线开头的属性和方法,约定为“受保护的”(protected),表示不建议在类外部直接访问,但 Python 并不会阻止你访问。
class Person:
def __init__(self, name, age):
self._name = name # 受保护的属性
self._age = age
def _display_info(self): # 受保护的方法
print(f"Name: {self._name}, Age: {self._age}")
# 可以访问,但不建议
p = Person("Alice", 25)
print(p._name) # 可以访问,但不推荐
p._display_info() # 可以调用,但不推荐
1.2.2 双下划线 __(私有的)
以双下划线开头的属性和方法,Python 会进行名称修饰(Name Mangling),使其在类外部无法直接访问,从而实现“私有”效果。
class Person:
def __init__(self, name, age):
self.__name = name # 私有属性
self.__age = age
def __private_method(self): # 私有方法
print("This is private")
p = Person("Bob", 30)
# 直接访问会报错
print(p.__name) # AttributeError: 'Person' object has no attribute '__name'
p.__private_method() # AttributeError: 'Person' object has no attribute '__private_method'
1.2.3 名称修饰的原理
Python 会将 __name 重命名为 _类名__name,所以你仍然可以通过这个“变形”后的名字访问,但强烈不建议这样做。
# 通过变形后的名字可以访问(不推荐!)
print(p._Person__name) # Bob
# 查看对象的所有属性
print(dir(p))
# 输出中包含 '_Person__name' 和 '_Person__age'
1.2.4 前后双下划线 __xxx__(魔法方法)
这是 Python 的魔法方法(Magic Methods),用于实现特殊功能,如 __init__(构造函数)、__str__(字符串表示)等。这些方法可以被外部调用,但通常不直接调用。
class Person:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Person: {self.name}"
p = Person("Charlie")
print(p) # 自动调用 __str__,输出:Person: Charlie
1.3 封装的正确实践:使用属性装饰器
为了既保护数据又提供访问接口,Python 推荐使用 @property 装饰器。
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
"""获取姓名(只读)"""
return self._name
@property
def age(self):
"""获取年龄"""
return self._age
@age.setter
def age(self, value):
"""设置年龄,可以添加验证逻辑"""
if value < 0:
raise ValueError("年龄不能为负数")
self._age = value
p = Person("David", 28)
print(p.name) # David
print(p.age) # 28
p.age = 30 # 通过 setter 修改
print(p.age) # 30
# p.name = "Eve" # 报错:AttributeError: can't set attribute
二、继承(Inheritance)
继承是类与类之间的关系,子类可以继承父类的属性和方法,实现代码复用。就像孩子继承父母的基因,子类“天生”就拥有父类的功能。
2.1 继承的基本语法
class Animal: # 父类(基类)
def __init__(self, name):
self.name = name
def speak(self):
return "动物叫"
class Dog(Animal): # 子类(派生类)
def speak(self):
return "汪汪汪"
class Cat(Animal):
def speak(self):
return "喵喵喵"
dog = Dog("旺财")
print(dog.name) # 旺财(继承自父类)
print(dog.speak()) # 汪汪汪(重写了父类方法)
2.2 继承的层次结构
Python 支持多重继承,所有类最终都继承自 object 类。
class A:
pass
class B(A): # 单继承
pass
class C(A, B): # 多重继承(不推荐,容易混乱)
pass
print(issubclass(B, A)) # True
print(issubclass(C, object)) # True,所有类都是 object 的子类
2.3 方法重写(Override)
子类可以重新定义父类的方法,实现自己的行为。
class Vehicle:
def move(self):
return "交通工具移动"
class Car(Vehicle):
def move(self): # 重写父类方法
return "汽车在路上行驶"
class Plane(Vehicle):
def move(self): # 重写父类方法
return "飞机在空中飞行"
car = Car()
print(car.move()) # 汽车在路上行驶
2.4 super() 函数
super() 用于调用父类的方法,在重写方法时保留父类的功能。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"我叫{self.name},今年{self.age}岁"
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age) # 调用父类的 __init__
self.student_id = student_id
def introduce(self):
# 先调用父类的方法,再扩展
base_intro = super().introduce()
return f"{base_intro},学号是{self.student_id}"
stu = Student("小明", 18, "2023001")
print(stu.introduce()) # 我叫小明,今年18岁,学号是2023001
2.5 继承中的常见错误与修复
错误示例:属性访问问题
class Person:
def __init__(self, name, age):
self.__name = name # 私有属性
self.__age = age
def sing(self):
print(f"{self.name}爱唱歌") # 错误:访问的是 self.name,不是 self.__name
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age)
print(f"我叫{self.name}") # 错误:同样访问 self.name
# 运行会报错:AttributeError: 'Student' object has no attribute 'name'
修复方案
class Person:
def __init__(self, name, age):
self._name = name # 改为受保护属性
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
def sing(self):
print(f"{self.name}爱唱歌") # 现在可以正确访问
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age)
self.student_id = student_id
print(f"我叫{self.name},学号是{self.student_id}") # 通过 property 访问
stu = Student("小红", 20, "2023002")
stu.sing() # 小红爱唱歌
三、多态(Polymorphism)
多态是指同一个方法在不同的对象上会有不同的行为。就像“叫”这个方法,狗叫是“汪汪”,猫叫是“喵喵”,但都是“叫”。
3.1 多态的核心思想
- 接口统一:不同对象对同一消息做出不同响应。
- 提高灵活性:代码可以处理多种类型的对象,而无需关心具体类型。
- 易于扩展:新增子类时,现有代码无需修改。
3.2 Python 中的多态实现
Python 是动态类型语言,多态是天然支持的。
class Animal:
def speak(self):
raise NotImplementedError("子类必须实现 speak 方法")
class Dog(Animal):
def speak(self):
return "汪汪汪"
class Cat(Animal):
def speak(self):
return "喵喵喵"
class Duck(Animal):
def speak(self):
return "嘎嘎嘎"
def animal_speak(animal):
"""多态的体现:同一个函数可以处理不同类型的对象"""
print(animal.speak())
# 创建不同动物对象
animals = [Dog(), Cat(), Duck()]
# 统一调用 speak 方法,但得到不同结果
for animal in animals:
animal_speak(animal)
# 输出:
# 汪汪汪
# 喵喵喵
# 嘎嘎嘎
3.3 鸭子类型(Duck Typing)
Python 的多态不依赖于继承,而是依赖于“鸭子类型”:如果一个对象走起来像鸭子、叫起来像鸭子,那么它就是鸭子。
class Dog:
def speak(self):
return "汪汪汪"
class Robot:
def speak(self):
return "我是机器人"
class Car:
def speak(self):
return "滴滴滴"
def make_sound(obj):
"""不关心 obj 的类型,只关心它有没有 speak 方法"""
print(obj.speak())
# 不同类型的对象,只要有 speak 方法就能调用
make_sound(Dog()) # 汪汪汪
make_sound(Robot()) # 我是机器人
make_sound(Car()) # 滴滴滴
3.4 多态的实际应用:函数参数
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
def print_area(shape):
"""多态:可以接收任何 Shape 子类的对象"""
print(f"面积: {shape.area()}")
rect = Rectangle(5, 3)
circle = Circle(4)
print_area(rect) # 面积: 15
print_area(circle) # 面积: 50.24
四、魔法方法(Magic Methods)详解
魔法方法是 Python 面向对象编程的高级特性,它们以双下划线开头和结尾,让自定义对象能够像内置类型一样工作。
4.1 为什么需要魔法方法?
假设你写了一个自定义列表类:
class MyList:
def __init__(self, data):
self.data = data
def get_length(self):
return len(self.data)
def get_item(self, index):
return self.data[index]
ml = MyList([1, 2, 3])
print(ml.get_length()) # 3
print(ml.get_item(1)) # 2
这样写虽然能用,但不 Pythonic!Python 开发者习惯用 len(obj) 而不是 obj.get_length(),用 obj[1] 而不是 obj.get_item(1)。
4.2 常用魔法方法示例
4.2.1 __len__:实现 len() 函数
class MyList:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
ml = MyList([1, 2, 3, 4, 5])
print(len(ml)) # 5,自动调用 __len__
4.2.2 __getitem__:实现索引访问 []
class MyList:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
self.data[index] = value
def __delitem__(self, index):
del self.data[index]
ml = MyList([10, 20, 30])
print(ml[1]) # 20,自动调用 __getitem__
ml[1] = 200 # 自动调用 __setitem__
print(ml[1]) # 200
del ml[0] # 自动调用 __delitem__
print(len(ml.data)) # 2
4.2.3 __str__ 和 __repr__:对象字符串表示
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
"""用户友好的字符串表示,用于 print()"""
return f"点({self.x}, {self.y})"
def __repr__(self):
"""开发者友好的字符串表示,用于调试"""
return f"Point(x={self.x}, y={self.y})"
p = Point(3, 4)
print(p) # 点(3, 4) – 调用 __str__
print(repr(p)) # Point(x=3, y=4) – 调用 __repr__
4.2.4 __iter__ 和 __next__:实现迭代器
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
for num in CountDown(5):
print(num, end=" ") # 5 4 3 2 1
4.2.5 __call__:让对象可调用
class Adder:
def __init__(self, n):
self.n = n
def __call__(self, x):
return self.n + x
add5 = Adder(5)
print(add5(10)) # 15,对象像函数一样被调用
print(add5(20)) # 25
4.3 完整示例:自定义列表类
class MyList:
"""一个功能完整的自定义列表类"""
def __init__(self, *args):
self.data = list(args)
def __len__(self):
return len(self.data)
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
self.data[index] = value
def __delitem__(self, index):
del self.data[index]
def __str__(self):
return str(self.data)
def __repr__(self):
return f"MyList({self.data})"
def __add__(self, other):
"""实现 + 运算符"""
if isinstance(other, MyList):
return MyList(*(self.data + other.data))
return MyList(*(self.data + list(other)))
def append(self, item):
self.data.append(item)
def pop(self, index=–1):
return self.data.pop(index)
# 使用示例
ml = MyList(1, 2, 3)
print(len(ml)) # 3
print(ml[1]) # 2
ml[1] = 20
print(ml) # [1, 20, 3]
ml.append(4)
print(ml) # [1, 20, 3, 4]
ml2 = MyList(5, 6)
ml3 = ml + ml2
print(ml3) # [1, 20, 3, 4, 5, 6]
五、综合案例:员工管理系统
让我们用一个完整的案例,综合运用封装、继承、多态和魔法方法。
class Employee:
"""员工基类"""
def __init__(self, name, emp_id, salary):
self._name = name
self._emp_id = emp_id
self._salary = salary
@property
def name(self):
return self._name
@property
def emp_id(self):
return self._emp_id
@property
def salary(self):
return self._salary
@salary.setter
def salary(self, value):
if value < 0:
raise ValueError("工资不能为负数")
self._salary = value
def work(self):
"""工作方法,子类需要重写"""
raise NotImplementedError("子类必须实现 work 四、排序与反转:sort / reverse / sorted
## 1. sort()、reverse() 是**原地修改**,无返回值
这是**超级高频错误**:误以为会返回新列表
```python
# 错误写法
lst = [3,1,2]
new_lst = lst.sort()
print(new_lst) # None!sort 没有返回值
- lst.sort() / lst.reverse():直接改原列表,返回 None
- 想要新列表、保留原列表 → 用内置函数 sorted() / 切片反转
2. 不同数据类型不能一起排序
列表中同时存在数字、字符串,调用 sort() 直接报错
lst = [1, "2", 3]
lst.sort() # 报错:TypeError: '<' not supported between instances of str and int
3. reverse() 只是反转顺序,不是降序
- reverse():单纯颠倒元素位置,不做大小比较
- sort(reverse=True):先排序,再变成降序
lst = [3,1,2]
lst.reverse()
print(lst) # [2, 1, 3] 不是降序
4. sorted 可以反转、指定规则
sorted 不修改原列表,支持 reverse、key 参数,用法和 sort 一致。
五、列表复制:深浅拷贝大坑(重中之重)
1. 直接赋值 = 引用传递,不是复制
new_lst = lst 只是两个变量指向同一个列表,改一个全变
lst = [1,2,3]
new_lst = lst
new_lst.append(4)
print(lst) # [1,2,3,4] 原列表被篡改
2. 浅拷贝:copy() / 切片 [:]
适用于一维简单列表(数字、字符串),正常使用;
但嵌套列表(二维/多维) 只会拷贝外层,内层还是引用:
lst = [[1,2], 3]
new_lst = lst.copy()
new_lst[0][0] = 99
print(lst) # [[99, 2], 3] 原列表内层被修改
解决嵌套列表:使用 copy 模块的 deepcopy 深拷贝
import copy
lst = [[1,2], 3]
new_lst = copy.deepcopy(lst)
new_lst[0][0] = 99
print(lst) # 原列表不变
六、其他通用易错点
1. 列表索引从 0 开始,负数索引倒着数
- 正索引:0,1,2… 从左到右
- 负索引:-1 最后一个,-2 倒数第二个
lst = [10,20,30]
print(lst[–1]) # 30
易错:取最后一个元素别写成 lst[len(lst)],会索引越界。
2. 列表是可变对象,不能作为字典的键
字典键要求不可变类型(字符串、数字、元组),list 可变,直接报错:
d = {[1,2]: 100} # 报错:TypeError: unhashable type: 'list'
3. 切片操作不会越界报错
和索引取值不同,切片 lst[a:b] 起止索引超出范围不会报错,会自动截断:
lst = [1,2,3]
print(lst[10:20]) # []
print(lst[–10:1]) # [1]
快速避错总结(背诵版)
class list:
def __init__(self,lst):
self.lst = lst
def _getthing_(self,index):
return self.lst[index]
def _removething_(self,index):
del self.lst[index] #除了return,还有del可以使用
def _len_(self):
return len(self.lst)
def append(self,item):
self.lst.append(item)
如果
myList = [1,2,4,5,6,7,89]
douElg = list(myList)
print(douElg._len_())
print(douElg.append(10))
print(douElg._len_())
则输出为7
None
8
我先从你的代码运行结果解释入手,再彻底讲清楚魔法方法 vs 普通方法的核心区别、能不能不用、以及为什么必须掌握它。
一、先解释你的代码输出
你的代码运行结果完全正确,先拆解每一步:
myList = [1,2,4,5,6,7,89]
douElg = list(myList) # 调用你自定义的list类的__init__方法
print(douElg._len_()) # 手动调用你写的_len_(),返回len([1,2,4,5,6,7,89]) → 7
print(douElg.append(10)) # 调用你写的append(),它内部调用了原生list.append()
# 原生append是原地修改,返回None → 输出None
print(douElg._len_()) # 列表现在多了一个10,长度8 → 输出8
⚠️ 关键纠正:你写的 _len_()、_getthing_()、_removething_() 根本不是魔法方法!
- 真正的Python魔法方法是前后各两个下划线:__len__、__getitem__、__delitem__
- 单下划线 _xxx_ 只是你自己随便起的普通方法名,Python解释器完全不认识
二、魔法方法 vs 普通方法:核心区别
用一张表直接对比,一目了然:
| 命名规范 | 严格:前后各两个下划线 __xxx__ | 任意合法标识符,通常用小写+下划线 |
| 调用方式 | Python解释器自动调用,用户几乎不手动写 | 必须用户手动调用 |
| 触发时机 | 对应特定Python语法/内置函数 | 只有你写 obj.method() 时才执行 |
| 作用 | 实现Python语言的标准协议,让自定义对象拥有和内置类型一样的行为 | 实现你自己的业务逻辑 |
| 归属 | Python语言层面定义的标准接口 | 开发者自定义的功能 |
| 示例 | __len__ → 触发 len(obj)__getitem__ → 触发 obj[index]__init__ → 触发 obj() 创建对象 | 你写的 _len_()、append()、calculate() |
三、不使用魔法方法能运行吗?
完全可以! 你的代码就是最好的证明。
你完全可以不用任何魔法方法,所有功能都用自己的普通方法实现:
- 想获取长度 → 写 get_length() 手动调用
- 想按索引取值 → 写 get_item(index) 手动调用
- 想删除元素 → 写 delete_item(index) 手动调用
但这样写会有灾难性的问题,我用你的代码改一下给你看:
不用魔法方法的写法(反例)
class MyList:
def __init__(self, lst): # 其实__init__也是魔法方法,这里先保留
self.data = lst
# 自己写的普通方法
def get_length(self):
return len(self.data)
def get_item(self, index):
return self.data[index]
def delete_item(self, index):
del self.data[index]
def append(self, item):
self.data.append(item)
# 使用时必须记住所有自定义方法名
ml = MyList([1,2,3])
print(ml.get_length()) # 3
print(ml.get_item(1)) # 2
ml.delete_item(0)
print(ml.get_length()) # 2
用魔法方法的写法(正例)
class MyList:
def __init__(self, lst):
self.data = lst
# 实现魔法方法,Python自动识别
def __len__(self):
return len(self.data)
def __getitem__(self, index):
return self.data[index]
def __delitem__(self, index):
del self.data[index]
def append(self, item):
self.data.append(item)
# 使用时和Python原生list一模一样!
ml = MyList([1,2,3])
print(len(ml)) # 直接用内置函数len() → 3
print(ml[1]) # 直接用[]语法 → 2
del ml[0] # 直接用del语法
print(len(ml)) # 2
四、为什么必须使用魔法方法?(核心价值)
魔法方法不是Python的“炫技”,而是让你的代码变得Pythonic、易用、可维护的核心手段。
1. 与Python内置语法无缝兼容,行为一致
这是最核心的原因。
- 所有Python开发者都知道 len(obj) 是获取长度,obj[index] 是取值,del obj[index] 是删除
- 如果你用自己的 get_length()、get_item(),别人用你的类时必须先读你的文档,记一堆特殊方法名
- 实现了魔法方法,你的自定义类就和原生 list、dict、str 行为完全一致,零学习成本
2. 自动触发,代码更简洁优雅
魔法方法让你能用语法糖代替繁琐的方法调用:
| ml.get_length() | len(ml) |
| ml.get_item(1) | ml[1] |
| ml.set_item(1, 100) | ml[1] = 100 |
| ml.delete_item(1) | del ml[1] |
| for i in ml.get_iterator(): | for i in ml: |
3. 能使用Python所有内置功能和标准库
Python的大量内置函数、标准库都是基于魔法方法工作的:
- for 循环依赖 __iter__ 或 __getitem__
- sorted() 依赖 __lt__(小于比较)
- print(obj) 依赖 __str__
- if obj: 依赖 __bool__
如果你不实现对应的魔法方法,这些功能都用不了:
ml = MyList([1,2,3])
# 如果你没实现__iter__,这行直接报错!
for num in ml:
print(num)
# 如果你没实现__str__,print会输出丑陋的内存地址
print(ml) # <__main__.MyList object at 0x7f8a1b2c3d30>
4. 实现强大的自定义行为
魔法方法让你能自定义几乎所有Python语法的行为:
- 运算符重载:__add__ → a + b,__mul__ → a * b
- 比较运算:__eq__ → a == b,__lt__ → a < b
- 上下文管理器:__enter__、__exit__ → with obj:
- 可调用对象:__call__ → obj() 像函数一样调用
五、针对你的代码的修正建议
把你写的普通方法改成对应的标准魔法方法,让它变成一个真正好用的自定义列表类:
class MyList: # 注意:不要和Python原生的list重名!容易出bug
def __init__(self, lst):
self.lst = lst
# 对应 len()
def __len__(self):
return len(self.lst)
# 对应 [] 取值
def __getitem__(self, index):
return self.lst[index]
# 对应 del [] 删除
def __delitem__(self, index):
del self.lst[index]
# 对应 [] 赋值
def __setitem__(self, index, value):
self.lst[index] = value
def append(self, item):
self.lst.append(item)
# 对应 print() 和 str()
def __str__(self):
return str(self.lst)
# 现在用起来和原生list一模一样!
myList = [1,2,4,5,6,7,89]
douElg = MyList(myList)
print(len(douElg)) # 7
douElg.append(10)
print(len(douElg)) # 8
print(douElg[0]) # 1
douElg[0] = 100
print(douElg) # [100, 2, 4, 5, 6, 7, 89, 10]
del douElg[–1]
print(len(douElg)) # 7




