python基础-面向对象编程(2)

介绍面向对象编程三大特性:封装(数据与方法捆绑,隐藏实现细节,强调高内聚低耦合)、继承(代码重用,子类继承父类属性与方法)、多态(同一接口多种实现,鸭子类型与抽象基类)。通过Python代码示例,展示封装、继承、多态、方法重写、object类及特殊方法与属性

Posted by Hilda on July 9, 2025

1、封装

image-20250703170013103

封装是面向对象编程的三大核心特性之一(另两个是继承和多态)。它指的是将数据(属性)和操作数据的方法(行为)捆绑在一起,形成一个独立的单元——对象。同时,封装也强调对内部实现细节的隐藏,只对外提供有限且明确的接口,以保护数据的完整性和安全性。

封装的核心思想是“高内聚,低耦合”。

高内聚: 指一个模块内部的元素(数据和方法)彼此紧密相关,共同完成一个单一的、明确的功能。

低耦合: 指模块之间相互依赖的程度低,一个模块的改变对其他模块的影响尽可能小。

在Python中,封装的实现主要依赖于以下几个方面:

  • 捆绑数据和方法: 这是通过在类中定义属性和方法自然实现的。一个 Person 类会包含 name, age 等属性,以及 walk(), eat() 等方法,这些都属于 Person 对象。

  • 信息隐藏(访问控制): 这是封装的关键部分。Python不像Java或C++那样提供严格的 private 或 protected 关键字来强制限制访问。Python采用的是一种约定俗成的访问控制机制:

    • 公共成员 (Public Members): 任何不以一个或两个下划线开头的属性和方法都被认为是公共的,可以从类的外部直接访问。这是Python中默认的访问级别。

    • 受保护成员 (Protected Members): 以一个下划线 _ 开头的属性和方法(例如 _protected_attribute_protected_method())被约定为“受保护的”。这意味着它们应该被视为类的内部实现细节,不鼓励从外部直接访问,但从技术上讲,它们仍然是可访问的。这是一种给开发者看的信号。

    • 私有成员 (Private Members): 以两个下划线 __ 开头(但不能以两个下划线结尾,例如 __private_attribute__private_method())的属性和方法被约定为“私有的”。Python解释器会对这类名称进行名称修饰(Name Mangling),使其在类的外部难以直接访问。

  • 属性访问器 (Getters/Setters) 和 @property 装饰器:

    • 虽然Python不强制私有化,但为了更好地控制属性的读取和写入,通常会使用 getter 和 setter 方法。

    • Python提供了 @property 装饰器,可以将一个方法伪装成属性,从而在不改变外部访问方式的情况下,在内部对属性的读写进行逻辑控制和验证。这使得属性的访问方式更加“Pythonic”。

名称修饰 (Name Mangling) 的原理: 当Python解释器遇到以双下划线 __ 开头(且不以双下划线结尾)的属性或方法名时,它会在编译阶段将其名称进行修改,变为 _ClassName__attribute_name 的形式。例如,__private_attribute 在 MyClass 中会被转换为 _MyClass__private_attribute。这种机制使得从外部直接通过 obj.__private_attribute 访问变得困难,因为外部代码并不知道这个被修饰后的名称。但如果知道修饰后的名称,仍然可以访问(这说明Python的“私有”是约定而非强制)。

@property 装饰器的原理: @property 是一个内置装饰器,它将一个方法转换为一个属性访问器。当你在类中定义一个 @property 方法时,它实际上创建了一个 property 对象。这个 property 对象内部包含了三个可选的方法:

○ fget:用于获取属性值(@property 装饰的方法)。

○ fset:用于设置属性值(通过 @<property_name>.setter 装饰的方法)。

○ fdel:用于删除属性值(通过 @<property_name>.deleter 装饰的方法)。 当你通过 obj.attribute 访问时,实际上是调用了 fget 方法;当你通过 obj.attribute = value 赋值时,实际上是调用了 fset 方法。这使得你可以在属性的读写操作中嵌入自定义逻辑,实现更精细的封装。


来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/3  17:08


