Logo
Published on

3.11.面向对象

Authors
  • avatar
    Name
    xiaobai
    Twitter

1.概述

面向对象编程(OOP)是一种将数据和对数据的操作封装在一起,通过"对象"这一基本单元来组织程序的方法。通过OOP,开发者可以更好地模拟现实世界的事物,使程序结构更加清晰、可维护和可扩展。

2.核心概念

2.1.基本术语

术语定义示例
类(Class)对象的蓝图,定义属性和方法class Dog:
对象(Object)类的具体实例dog1 = Dog("Buddy", 3)
属性(Attribute)对象持有的数据nameage
方法(Method)对象可执行的操作bark()eat()
继承(Inheritance)子类获得父类特性class Puppy(Dog):
多态(Polymorphism)不同对象对同一消息的不同响应animal.speak()
封装(Encapsulation)隐藏内部细节,暴露接口私有属性、公共方法

2.2.四大核心特性

  1. 封装:将数据和方法包装在类中
  2. 继承:子类可以继承父类的特性
  3. 多态:不同对象对同一消息做出不同响应
  4. 抽象:隐藏复杂实现细节,只暴露必要接口

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

说明

  • 子类 DogCat 继承了 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 同时继承了 AB 的所有方法。

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.多态的核心

  • 父类定义抽象方法(如areaperimeter),子类各自实现
  • 外部代码可以"面向父类编程",对所有子类对象一视同仁地调用同名方法,而实际执行的是子类自己的实现

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)

无论传入的是RectangleCircleprint_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.如何定义抽象基类?

  1. 继承 ABC: 需要从 abc.ABC 继承。
  2. 使用 @abstractmethod 装饰器: 标记必须在子类中实现的方法。

8.3.示例解析

  • Shape 作为抽象基类,定义了抽象方法 area(面积)和 perimeter(周长)。
  • CircleSquare 继承 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)

  • 静态方法不需要传入 selfcls 参数。
  • 它并不关心类或实例的状态,通常用来实现和类有一定关系的功能,但不依赖于实例或类的属性。

示例

# 定义一个数学工具类
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_gradecalculate_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!")

执行流程:

  1. 调用__enter__()方法,返回文件对象。
  2. 执行with块内的代码。
  3. 离开with块(无论是否异常),自动调用__exit__()方法,进行资源释放。

10.3.总结

  • with语句让资源的获取与释放更加安全和简洁,避免遗忘关闭/释放资源而产生bug或资源泄漏。
  • 凡是实现了__enter____exit__的对象都可用于with
  • 常见应用:文件操作、网络连接、线程锁、数据库游标等。

10.4.contextmanager

contextmanager 是 Python contextlib 模块中的一个装饰器,它让我们无需定义类,也能轻松创建自定义的上下文管理器。 通过在函数内部用 yield 分隔资源获取和释放逻辑,Python 会帮你自动补齐 __enter____exit__ 协议,使得函数对象可以直接用于 with 语句。

使用场景

  • 希望用简单函数包装需要在开始和结束时做些额外处理的语句。
  • 资源的获取与清理逻辑非常简单,不需要完整实现类。

工作流程

  1. 进入 with 语句时,运行到 yield 之前的所有代码(此处获取资源)。
  2. yield 的值(通常是被管理的资源)赋值给 as 后的变量。
  3. 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.总结

面向对象编程的核心优势:

  • 代码重用:通过继承和组合
  • 维护性:封装使代码更易维护
  • 扩展性:多态和继承便于扩展
  • 组织性:更好的代码组织结构
img