- Published on
3.11.面向对象
- Authors

- Name
- xiaobai
1.概述
面向对象编程(OOP)是一种将数据和对数据的操作封装在一起,通过"对象"这一基本单元来组织程序的方法。通过OOP,开发者可以更好地模拟现实世界的事物,使程序结构更加清晰、可维护和可扩展。
2.核心概念
2.1.基本术语
| 术语 | 定义 | 示例 |
|---|---|---|
| 类(Class) | 对象的蓝图,定义属性和方法 | class Dog: |
| 对象(Object) | 类的具体实例 | dog1 = Dog("Buddy", 3) |
| 属性(Attribute) | 对象持有的数据 | name、age |
| 方法(Method) | 对象可执行的操作 | bark()、eat() |
| 继承(Inheritance) | 子类获得父类特性 | class Puppy(Dog): |
| 多态(Polymorphism) | 不同对象对同一消息的不同响应 | animal.speak() |
| 封装(Encapsulation) | 隐藏内部细节,暴露接口 | 私有属性、公共方法 |
2.2.四大核心特性
- 封装:将数据和方法包装在类中
- 继承:子类可以继承父类的特性
- 多态:不同对象对同一消息做出不同响应
- 抽象:隐藏复杂实现细节,只暴露必要接口
2.3.为什么要用面向对象?
- 提升代码复用性:类和继承机制让你能方便地复用和扩展已有代码
- 增强程序结构性:清晰划分对象职责,复杂业务拆分更有条理
- 便于维护和升级:封装特性让局部改动对外部影响最小
- 更贴近现实建模:可用类和对象自然映射真实世界中的各种"事物"
面向对象的核心是"万物皆对象"——在开发时,首先思考有哪些对象及其关系,再考虑如何通过类和对象映射到代码上。
3.类和对象
3.1.基本概念
- 类:对象的蓝图,定义了某一类事物所共有的属性(数据)和方法(行为)
- 对象:根据类创建出来的具体实例,每个对象都拥有独立的数据(属性)和专属的方法
3.2.定义类和创建对象
3.2.1.基本语法
class 类名:
def __init__(self, 属性1, 属性2):
self.属性1 = 属性1
self.属性2 = 属性2
def 方法名(self):
# 方法体
pass
# 创建对象(实例化)
对象名 = 类名(参数1, 参数2)
3.2.2.重要说明
__init__是构造方法,创建对象时自动调用,用于初始化对象属性self代表当前对象本身,所有在类的方法中都必须写上(调用时不用传)
3.2.3.实际示例
class Student:
def __init__(self, name, age, score):
self.name = name # 实例属性
self.age = age
self.score = score
def introduce(self):
print(f"我是{self.name},今年{self.age}岁,分数{self.score}")
def is_pass(self):
return self.score >= 60
# 创建一个学生对象
stu1 = Student("Alice", 20, 85)
stu1.introduce() # 输出:我是Alice,今年20岁,分数85
print(stu1.is_pass()) # True
小结:类是模板,对象是具体实例。通过对象,我们可以操作和访问定义在类中的属性和方法。
3.3.实例属性 vs 类属性
在 Python 类中,可以定义两种属性:
- 实例属性:属于具体对象,每个实例(对象)都有自己独立的一份,通常在
__init__方法中通过self.xxx = ...方式定义 - 类属性:属于整个类,所有实例共享同一份,通常在类体内部直接定义
3.3.1.属性对比
| 属性类型 | 定义位置 | 归属 | 访问方法 | 修改影响 |
|---|---|---|---|---|
| 实例属性 | 方法内(如__init__) | 实例 | obj.属性 | 只影响该实例自身 |
| 类属性 | 类体内 | 类 | 类名.属性obj.属性 | 所有实例&类本身 |
3.3.2.示例代码
class Dog:
species = "狗" # 类属性
def __init__(self, name, age):
self.name = name # 实例属性
self.age = age # 实例属性
dog1 = Dog("Buddy", 3)
dog2 = Dog("Lucy", 5)
# 访问实例属性
print(dog1.name, dog2.name) # Buddy Lucy
# 访问类属性(推荐用类名访问,但实例也可访问)
print(dog1.species, dog2.species) # 狗 狗
# 修改实例属性只影响自身
dog1.age = 4
print(dog1.age, dog2.age) # 4 5
# 修改类属性(通过类名修改)
Dog.species = "Dog"
print(dog1.species, dog2.species) # Dog Dog
# 如果通过实例修改类属性,只会为该实例新建同名实例属性
dog1.species = "Wolf"
print(dog1.species) # Wolf
print(dog2.species) # Dog
print(Dog.species) # Dog
小结:类属性适合存放所有对象都一样的数据;实例属性用于每个对象自己的数据。记住"类属性被所有实例共享",而"实例属性只属于自己"。
4.封装
4.1.概念
封装(Encapsulation)是面向对象编程的核心特性之一,强调"把数据(属性)和对数据的操作(方法)打包在一起,并对外隐藏实现细节"。简单来说,就是让"对象自己管理自己的数据",外部代码无法随意直接访问或修改对象的内部状态,而是必须通过公开的方法与对象交互。
4.2.封装的好处
- 安全性:防止外部随意篡改对象的内部数据,降低维护和出现 Bug 的风险
- 灵活性:可以在不影响外部代码的前提下,随时修改类的内部实现
- 可维护性:通过接口(方法)暴露必要功能,提高代码的可读性和可控性
4.3.属性访问控制
在 Python 中,没有像 Java 那样真正的"private",但有命名约定和Name Mangling机制,实现不同级别的封装:
| 访问级别 | 命名方式 | 说明 | 示例 |
|---|---|---|---|
| 公开属性 | self.name | 公共的,外部可直接访问 | self.name |
| 受保护属性 | self._name | 单下划线开头,约定"不要在类外直接访问" | self._nickname |
| 私有属性 | self.__name | 双下划线开头,Python 会做"名称改写" | self.__realname |
4.3.1.示例代码
class Student:
def __init__(self, name):
self.name = name # 公开属性 public
self._nickname = name # 受保护属性 protected
self.__realname = name # 私有属性 private
# 创建实例
stu = Student("小明")
# 公开属性,直接访问
print(stu.name) # 小明
stu.name = "小红"
print(stu.name) # 小红
# 受保护属性(实际能访问,但约定不要在类外直接访问)
print(stu._nickname) # 小明
stu._nickname = "小亮"
print(stu._nickname) # 小亮
# 私有属性,不能直接访问
# print(stu.__realname) # AttributeError,不能直接访问
# 可以通过特殊方式访问私有属性(不推荐)
print(stu._Student__realname) # 小明
小技巧:Python 内部会将
__age自动改名为_类名__age,所以stu._Student__age是可以访问的,但强烈不推荐这么做。
4.4.使用属性装饰器
在 Python 中,可以通过 @property 装饰器,将实例方法变成属性访问的方式,从而优雅地控制获取和设置过程。这种写法既保留了属性式的易用,又提供了逻辑控制点和数据验证,非常适合对类中的私有(或受保护)属性做封装。
4.4.1.为什么用属性装饰器?
- 安全性提升:避免直接暴露内部数据,防止属性被非法赋值
- 易于维护:随时可以在 getter/setter 内添加校验、自动处理等逻辑
- 统一接口:外部代码和普通属性一样调用,无需显式函数调用
4.4.2.基本用法
class Demo:
def __init__(self, value):
self._value = value # 习惯上受保护(单下划线)
# 定义 value 属性的 getter 方法
@property
def value(self):
return self._value
# 定义 value 属性的 setter 方法
@value.setter
def value(self, new_value):
# 检查 new_value 是否为 int 类型且值为非负
if isinstance(new_value, int) and new_value >= 0:
self._value = new_value
else:
raise ValueError("value 必须为非负整数")
# 创建实例
d = Demo(5)
print(d.value) # 相当于 d.value(),自动调用@property修饰的方法,输出 5
d.value = 10 # 相当于 d.value(10),自动调用 setter
# d.value = -3 # 会抛出 ValueError
4.4.3.只读属性
只写 @property,不写 setter,则属性为只读:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def area(self):
return 3.14 * self._radius ** 2
c = Circle(2)
print(c.area) # 12.56
# c.area = 10 # 报错:只读属性
5.继承
5.1.概念
继承(Inheritance)是面向对象编程的核心特性之一,可以基于已有的类(父类/基类)创建出新的类(子类/派生类),新的类自动获得父类的属性和方法,且还能新增或重写方法,从而实现代码复用与扩展。
5.2.继承的作用
- 减少重复代码:抽象出共性的代码到父类,子类重用
- 多态性:父类引用可以指向子类对象,灵活调用子类的实现
- 扩展性强:只需新增子类就可以扩展功能,无须改动父类代码
5.3.基本继承
5.3.1.语法格式
class 父类名:
# 父类定义
class 子类名(父类名):
# 子类定义,自动拥有父类属性和方法
5.3.2.示例:动物基类与具体动物
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
# 父类只定义接口/默认实现,一般由子类重写
raise NotImplementedError("子类必须实现speak方法")
class Dog(Animal):
def speak(self):
return f"{self.name}: 汪汪!"
class Cat(Animal):
def speak(self):
return f"{self.name}: 喵喵!"
dog = Dog("小黑")
cat = Cat("小花")
print(dog.speak()) # 小黑: 汪汪!
print(cat.speak()) # 小花: 喵喵!
print(isinstance(dog, Animal)) # True
说明:
- 子类
Dog、Cat继承了Animal的构造方法和属性 - 子类重写(override)了
speak方法,赋予了不同动物不同的叫声 - 父类的方法
speak使用raise NotImplementedError,约定子类必须实现,否则会报错
继承体现了"由一般到特殊",即公共行为在父类封装,特殊行为在子类自行扩展/重写。
5.4.super() 的使用
在继承中,super() 是一个常用函数,用于调用父类(基类)的方法。特别是在子类重写了父类的方法时,经常需要用 super() 先执行父类的逻辑,然后再扩展属于子类的逻辑。
5.4.1.常见用法
super().__init__(参数...):调用父类的构造方法进行初始化super().方法名():在子类中调用父类的指定方法
5.4.2.示例
class Animal:
def __init__(self, name):
self.name = name
class Bird(Animal):
def __init__(self, name, can_fly=True):
super().__init__(name) # 先初始化父类部分
self.can_fly = can_fly
上面的例子中,super().__init__(name) 确保父类的 name 被初始化,后面再初始化自己的特殊属性 can_fly。
小结:在子类构造方法及对父类功能增强时,记得调用
super(),复用父类逻辑,减少重复代码。
5.5.多重继承
多重继承是指一个类可以同时继承自多个父类,从而获得多个父类的属性和方法。在 Python 中,类名的小括号内可以写多个父类,用逗号分隔。
5.5.1.基本语法
class A:
def foo(self):
print("A.foo")
class B:
def bar(self):
print("B.bar")
class C(A, B):
pass
c = C()
c.foo() # 输出: A.foo
c.bar() # 输出: B.bar
通过多重继承,类 C 同时继承了 A 和 B 的所有方法。
5.5.2.方法解析顺序(MRO)
当继承链复杂时,比如多个父类有同名的方法,Python 会按照"方法解析顺序(Method Resolution Order, MRO)"查找。MRO 采用 C3 线性化算法,优先按照类的继承列表自左向右依次查找,第一个找到的为准。
可以通过类名.__mro__或类名.mro()查看解析顺序:
print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
5.5.3.注意事项
- 多重继承会带来灵活性,但也增加了代码复杂度和歧义风险,建议合理使用
- 当多个父类有同名方法时,建议明确调用(如:A.foo(self)),以避免歧义
6.多态
6.1.概念
多态(Polymorphism)是指"同一种操作作用于不同的对象时,可以有不同的表现"。在面向对象编程中,常通过继承和方法重写实现多态。
6.2.多态的核心
- 父类定义抽象方法(如
area、perimeter),子类各自实现 - 外部代码可以"面向父类编程",对所有子类对象一视同仁地调用同名方法,而实际执行的是子类自己的实现
6.3.示例
import math
# 定义一个矩形类
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# 定义一个圆形类
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
# 打印图形面积和周长信息的函数
def print_shape_info(shape):
print(f"面积: {shape.area():.2f}")
print(f"周长: {shape.perimeter():.2f}")
# 创建图形对象列表,包括一个矩形和一个圆形
shapes = [Rectangle(3, 4), Circle(5)]
# 遍历每个图形,打印其信息
for s in shapes:
print_shape_info(s)
无论传入的是Rectangle、Circle,print_shape_info都可以自动调用对应的area()、perimeter()实现。
6.4.多态的好处
- 扩展性强:只要新定义
Shape的子类,实现相关方法,无需改动外部调用代码即可接入新类型 - 接口一致性:调用方无需关心对象的具体类型,只需要遵循"接口"即可
7.特殊方法(魔术方法)
7.1.概念
在 Python 中,双下划线包裹的方法(如__init__、__str__、__add__等)被称为特殊方法或魔术方法(magic methods)。这些方法允许我们定制类的某些内置行为,例如对象的初始化、打印、运算等。
7.2.常见的特殊方法
| 方法 | 描述 | 触发时机 |
|---|---|---|
__init__(self, ...) | 对象初始化 | 创建对象时自动调用 |
__new__(cls, ...) | 创建对象实例 | 对象创建时调用 |
__str__(self) | 字符串表示 | print(对象) 或 str(对象) 时调用 |
__repr__(self) | 正式字符串表示 | repr(对象) 时调用 |
__add__(self, other) | 加法运算 | + 运算时调用 |
__eq__(self, other) | 等于比较 | == 运算时调用 |
__lt__(self, other) | 小于比较 | < 运算时调用 |
__getitem__(self, index) | 索引取值 | obj[0] 时调用 |
__len__(self) | 长度 | len(obj) 时调用 |
7.3.与
- 在创建一个对象时,会先调用类的
__new__方法分配对象空间,返回实例对象 - 然后将该对象传递给
__init__方法进行初始化 - 通常只需要重写
__init__,但如果需要控制对象的创建过程(如实现单例模式、元类逻辑等),才会重写__new__
class Demo:
def __new__(cls, *args, **kwargs):
print(f"__new__ called: 分配对象空间,cls={cls}")
instance = super().__new__(cls) # 实际分配空间并返回实例
return instance
def __init__(self, value):
print(f"__init__ called: 初始化对象,设置 value={value}")
self.value = value
print("创建 Demo 对象:")
d = Demo(42)
print(f"对象属性 value = {d.value}")
7.4.作用
通过实现这些特殊方法,可以让自定义类的对象拥有和内建类型类似的操作方式,如参与运算、比较、索引取值、与内置函数配合等等,从而让类变得更加易用。
7.5.综合示例:Vector 类
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return self.magnitude() < other.magnitude()
def __len__(self):
return 2 # 总是2D向量
def __getitem__(self, index):
if index == 0:
return self.x
elif index == 1:
return self.y
else:
raise IndexError("Vector index out of range")
def magnitude(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
# 使用示例
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1) # Vector(2, 3)
print(v1 + v2) # Vector(6, 8)
print(v1 * 3) # Vector(6, 9)
print(v1 == Vector(2, 3)) # True
print(v1[0]) # 2
print(v1.magnitude()) # 3.605551275463989
8.抽象基类
在 Python 当中,**抽象基类(Abstract Base Class, ABC)**是一种用于定义接口规范的特殊类。抽象基类不能被实例化,主要用于约定子类必须实现某些特定方法。Python 的 abc 模块提供了相关功能。
8.1.为什么使用抽象基类?
抽象基类可以用于:
- 明确规定子类必须实现的方法(即接口约束),提高代码的规范性和可维护性;
- 实现多态,不同子类实现不同的细节,但接口保持一致;
- 防止直接实例化一个不完整的类。
8.2.如何定义抽象基类?
- 继承
ABC类: 需要从abc.ABC继承。 - 使用
@abstractmethod装饰器: 标记必须在子类中实现的方法。
8.3.示例解析
Shape作为抽象基类,定义了抽象方法area(面积)和perimeter(周长)。Circle和Square继承Shape,并实现了所有抽象方法。- 不能直接创建
Shape的实例,否则会报错。
通过这种方式,可以强制所有形状子类都实现自己的面积和周长计算公式。
注意:
- 如果子类没有实现全部抽象方法,子类本身也会变成抽象类,仍然无法实例化。
- 抽象基类还可以包含普通方法(如
description),供子类直接使用或重写。
from abc import ABC, abstractmethod
import math
class Shape(ABC):
"""形状抽象基类"""
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
# 具体方法
def description(self):
return f"This is a {self.__class__.__name__}"
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
def perimeter(self):
return 4 * self.side
# 不能实例化抽象类
# shape = Shape() # 报错:TypeError
circle = Circle(5)
square = Square(4)
print(circle.area()) # 78.53981633974483
print(square.perimeter()) # 16
print(circle.description()) # This is a Circle
9.类方法和静态方法
在 Python 中,类方法和静态方法是面向对象中经常用到的两种方法类型。它们可以通过装饰器 @classmethod 和 @staticmethod 分别进行定义。
9.1.类方法(@classmethod)
- 类方法的第一个参数是
cls,代表类本身,而不是实例。 - 类方法可以通过类或者实例调用,常用于访问或修改类属性,或提供工厂方法来创建类的对象。
示例:
# 定义一个名为 MyClass 的类
class MyClass:
# 定义类变量 count,初始值为0
count = 0
# 定义一个类方法 increment_count,用于增加 count 的值
@classmethod
def increment_count(cls):
# 将类变量 count 的值加1
cls.count += 1
# 输出 MyClass 类的 count 值,即0
print(MyClass.count)
# 调用 MyClass 的类方法 increment_count,将 count 增加1
MyClass.increment_count()
# 再次输出 MyClass 类的 count 值,即1
print(MyClass.count)
9.2.静态方法(@staticmethod)
- 静态方法不需要传入
self或cls参数。 - 它并不关心类或实例的状态,通常用来实现和类有一定关系的功能,但不依赖于实例或类的属性。
示例:
# 定义一个数学工具类
class MathUtils:
# 定义一个静态方法,用于计算两个数的和
@staticmethod
def add(a, b):
# 返回两个参数的和
return a + b
# 调用MathUtils类的add方法,并打印1和2的和
print(MathUtils.add(1, 2))
9.3.区别
| 类型 | 第一个参数 | 可访问/修改类属性 | 和实例相关 | 通常场景 |
|---|---|---|---|---|
| 实例方法 | self | 是 | 是 | 操作实例状态 |
| 类方法 | cls | 是 | 否 | 操作类属性、工厂方法等 |
| 静态方法 | 无 | 否 | 否 | 工具、工具类函数,不涉及任何状态 |
如下面 Student 类中的例子,get_school_info 是类方法,用于访问类属性;is_passing_grade 和 calculate_average 是静态方法,用于通用的功能,无需关注类或实例属性。
# 定义一个学生类
class Student:
# 定义学校名称,为类属性
school_name = "Python University"
# 记录学生总数,为类属性
students_count = 0
# 构造方法,初始化学生对象
def __init__(self, name, grade):
# 赋值学生姓名
self.name = name
# 赋值学生年级
self.grade = grade
# 创建新学生对象时,学生总数加1
Student.students_count += 1
# 实例方法:获取学生信息
def get_info(self):
# 返回学生的姓名和成绩信息
return f"{self.name} - Grade {self.grade}"
# 类方法:获取学校信息
@classmethod
def get_school_info(cls):
# 返回学校名称和学生总数
return f"School: {cls.school_name}, Students: {cls.students_count}"
# 工厂方法:通过字符串创建学生对象
@classmethod
def create_from_string(cls, student_str):
# 从字符串中分割出姓名和年级
name, grade = student_str.split(',')
# 去除空格并将年级转换为整数后,创建学生对象
return cls(name.strip(), int(grade.strip()))
# 静态方法:判断成绩及格与否
@staticmethod
def is_passing_grade(grade):
# 成绩大于等于60为及格
return grade >= 60
# 静态方法:计算成绩平均值
@staticmethod
def calculate_average(grades):
# 如果成绩列表非空,返回平均值,否则返回0
return sum(grades) / len(grades) if grades else 0
# 创建两个学生对象,分别为Alice和Bob
student1 = Student("Alice", 85)
student2 = Student("Bob", 92)
# 调用实例方法,输出student1的信息
print(student1.get_info()) # Alice - Grade 85
# 调用类方法,输出学校信息和学生总数
print(Student.get_school_info()) # School: Python University, Students: 2
# 使用工厂方法从字符串创建学生对象
student3 = Student.create_from_string("Charlie, 78")
# 输出student3的信息
print(student3.get_info()) # Charlie - Grade 78
# 调用静态方法,判断75是否及格
print(Student.is_passing_grade(75)) # True
# 调用静态方法,计算平均成绩
print(Student.calculate_average([85, 92, 78])) # 85.0
10.上下文管理器与 with 语句
在 Python 中,with 语句(又称上下文管理器)是一种简洁、安全地自动管理资源的机制。 常用场景包括文件操作、数据库连接、线程锁等,能够确保“进入”和“退出”时自动处理相关清理操作。
10.1.基础语法
with 表达式 as 变量:
# 在这里进行资源操作
...
# 离开with块后,资源会被自动释放
最常见示例:文件操作
with open("data.txt", "r") as f:
content = f.read()
# 离开with块后,文件f会被自动关闭,无需手动调用f.close()
10.2.工作原理
with需要一个上下文管理器对象。这个对象需要实现两个特殊方法:__enter__()和__exit__()。- 进入
with语句时,自动调用__enter__(),该方法的返回值赋给as后的变量。 - 无论块中是否发生异常,最后都会自动调用
__exit__()方法,实现资源清理。
- 进入
10.2.1.自定义上下文管理器示例
class FileOpener:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
print("打开文件")
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
print("关闭文件")
if self.file:
self.file.close()
# 可以选择是否处理异常(True防止异常继续向上传播,False/不返回则传播)
# 使用自定义上下文管理器
with FileOpener("demo.txt", "w") as f:
f.write("hello, context manager!")
执行流程:
- 调用
__enter__()方法,返回文件对象。 - 执行with块内的代码。
- 离开with块(无论是否异常),自动调用
__exit__()方法,进行资源释放。
10.3.总结
with语句让资源的获取与释放更加安全和简洁,避免遗忘关闭/释放资源而产生bug或资源泄漏。- 凡是实现了
__enter__和__exit__的对象都可用于with。 - 常见应用:文件操作、网络连接、线程锁、数据库游标等。
10.4.contextmanager
contextmanager 是 Python contextlib 模块中的一个装饰器,它让我们无需定义类,也能轻松创建自定义的上下文管理器。 通过在函数内部用 yield 分隔资源获取和释放逻辑,Python 会帮你自动补齐 __enter__ 和 __exit__ 协议,使得函数对象可以直接用于 with 语句。
使用场景:
- 希望用简单函数包装需要在开始和结束时做些额外处理的语句。
- 资源的获取与清理逻辑非常简单,不需要完整实现类。
工作流程:
- 进入
with语句时,运行到yield之前的所有代码(此处获取资源)。 yield的值(通常是被管理的资源)赋值给as后的变量。with块执行结束后(无论是否抛出异常),自动运行yield之后的代码(收尾清理)。
简要优点:
- 省去手写
__enter__和__exit__。 - 代码清晰简洁,易于维护。
常见用法是包裹文件、数据库连接、锁、环境切换等场景。
from contextlib import contextmanager
@contextmanager
def simple_open(filename, mode):
print("进入上下文,打开文件")
f = open(filename, mode)
try:
yield f
finally:
print("退出上下文,关闭文件")
f.close()
with simple_open("abc.txt", "w") as f:
f.write("带装饰器的上下文管理器")
借助@contextmanager,只需要写好 yield 前后的资源获取和释放逻辑即可。
小结:
with语句用于自动管理资源获取和释放,避免资源泄露。- 只要实现了
__enter__和__exit__的对象都可以配合with语句使用。 - 推荐在涉及资源使用的场景下,优先使用
with(如文件、数据库、锁等)。
11.综合示例:完整的面向对象系统
# 导入抽象基类模块
from abc import ABC, abstractmethod
# 导入当前日期时间模块
from datetime import datetime
# 导入类型提示列表
from typing import List
# 定义员工抽象基类
class Employee(ABC):
# """员工抽象基类"""
# 初始化员工信息
def __init__(self, name: str, employee_id: str, salary: float):
# 员工姓名
self.name = name
# 员工编号
self.employee_id = employee_id
# 员工工资
self.salary = salary
# 入职日期设为当前时间
self.hire_date = datetime.now()
# 抽象方法,计算奖金
@abstractmethod
def calculate_bonus(self) -> float:
# """计算奖金"""
pass
# 抽象方法,获取员工角色
@abstractmethod
def get_role(self) -> str:
# """获取角色"""
pass
# 获取员工基本信息字符串
def get_info(self) -> str:
return f"{self.name}({self.employee_id}) - {self.get_role()}"
# 定义经理类,继承自Employee
class Manager(Employee):
# 初始化经理相关信息
def __init__(self, name: str, employee_id: str, salary: float, department: str):
# 调用父类构造器
super().__init__(name, employee_id, salary)
# 部门名称
self.department = department
# 团队成员列表
self.team: List[Employee] = []
# 计算经理奖金(工资的30%)
def calculate_bonus(self) -> float:
return self.salary * 0.3
# 获取经理的角色名
def get_role(self) -> str:
return f"{self.department}经理"
# 添加团队成员
def add_team_member(self, employee: Employee):
self.team.append(employee)
# 获取团队人数
def get_team_size(self) -> int:
return len(self.team)
# 定义开发工程师类,继承自Employee
class Developer(Employee):
# 初始化开发工程师信息
def __init__(self, name: str, employee_id: str, salary: float, programming_language: str):
# 调用父类构造器
super().__init__(name, employee_id, salary)
# 擅长的编程语言
self.programming_language = programming_language
# 计算开发工程师奖金(工资的20%)
def calculate_bonus(self) -> float:
return self.salary * 0.2
# 获取工程师的角色名
def get_role(self) -> str:
return f"{self.programming_language}开发工程师"
# 定义设计师类,继承自Employee
class Designer(Employee):
# 初始化设计师信息
def __init__(self, name: str, employee_id: str, salary: float, design_tool: str):
# 调用父类构造器
super().__init__(name, employee_id, salary)
# 擅长的设计工具
self.design_tool = design_tool
# 计算设计师奖金(工资的15%)
def calculate_bonus(self) -> float:
return self.salary * 0.15
# 获取设计师的角色名
def get_role(self) -> str:
return f"{self.design_tool}设计师"
# 定义公司类
class Company:
# 初始化公司信息
def __init__(self, name: str):
# 公司名称
self.name = name
# 员工列表
self.employees: List[Employee] = []
# 雇佣新员工
def hire(self, employee: Employee):
self.employees.append(employee)
# 打印雇佣信息
print(f"已雇佣: {employee.get_info()}")
# 计算公司总薪酬(基本工资+奖金)
def calculate_total_payroll(self) -> float:
# 所有员工工资之和
total = sum(emp.salary for emp in self.employees)
# 所有员工奖金之和
total_bonus = sum(emp.calculate_bonus() for emp in self.employees)
return total + total_bonus
# 输出员工列表及奖金
def list_employees(self):
print(f"\n{self.name} 员工列表:")
for employee in self.employees:
print(f" - {employee.get_info()}")
print(f" 奖金: ¥{employee.calculate_bonus():.2f}")
# 使用完整的面向对象系统
# 创建公司对象
company = Company("科技公司")
# 创建经理对象
manager = Manager("王小明", "M001", 80000, "工程部")
# 创建开发工程师对象1
dev1 = Developer("李雷", "D001", 70000, "Python")
# 创建开发工程师对象2
dev2 = Developer("韩梅梅", "D002", 75000, "JavaScript")
# 创建设计师对象
designer = Designer("张伟", "DS001", 65000, "Figma")
# 构建经理的团队,添加开发与设计成员
manager.add_team_member(dev1)
manager.add_team_member(dev2)
manager.add_team_member(designer)
# 公司雇佣所有员工
company.hire(manager)
company.hire(dev1)
company.hire(dev2)
company.hire(designer)
# 输出公司员工信息
company.list_employees()
# 输出公司总薪酬
print(f"\n总薪酬: ¥{company.calculate_total_payroll():.2f}")
# 输出经理的团队人数
print(f"经理团队人数: {manager.get_team_size()}")
12.面向对象设计原则
在面向对象编程(OOP)中,为了让系统更加健壮、灵活以及易于维护,诞生了一系列被业界广泛认可的设计原则。这些原则被简称为 SOLID 原则,是高级面向对象设计必备的知识。
12.1.单一职责原则
- S(Single Responsibility Principle, 单一职责原则) 每个类应该仅有一个引起它变化的原因。也就是说,一个类只负责一项职责。例如,将用户信息管理和邮件发送分离到不同类。
class UserManager:
def __init__(self, name, email):
self.name = name
self.email = email
def save(self):
print(f"保存用户信息:{self.name},{self.email}")
class EmailSender:
def send_email(self, to, subject, content):
print(f"发送邮件给{to},主题:{subject},内容:{content}")
# 用法
user = UserManager("小明", "xiaoming@example.com")
user.save()
email_sender = EmailSender()
email_sender.send_email(user.email, "欢迎", "欢迎注册本系统!")
12.2.开闭原则
- O(Open/Closed Principle, 开闭原则) 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即在不修改原代码的情况下进行扩展,例如通过继承和多态实现不同类型的折扣算法。
class Discount:
def calculate(self, amount):
return amount
class StudentDiscount(Discount):
def calculate(self, amount):
return amount * 0.8 # 学生8折
class VIPDiscount(Discount):
def calculate(self, amount):
return amount * 0.7 # VIP7折
# 用法
def get_final_price(discount: Discount, price):
return discount.calculate(price)
print(get_final_price(StudentDiscount(), 100)) # 输出:80.0
print(get_final_price(VIPDiscount(), 100)) # 输出:70.0
12.3.里氏替换原则
- L(Liskov Substitution Principle, 里氏替换原则) 所有基类出现的地方,子类都应该可以替换,并且保证程序的正确性。例如,子类(如麻雀和鸵鸟)应当可以被用于接受父类(Bird)的地方。
class Bird:
def fly(self):
print("会飞")
class Sparrow(Bird):
def fly(self):
print("麻雀飞翔")
class Ostrich(Bird):
def fly(self):
print("鸵鸟不能飞") # 也可 raising NotImplementedError,视系统需要
# 用法
def bird_fly(bird: Bird):
bird.fly()
bird_fly(Sparrow()) # 输出:麻雀飞翔
bird_fly(Ostrich()) # 输出:鸵鸟不能飞
12.4.接口隔离原则
- I(Interface Segregation Principle, 接口隔离原则) 不应该强迫客户依赖它们不需要的接口。接口应当小而专一。例如,将打印和扫描功能分别定义在不同接口中,而不是在同一个接口中。
# 导入ABC和abstractmethod用于定义抽象基类
from abc import ABC, abstractmethod
# 定义打印机接口,继承自ABC
class IPrinter(ABC):
# 定义抽象方法print_document,子类需实现
@abstractmethod
def print_document(self, doc):
pass
# 定义扫描仪接口,继承自ABC
class IScanner(ABC):
# 定义抽象方法scan_document,子类需实现
@abstractmethod
def scan_document(self, doc):
pass
# 定义多功能打印机类,实现IPrinter和IScanner接口
class MultiFunctionPrinter(IPrinter, IScanner):
# 实现打印文档的方法
def print_document(self, doc):
print(f"打印文档:{doc}")
# 实现扫描文档的方法
def scan_document(self, doc):
print(f"扫描文档:{doc}")
# 定义简单打印机类,只实现打印功能
class SimplePrinter(IPrinter):
# 实现打印文档的方法
def print_document(self, doc):
print(f"只支持打印:{doc}")
# 用法示例
# 创建SimplePrinter实例
printer = SimplePrinter()
# 打印“报告”文档
printer.print_document("报告")
12.5.依赖倒置原则
- D(Dependency Inversion Principle, 依赖倒置原则) 高层模块不应该依赖低层模块,二者都应该依赖其抽象。亦即,代码应依赖于接口或抽象类,而不是具体实现。这样可以增强代码的灵活性和可扩展性。
# 导入ABC和abstractmethod用于定义抽象基类
from abc import ABC, abstractmethod
# 定义消息发送者接口,继承自ABC抽象基类
class IMessageSender(ABC):
# 声明抽象方法send,子类必须实现
@abstractmethod
def send(self, message):
pass
# 定义电子邮件发送者,实现IMessageSender接口
class EmailSender(IMessageSender):
# 实现send方法,打印发送邮件的信息
def send(self, message):
print(f"发送邮件:{message}")
# 定义短信发送者,实现IMessageSender接口
class SMSSender(IMessageSender):
# 实现send方法,打印发送短信的信息
def send(self, message):
print(f"发送短信:{message}")
# 定义通知类,用于发送通知
class Notification:
# 构造方法,初始化时需要传入一个发送者对象
def __init__(self, sender: IMessageSender):
self.sender = sender
# 通知方法,调用发送者的send方法发送消息
def notify(self, msg):
self.sender.send(msg)
# 创建EmailSender的Notification对象
email_notify = Notification(EmailSender())
# 创建SMSSender的Notification对象
sms_notify = Notification(SMSSender())
# 使用email_notify发送一条通知
email_notify.notify("SOLID 原则学习成功!")
# 使用sms_notify发送一条通知
sms_notify.notify("请查收验证码。")
13.总结
面向对象编程的核心优势:
- 代码重用:通过继承和组合
- 维护性:封装使代码更易维护
- 扩展性:多态和继承便于扩展
- 组织性:更好的代码组织结构