class BankAccount:
    def __init__(self, initial_balance):
        self.balance = initial_balance
        # 受保护成员:以单下划线开头,约定为内部使用
        self._account_number = "123456789"
        # 私有成员:以双下划线开头,Python会进行名称修饰
        self.__balance = initial_balance # 实际存储余额的“私有”属性

    # 使用 @property 实现对 __balance 的封装
    @property
    def balance(self):
        """
        getter 方法:允许外部通过属性方式访问余额,但可以在内部添加逻辑。
        """
        print("正在获取余额...")
        return self.__balance

    @balance.setter
    def balance(self, value):
        """
        setter 方法:允许外部通过属性方式设置余额,但可以在内部进行验证。
        """
        if not isinstance(value, (int, float)):
            raise TypeError("余额必须是数字!")
        if value < 0:
            print("警告: 余额不能为负数,已重置为 0。")
            self.__balance = 0
        else:
            print("正在调用setter方法...")
            print(f"正在设置余额为{value}")
            self.__balance = value

    # 公共方法,提供操作数据的接口
    def deposit(self, amount):
        """
        存款操作
        """
        if amount <= 0:
            print("存款金额不可小于等于0")
        else:
            self.balance += amount  # 通过 setter 修改余额
            print(f"已经成功存款:{amount}元,现余额:{self.balance}元")

    def withdraw(self, amount):
        """
        取款操作
        """
        if amount > 0 and self.balance >= amount:
            self.balance -= amount
            print(f"已经成功取款{amount}元,现余额:{self.balance}元")
        else:
            print(f"余额不足,无法取出 {amount} 元。当前余额: {self.balance} 元。")

    # 演示受保护方法
    def _log_transaction(self, transaction_type, amount):
        """受保护的方法,用于内部记录交易日志。"""
        print(f"内部日志: {transaction_type} {amount} 元。")

    # 演示私有方法
    def __calculate_interest(self):
        """私有方法,计算利息(仅供内部使用)。"""
        print("正在秘密计算利息...")
        return self.__balance * 0.01

    def apply_monthly_interest(self):
        """公共方法,调用私有方法。"""
        interest = self.__calculate_interest()
        self.balance += interest
        print(f"已应用月利息:{interest}元")


account = BankAccount(1000)
# 访问属性 (通过 @property 调用的 getter)
print(f"当前余额:{account.balance}")
# 修改属性 (通过 @property 调用的 setter)
account.balance = 1200
print(f"当前余额:{account.balance}")
# 触发setter中的验证逻辑
account.balance = -200
print(f"当前余额:{account.balance}")

# 调用公用方法
account.deposit(300)
account.withdraw(200)
account.withdraw(500)
account.apply_monthly_interest()

# 尝试访问 受保护成员(技术上可行,但是不推荐)
print(f"账户号码(受保护):{account._account_number}")

# 尝试直接访问私有成员(失败,因为名称已经被修饰)
try:
    print(account.__balnace)
except AttributeError as e:
    print(f"报错:{e}")

# 尝试访问被名称修饰之后的私有成员(不推荐)
print(account._BankAccount__balance)


image-20250706205645817

2、继承

面向对象编程的另一个核心特性是继承,它允许一个类(子类/派生类)从另一个类(父类/基类)中获取(继承)属性和方法。继承促进代码重用,建立了类之间is a的关系(比如,狗是一种动物)

继承是实现代码重用和构建类层次结构的关键机制。

  • 父类:被继承的类,定义了通用的属性和行为。
  • 子类:继承父类的类。子类会自动拥有父类的所有公共和受保护属性及方法。子类可以在继承的基础上添加新的属性和方法,或者重写(覆盖)父类的方法以改变其行为。
  • is a的关系:继承表达的是一种is a的关系,比如狗是一种动物等等。

Python中的继承:

  • 单继承:一个子类只继承一个父类,语法就是class ChildClass(ParentClass):
  • 多重继承:一个子类可以继承多个父类,语法是class ChildClass(ParentClass1, ParentClass2):
    • 注意:菱形问题,即当一个类从2个或者更多父类继承,而这些父类又共同继承自同一个祖先类时,方法解析顺序会变得复杂。Python通过MRO(Method Resolution Order)来解决这个问题
  • super()函数:用于调用父类(或更广义地说,MRO中的下一个类)的方法。最常见的用法是在子类的 __init__ 方法中调用父类的 __init__ 方法,以确保父类的属性得到正确初始化。
    • 语法:super().__init__(args) super().method_name(args)
    • super() 并不是简单地调用“父类”的方法,它根据当前类的MRO来确定下一个要调用的类。这使得它在多重继承中非常有用,能够确保所有父类的初始化方法都被调用,并且方法调用遵循正确的顺序。

isinstance() 和 issubclass():

● isinstance(obj, Class):检查 obj 是否是 Class 的实例,或者 Class 的子类的实例。

● issubclass(SubClass, SuperClass):检查 SubClass 是否是 SuperClass 的子类(包括自身)。

属性和方法的查找: 当你通过一个实例调用方法或访问属性时(例如 dog_instance.bark()),Python会首先在实例自身的 __dict__ 中查找。如果找不到,它会沿着该实例所属类的MRO(Method Resolution Order)链向上查找。MRO 定义了类及其所有父类(包括多重继承中的所有基类)的搜索顺序。

MRO (Method Resolution Order) 的原理:

○ MRO 是一个线性化的列表,它决定了Python在查找方法或属性时,应该按照什么顺序遍历一个类的继承层次结构。

○ 对于单继承,MRO很简单,就是当前类 -> 父类 -> 祖父类 -> ... -> object

○ 对于多重继承,Python 3 及其以后的版本使用 C3 线性化算法来计算MRO。C3算法保证了以下几点:

  • 子类优先于父类。
  • 多个父类之间的顺序保留(按照在类定义中出现的顺序)。
  • 单调性(如果一个类在MRO中出现在另一个类之前,那么在所有继承自它们的子类的MRO中,这个顺序也会保持)。

○ 你可以通过 ClassName.__mro__ 属性或 help(ClassName) 来查看一个类的MRO。

super() 的原理: super() 函数返回一个代理对象(proxy object),它能够根据当前类的MRO来查找并调用下一个类的方法。它不是简单地调用父类的方法,而是调用MRO中当前类之后(下一个)的类的方法。这在多重继承中尤为重要,因为它确保了所有共同祖先的方法都能被正确调用,避免了重复调用和遗漏。

(1)单继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  12:37


class Animal:
    """
    父类:动物
    """
    def __init__(self, name):
        self.name = name
        print(f"动物:{self.name}实例创建好了")

    def speak(self):
        """
        动物发声通用的方法
        """
        print(f"{self.name}发出声音...")

class Dog(Animal):
    def __init__(self, name, breed):
        # 调用父类的构造函数,初始化父类的属性
        super().__init__(name)
        self.breed = breed
        print(f"Dog{self.name}({self.breed})实例创建好了")

    def bark(self):
        """
        狗的特有的行为
        """
        print(f"{self.name}bark 很大声...")

# 创建子类的实例
my_dog = Dog("Buddy", "Golden Retriever")
my_dog.speak()  # 调用继承自Animal的方法
my_dog.bark()  # 调用Dog自己的方法

print(f"my_dog是Animal的实例吗?{isinstance(my_dog, Animal)}")
print(f"Dog是Animal的子类吗?{issubclass(Dog, Animal)}")


image-20250708124518785

(2)多重继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  12:49


class Flyer:
    """
    父类:会飞的生物
    """
    def fly(self):
        print("我会飞...")

class Swimmer:
    """
    父类:会游泳的生物
    """
    def swim(self):
        print("我会游泳...")

class Duck(Flyer, Swimmer):
    """
    子类:鸭子,同时继承Flyer和Swimmer
    """
    def __init__(self, name):
        self.name = name
        print(f"鸭子:{self.name}实例创建好了...")

    def quack(self):
        """
        鸭子叫的方法
        """
        print(f"{self.name}嘎嘎叫")

my_duck = Duck("Donald")
my_duck.quack()
my_duck.fly()  # 继承自Flyer的方法
my_duck.swim()  # 继承自Swimmer的方法
print(f"Duck的MRO:{Duck.__mro__}")

image-20250708125518619

(3)菱形问题(MRO)

下面例子中,确保A的m1方法只被调用一次,避免了菱形问题的中的重复调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  12:55


class A:
    def m1(self):
        print("类A的m1方法被调用...")

class B(A):
    def m1(self):
        print("类B的m1方法被调用...")
        super().m1()  # 调用A的m1方法

class C(A):
    def m1(self):
        print("类C的m1方法被调用...")
        super().m1()  # 调用A的m1方法

class D(B, C):
    def m1(self):
        print("类D的m1方法被调用...")
        super().m1()  # 调用A的m1方法

d = D()
d.m1()

print(D.__mro__)

image-20250708132119321

3、方法重写

方法重写(Method Overriding)是指子类定义了一个与父类中同名的方法,以提供自己特有的实现。当通过子类实例调用该方法时,会执行子类中的版本,而不是父类中的版本。

方法重写是继承机制的重要组成部分,它允许子类修改或扩展父类的行为,以适应更具体的场景。

目的:

特殊化行为: 子类需要对父类提供的通用行为进行定制。

扩展功能: 在保留父类原有功能的基础上,添加额外的逻辑。

实现抽象方法: 如果父类定义了抽象方法(通过 abc 模块),子类必须重写这些方法才能被实例化。

调用父类被重写的方法:

○ 在子类重写的方法中,你可能仍然需要调用父类中被重写的方法,以保留其原有功能,并在其基础上进行扩展。

○ 这通常通过 super().method_name(args) 来实现。

○ 也可以直接通过 ParentClassName.method_name(self, args) 来调用,但 super() 更推荐,因为它在多重继承中能正确处理MRO。

属性查找机制 (MRO) 的作用: 当你通过一个子类实例调用一个方法时(例如 child_obj.some_method()),Python会按照该子类的MRO(Method Resolution Order)来查找 some_method

  • 首先,它会在子类自身的 __dict__ 中查找 some_method
  • 如果子类定义了 some_method(即重写了它),那么就会找到并执行子类中的这个方法。
  • 如果子类没有定义,它会沿着MRO向上到父类、祖父类等,直到找到第一个定义了 some_method 的类,然后执行那个方法。

动态分派 (Dynamic Dispatch): 这就是多态的基础。Python在运行时才决定调用哪个具体的方法实现。它不会在编译时就确定,而是根据实际调用方法的对象的类型来查找并执行对应的方法。这种机制使得代码更加灵活,因为你可以用统一的接口(方法名)来操作不同类型的对象,而每个对象会根据自己的类型执行不同的行为。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  13:54

class Animal:
    def __init__(self, name):
        self.name = name
        print(f"动物{self.name}实例被创建")

    def speak(self):
        """
        动物发声的通用方法
        """
        print(f"{self.name} 发出声音...")

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed
        print(f"狗{self.name}-{self.breed}实例被创建了...")

    # 重写父类的speak方法
    def speak(self):
        print(f"{self.name}-{self.breed}汪汪叫...")

class Cat(Animal):
    def __init__(self,name):
        super().__init__(name)

    # 重写父类的speak方法,并且调用父类的方法
    def speak(self):
        super().speak()
        print(f"{self.name}喵喵叫")

class Bird(Animal):
    """
    不重写speak方法
    """
    def __init__(self, name):
        super().__init__(name)

animal = Animal("通用动物")
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers")
bird = Bird("Tweety")

animal.speak()
dog.speak()
cat.speak()
bird.speak()

animals = [Animal("Zoo 动物"), Dog("Max", "German Shepherd"), Cat("Mittens"), Bird("Parrot")]
for a in animals:
    a.speak()

image-20250708140340244

4、object类

object 类是Python中所有类的最终基类。这意味着所有在Python中定义的类(无论是显式继承还是隐式继承)都直接或间接地继承自 object。它提供了所有Python对象共有的基本行为和特殊方法。

在Python 3中,所有的类都是“新式类”(new-style classes),它们都隐式地继承自 object。即使你定义一个简单的类 class MyClass: pass,它也等同于 class MyClass(object): pass

object 类定义了许多特殊的“魔术方法”(或称“双下划线方法”、“dunder methods”),这些方法为Python的内置操作提供了默认实现。当你定义一个类并重写这些方法时,你实际上是在定制这些内置操作的行为。

object 类提供的一些重要特殊方法:

__init__(self, ...):对象的构造器(构造函数)。

__new__(cls, ...):对象的创建器(在 __init__ 之前调用)。

__str__(self):返回对象的“非正式”字符串表示,供 str() 函数和 print() 函数使用。通常用于用户友好的输出。

__repr__(self):返回对象的“正式”字符串表示,供 repr() 函数和调试器使用。通常应返回一个字符串,该字符串在可能的情况下,可以用来重新创建对象。

__eq__(self, other):定义相等性比较(== 运算符)。

__ne__(self, other):定义不相等性比较(!= 运算符)。

__lt__(self, other):定义小于比较(< 运算符)。

__le__(self, other):定义小于等于比较(<= 运算符)。

__gt__(self, other):定义大于比较(> 运算符)。

__ge__(self, other):定义大于等于比较(>= 运算符)。

__hash__(self):定义对象的哈希值,用于在哈希表(如字典的键、集合的元素)中存储对象。如果一个类重写了 __eq__,通常也应该重写 __hash__。可变对象默认不可哈希。

__delattr__(self, name):定义删除属性时的行为(del obj.attr)。

__getattr__(self, name):定义当访问不存在的属性时,如何处理。

__setattr__(self, name, value):定义设置属性时的行为(obj.attr = value)。

__dir__(self):定义 dir() 函数的行为。

__doc__:类的文档字符串。

__class__:指向对象所属的类。

__dict__:存储实例属性的字典。

统一对象模型: object 类作为所有类的根,确保了Python中所有对象都具有一套共同的基本行为。这使得Python能够实现统一的内存管理、垃圾回收、属性查找机制等。无论你处理的是整数、字符串、列表还是自定义类的实例,它们本质上都是 object 的子类,因此可以以统一的方式进行操作。

MRO 的终点: 任何类的MRO链最终都会以 object 结束。当Python查找一个方法或属性时,如果一直向上追溯到 object 仍然没有找到,才会抛出 AttributeError。

默认行为的提供: object 类提供了许多特殊方法的默认实现。例如,__str__ 的默认实现通常会返回一个包含类名和内存地址的字符串。当你重写这些方法时,你是在定制这些默认行为。

新式类与旧式类 (Python 2 历史背景): 在Python 2 中,存在“旧式类”(不继承自 object)和“新式类”(继承自 object)。旧式类在多重继承和方法解析方面存在一些问题。Python 3 统一了所有类都是新式类,从而简化了继承模型,并强制使用C3 MRO算法。这个统一是Python设计哲学中“一切皆对象”原则的体现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  14:29


# 显式继承object
class MyCustomObject(object):
    """
    一个自定义对象,演示继承自object的默认行为和重写
    """
    def __init__(self, value):
        self.value = value

    # 重写__str__方法,提供用户友好的字符串表示
    def __str__(self):
        return f"MyCustomObject value是:{self.value}"

    # 重写__repr__方法,提供一个官方的、可用于重建的字符串表示
    def __repr__(self):
        # !r表示调用repr()转换value
        return f"MyCustomObject value是:{self.value!r}"

    def __eq__(self, other):
        if isinstance(other, MyCustomObject):
            return self.value == other.value
        return NotImplemented  # 表示不处理与其他类型的比较

    # 如果重写了__eq__且对象可哈希,则应该重写__hash__
    def __hash__(self):
        return hash(self.value) # 使用value的哈希值作为对象的哈希值

    def show_internal_info(self):
        print(f"实例属性字典:{self.__dict__}")
        print(f"所属的类:{self.__class__}")
        print(f"类的基类:{self.__class__.__bases__}")
        print(f"类的MRO:{self.__class__.__mro__}")



o1 = MyCustomObject(10)
print(f"print(o1):{o1}")  # 调用__str__
print(f"repr(o1):{repr(o1)}")  # 调用__repr__
# 默认情况下,如果只定义了__repr__,没有定义__str__, 那么print会调用__repr__


# 调用__eq__
o2 = MyCustomObject(20)
o3 = MyCustomObject(10)
print(o1 == o2)
print(o1 == o3)

# 字典中,__hash__
set1 = {o1, o2, o3}
print(len(set1))
dict1 = {o1: "v1", o2: "v2"}
print(dict1[o3])

o1.show_internal_info()

print(issubclass(MyCustomObject, object))

image-20250708144932807

5、多态

多态是面向对象编程的第三个核心特性。它允许不同类的对象对同一消息(方法调用)作出不同的响应。在Python中,多态主要通过“鸭子类型”(Duck Typing)和抽象基类(Abstract Base Classes, ABCs)来实现。多态的核心在于“一个接口,多种实现”。它使得代码更加灵活、可扩展和易于维护。

(1)鸭子类型

Python中多态的主要实现方式。

思想: “如果它走起来像鸭子,叫起来像鸭子,那么它就是一只鸭子。”这意味着,只要一个对象拥有所需的方法(或属性),就可以被当作特定类型来处理,而无需显式声明它继承自某个共同的基类或实现了某个接口。

优点: 极大地提高了代码的灵活性和解耦性。你不需要关心对象的具体类型,只需要关心它是否提供了你所需的方法。

缺点: 缺乏编译时类型检查,错误可能在运行时才暴露。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  15:26


class Duck:
    def quack(self):
        print("Quack!!!")

    def fly(self):
        print("鸭子在飞...")

class Plane:
    def quack(self): # 尽管是飞机,但也有quack方法
        print("飞机也可以quack(像鸭子?)...")

    def fly(self):
        print("飞机在飞...")

class Person:
    def quack(self):
        print("人模仿鸭子quack...")

    def fly(self):
        print("人尝试飞,但是掉下来了...")

def make_it_quack_and_fly(entity):
    """
    一个可以接受任何拥有quack和fly的对象的函数
    """
    print("一个可以接受任何拥有quack和fly的对象的函数make_it_quack_and_fly被调用")
    entity.quack()
    entity.fly()

duck = Duck()
plane = Plane()
person = Person()

make_it_quack_and_fly(duck)
make_it_quack_and_fly(plane)
make_it_quack_and_fly(person)

image-20250708153257998

(2)抽象基类 (Abstract Base Classes, ABCs)

○ Python提供了 abc 模块来支持抽象基类。ABCs允许你定义一个接口(即一组必须由子类实现的方法),但不能直接实例化。

用途:

强制实现: 确保子类实现了特定的方法,否则无法实例化。

形式化接口: 为鸭子类型提供一个更明确的契约,提高代码可读性和可维护性。

类型检查: 可以使用 isinstance() issubclass() 对实现了ABC的类进行类型检查。

定义:

■ 继承 abc.ABC

■ 使用 @abc.abstractmethod 装饰器标记抽象方法。

动态分派 (Dynamic Dispatch): 这是多态的底层机制。当调用一个对象的方法时,Python解释器在运行时根据对象的实际类型来查找并执行对应的方法。它不是在编译时就确定调用哪个方法,而是等到运行时才进行“分派”。

○ 例如,animal.speak():如果 animal 是 Dog 实例,就调用 Dog 类的 speak 方法;如果是 Cat 实例,就调用 Cat 类的 speak 方法。这种运行时查找和执行的机制就是动态分派。

Python的类型系统: Python是一种动态类型语言,变量没有固定的类型,而是引用对象的类型。这种灵活性是鸭子类型的基础。解释器在执行方法调用时,只关心对象是否具有该方法,而不关心对象的声明类型。

ABCs 的原理:

○ 当一个类继承自 abc.ABC 并包含 @abstractmethod 装饰的方法时,该类就不能被直接实例化。

○ 当一个子类继承这个ABC时,如果它没有实现所有抽象方法,Python会在尝试实例化该子类时抛出 TypeError。

○ ABCs通过在类创建时修改类的元类(metaclass,通常是 abc.ABCMeta)来实现这种行为。元类控制着类的创建过程,从而能够强制执行抽象方法的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  15:33

import abc
class Shape(abc.ABC): # 继承自abc.ABC,使其成为抽象类
    """
    抽象基类:图形
    定义了所有图形应该有的通用的接口
    """
    def __init__(self, name):
        self.name = name

    @abc.abstractmethod # 抽象方法,子类必须实现
    def area(self):
        """
        计算图形面积的抽象方法
        """
        pass  # 抽象方法没有实现体

    @abc.abstractmethod
    def perimeter(self):
        """
        计算图形周长的抽象方法
        """
        pass

    def describe(self): # 普通方法,子类可以继承,也可以重写
        print(f"这是一个{self.name}图形...")

# generic_shape = Shape("Generic")  # TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter

class Rectangle(Shape):
    """
    具体子类:矩形,实现Shape的抽象方法
    """
    def __init__(self, width, height):
        super().__init__("Rectangle")
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.height + self.width)

class Circle(Shape):
    """
    具体子类:圆形,实现Shape的抽象方法
    """
    PI = 3.14159
    def __init__(self, radius):
        super().__init__("Circle")
        self.radius = radius

    def area(self):
        return Circle.PI * self.radius * self.radius

    def perimeter(self):
        return 2 * Circle.PI * self.radius

# 测试
r1 = Rectangle(3, 5)
c1 = Circle(2)

# 使用多态的方式处理不同的形状
shapes = [r1, c1]
for item in shapes:
    item.describe()
    print(f"面积:{item.area()}")
    print(f"周长:{item.perimeter()}")
    print(f"是Shape的实例吗?{isinstance(item, Shape)}")

class IncompleteShape(Shape):
    """
    未实现抽象方法的子类
    """
    def __init__(self):
        super().__init__("Incomplete")

try:
    o1 = IncompleteShape()
except TypeError as e:
    print(f"报错:{e}")

image-20250708154539683

6、特殊方法和特殊属性

特殊方法(Special Methods),也常被称为“魔术方法”(Magic Methods)或“双下划线方法”(Dunder Methods),是Python中以双下划线开头和结尾的方法(例如 __init__, __str__)。它们允许你定义类的行为,以便与Python的内置函数、运算符和语言特性(如上下文管理器、迭代器)进行交互。特殊属性是类或实例内置的、以双下划线开头和结尾的属性。

特殊方法是Python实现其“数据模型”(Data Model)的核心。通过重写这些特殊方法,你可以让自定义类的对象行为得像内置类型一样,例如支持加法、索引、迭代、上下文管理等。这被称为运算符重载 (Operator Overloading)

常见且重要的特殊方法:

  • 构造与初始化:

__new__(cls, ...):对象的创建方法,在 __init__ 之前调用,负责创建并返回一个实例对象。

__init__(self, ...):对象的初始化方法,用于设置新创建实例的属性。

__del__(self):析构方法,当对象被垃圾回收时调用(不保证何时调用)。

  • 字符串表示:

__str__(self):返回对象的“非正式”字符串表示,供 print() 和 str() 使用。

__repr__(self):返回对象的“正式”字符串表示,供 repr() 和调试器使用,通常应能用于重建对象。

  • 比较操作:

__eq__(self, other)== 运算符。

__ne__(self, other)!= 运算符。

__lt__, __le__, __gt__, __ge__:<, <=, >, >= 运算符。

  • 数值运算:

__add__(self, other)+ 运算符。

__sub__(self, other)- 运算符。

__mul__(self, other)* 运算符。

__truediv__(self, other)/ 运算符。

__floordiv__(self, other)// 运算符。

__mod__(self, other)% 运算符。

__pow__(self, other)** 运算符。

○ 还有反射版本(如 __radd__)、原地操作(如 __iadd__)等。

  • 容器类型操作:

__len__(self)len() 函数。

__getitem__(self, key):索引访问(obj[key])和切片访问。

__setitem__(self, key, value):索引赋值(obj[key] = value)。

__delitem__(self, key):删除元素(del obj[key])。

__contains__(self, item)in 运算符。

  • 可调用对象:

__call__(self, *args, **kwargs):使实例对象可以像函数一样被调用(obj(...))。

  • 迭代器:

__iter__(self):返回一个迭代器对象,用于 for ... in ... 循环。

__next__(self):迭代器协议的一部分,返回下一个元素。

  • 上下文管理器:

__enter__(self):进入 with 语句块时调用。

__exit__(self, exc_type, exc_val, exc_tb):退出 with 语句块时调用,无论是否发生异常。

特殊属性:

__dict__:存储实例或类的命名空间的字典。

__class__:指向对象所属的类。

__name__:模块或类的名称。

__module__:对象所属的模块名称。

__doc__:对象的文档字符串。

__bases__:类的直接基类元组。

__mro__:类的MRO元组。

Python数据模型: Python的解释器在执行内置操作(如 + 运算符、len() 函数、for 循环等)时,会查找对象类型中对应的特殊方法。例如,当执行 a + b 时,Python会尝试调用 a.__add__(b)。如果 a 没有定义 __add__,它会尝试调用 b.__radd__(a)(反射加法)

协议 (Protocols): 特殊方法实现了Python的各种“协议”。例如,__iter__ __next__ 实现了迭代器协议,__enter__ __exit__ 实现了上下文管理器协议。当一个对象实现了某个协议所需的所有特殊方法时,它就可以被当作符合该协议的类型来使用。

动态绑定: 与普通方法一样,特殊方法的调用也是动态绑定的。解释器在运行时根据对象的实际类型来查找并调用正确的特殊方法。

属性查找: 特殊属性通常是解释器在创建类或实例时自动设置的元数据,或者通过特定的机制(如 __dict__)提供对内部状态的访问。


(1)字符串表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  17:28


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        """
        用户友好的字符串表示
        """
        return f"(x={self.x}, y={self.y})"

    def __repr__(self):
        """
        官方的、可以用于重建的字符串表示(重新创建对象)
        """
        return f"Point(x={self.x}, y={self.y})"

p = Point(1, 2)
print(p)

p_repr = repr(p)
p_ = eval(p_repr)
print(p_)


image-20250708174933023

(2)比较操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  19:35

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        if isinstance(other, MyNumber):
            return self.value == other.value
        return NotImplemented  # 允许与其他类型比较的时候,如果无法处理就返回NotImplemented

    def __lt__(self, other):
        if isinstance(other, MyNumber):
            return self.value < other.value
        return NotImplemented

    def __str__(self):
        print(f"MyNumber({self.value})")

n1 = MyNumber(10)
n2 = MyNumber(20)
n3 = MyNumber(10)
print(f"n1 == n3:{n1 == n3}")
print(f"n1 < n2?{n1 < n2}")
print(f"n1 == 10?{n1 == 10}")

image-20250708194240144

(3)数值运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  19:45


class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        return NotImplemented

    def __mul__(self, scalar):
        """
        标量和向量的乘法
        """
        if isinstance(scalar, (int, float)):
            return  Vector(scalar * self.x, scalar * self.y)
        return  NotImplemented

    def __str__(self):
        return f"Vector(x={self.x}, y={self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)

v_sum = v1 + v2
print(f"{v1} + {v2}= {v_sum}")

v_times_5 = v1 * 5
print(f"{v1}*5={v_times_5}")

image-20250708195009006

(4)容器类型操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  19:50


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 __contains__(self, item):
        return item in self._data

    def __str__(self):
        return f"MyList({self._data})"

ml = MyList(10, 20, 20, 30, 40)
print(ml)
print(f"长度:{len(ml)}")
print(ml[2])
print(ml[1:4])
ml[0] = 100
print(ml)

del ml[1]
print(ml)

print(99 in ml)

image-20250708195516716

(5)可调用对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  23:45


class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, value):
        """
        使得实例可像函数一样被调用
        """
        return self.factor * value

double = Multiplier(2)
triple = Multiplier(3)
print(double(5))
print(double.__call__(5))  # 与double(5)等价
print(triple(10))
print(triple.__call__(10))  # 与triple(10)等价

image-20250708234835288

(6)上下文管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  23:49

class MyFileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        """
        进入with语句块时会调用
        """
        print(f"打开文件:{self.filename}")
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        退出with语句块时调用,无论是否发生异常
        """
        if self.file:
            self.file.close()
            print(f"关闭文件:{self.filename}")
        if exc_type:
            print(f"发生异常:{exc_type.__name__}:{exc_val}")
        return False  # 返回False表示不处理异常,让其继续传播

with MyFileManager("test_file.txt", "w") as f:
    f.write("Hello from context manager...\n")
    f.write("this is line 2...")
print("文件写入完成")

try:
    with MyFileManager("test_file_error.txt", "w") as f:
        f.write("This will cause an error...\n")
        f.write("this is line 2...")
        raise ValueError("出问题了...")
except ValueError as e:
    print(f"报错:{e}")

image-20250708235712150

(7)特殊属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 博客地址: https://kirsten-1.github.io/
# 创建者: kirsten-1
# 时间: 2025/7/8  23:57

class ExampleClass:
    def __init__(self, name):
        self.name = name

    def my_method(self):
        pass


obj = ExampleClass("TestObj")
print(f"对象的实例属性:{obj.__dict__}")
print(f"对象所属的类:{obj.__class__}")
print(f"类的名称:{ExampleClass.__name__}")
print(f"类所在的模块:{ExampleClass.__module__}")
print(f"类的文档字符串:{ExampleClass.__doc__}")
print(f"类的直接基类:{ExampleClass.__bases__}")
print(f"类的MRO:{ExampleClass.__mro__}")

image-20250709000103726