Python(十二):第十一章:面向对象编程

第十一章:面向对象编程

对象的本质

对象本质上是一个 “容器”,用来存放数据和功能的集合体。面向对象编程的核心思想是将数据和操作数据的方法封装在一起,形成独立的实体。

为什么需要对象

以开发 MOBA 游戏为例,每个英雄都有自己的属性(如攻击力、移动速度)和功能(如技能)。如果不使用对象,代码会变得非常冗余:

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
## 不使用对象的方式
hero_work = 'soldier'
hero_name = '盖伦'
hero_atk = 165
hero_speed = 475

def hero_info():
print(f'我的名字是{hero_name},职业是{hero_work},攻击力是{hero_atk},移动速度是{hero_speed}')

def hero_atk_up(atk):
global hero_atk
hero_atk += atk

## 第二个英雄
hero_work2 = 'ADC'
hero_name2 = '后裔'
hero_atk2 = 150
hero_speed2 = 450

def hero_info2():
print(f'我的名字是{hero_name2},职业是{hero_work2},攻击力是{hero_atk2},移动速度是{hero_speed2}')

def hero_atk_up2(atk):
global hero_atk2
hero_atk2 += atk

使用字典可以改进一些,如下、但这样还是有一些瑕疵,因为函数是暴露在外面的,最理想的状态是函数也存在在 hero_obj 这个对象里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def hero_info(hero_obj):
print(f'我的名字是{hero_obj["name"]},职业是{hero_obj["work"]},攻击力是{hero_obj["atk"]}'
f',移动速度是{hero_obj["speed"]}')

def hero_atk_up(hero_obj, atk):
hero_obj["atk"] += atk

hero_obj = {
'name': '盖伦',
'work': 'soldier',
'atk': 165,
'speed': 475,
'info': hero_info,
'atk_up': hero_atk_up
}

在对象中,还有类的这么一个概念,比如说又新创建了一个“艾希”英雄,他与后裔的差别无非就是 ATK 与 Speed 是不一致的,但他们都同属于 ADC 这个职业,所以他们就可以存在在一个 ADC 类里面,在面向对象编程里面,是先创建一个类,再将对象创建出来,所以类也算是一种容器,他也可以被称作是对象

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
class Hero:
work = 'ADC'
def hero_info(hero_obj):
print(f'我的名字是{hero_obj["name"]},职业是{hero_obj["work"]},攻击力是{hero_obj["atk"]}'
f',移动速度是{hero_obj["speed"]}')

def hero_atk_up(hero_obj,atk):
hero_obj["atk"] += atk
print('xxx') # 在程序运行时,类的子代码会被运行,也就代表类在定义阶段就已经创建好了名称空间
# 在python里面类的名称空间中,会默认创建一个__dict__的字典属性
print('='*50)
print(Hero.__dict__)
print('='*50)
# {'__module__': '__main__', 'work': 'ADC', 'hero_info': <function Hero.hero_info at 0x00000226DA7B8C10>, 'hero_atk_up': <function Hero.hero_atk_up at 0x00000226DA7B9820>, '__dict__': <attribute '__dict__' of 'Hero' objects>, '__weakref__': <attribute '__weakref__' of 'Hero' objects>, '__doc__': None}
# 所以想要拿到类的属性,实际上访问的是类名称空间中的__dict__字典属性
print(Hero.__dict__['work']) # ADC
print('='*50)

# python提供了一个更简便的语法,也就是通过类名.属性名来访问的
print(Hero.work) # ADC
print('='*50)
# 每次调用一个类就会产生一个对象,此时这个对象里面的Dict是空的,需要添加值
hero_obj = Hero()
hero_obj2 = Hero()
hero_obj3 = Hero()
print(hero_obj.__dict__) # {}
print('='*50)
print(hero_obj2.__dict__) # {}
print('='*50)
print(hero_obj3.__dict__) # {}
print('='*50)
# 给对象添加属性
hero_obj.__dict__['name'] = '亚瑟'
hero_obj.__dict__['work'] = 'tank'
hero_obj.__dict__['atk'] = 100
hero_obj.__dict__['speed'] = 300
# 可是通过这种方式过于复杂,python也提供了简便的语法
hero_obj2.name = '妲己'
hero_obj2.work = 'mage'
hero_obj2.atk = 120
hero_obj2.speed = 350

hero_obj3.name = '后裔'
hero_obj3.work = 'ADC'
hero_obj3.atk = 150
hero_obj3.speed = 450


print(hero_obj.__dict__) # {'name': '亚瑟', 'work': 'tank', 'atk': 100, 'speed': 300}
print('='*50)
print(hero_obj2.__dict__) # {'name': '妲己', 'work': 'mage', 'atk': 120, 'speed': 350}
print('='*50)
print(hero_obj3.__dict__) # {'name': '后裔', 'work': 'ADC', 'atk': 150, 'speed': 450}
print('='*50)

__init__ 方法

为了减少代码冗余,我们可以定义一个初始化方法,在创建对象时自动设置属性:

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
class Hero:
work = 'ADC'
def hero_info(hero_obj):
print(f'我的名字是{hero_obj["name"]},职业是{hero_obj["work"]},攻击力是{hero_obj["atk"]}'
f',移动速度是{hero_obj["speed"]}')

def hero_atk_up(hero_obj,atk):
hero_obj["atk"] += atk
print('xxx') # 在程序运行时,类的子代码会被运行,也就代表类在定义阶段就已经创建好了名称空间

hero1_obj = Hero()
hero2_obj = Hero()
hero3_obj = Hero()
def init(hero_obj,name,work,atk,speed):
hero_obj.name = name
hero_obj.work = work
hero_obj.atk = atk
hero_obj.speed = speed

init(hero1_obj,'后裔','ADC',150,450)
init(hero2_obj,'程咬金','ADC',150,450)
init(hero3_obj,'妲己','ADC',150,450)

print(hero1_obj.__dict__) # {'name': '后裔', 'work': 'ADC', 'atk': 150, 'speed': 450}
print(hero2_obj.__dict__) # {'name': '程咬金', 'work': 'ADC', 'atk': 150, 'speed': 450}
print(hero3_obj.__dict__) # {'name': '妲己', 'work': 'ADC', 'atk': 150, 'speed': 450}

这还不够完美,面向对象的核心思想就是整合两个字,现在定义的这个函数和类是完全独立开的,那有没有什么办法能把这个 init 函数,和前面定义的类进一步整合到一起呢,最好是产生对象的时候,就自动调用 init 这个功能,完成对象属性的初始化操作,这时候拿到的对象就是有自己独有属性的对象

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
class Hero:
work = 'ADC'

def __init__(hero_obj, name, work, atk, speed):
hero_obj.name = name
hero_obj.work = work
hero_obj.atk = atk
hero_obj.speed = speed

def hero_info(hero_obj):
print(f'我的名字是{hero_obj.__dict__["name"]},职业是{hero_obj.__dict__["work"]},攻击力是{hero_obj.__dict__["atk"]}'
f',移动速度是{hero_obj.__dict__["speed"]}')

def hero_atk_up(hero_obj,atk):
hero_obj.__dict__["atk"] += atk
print('xxx') # 在程序运行时,类的子代码会被运行,也就代表类在定义阶段就已经创建好了名称空间


# 在我们调用类自动创建对象的时候,python内部会自动调用类下面的__init__方法并传入一个空对象,所以在实际传值时仅需要传入四个参数即可
hero1_obj = Hero('后裔','ADC',150,450) # Hero.__init__(空对象,name,work,atk,speed)
hero2_obj = Hero('程咬金','ADC',150,450)
hero3_obj = Hero('妲己','ADC',150,450)

print(hero1_obj.__dict__) # {'name': '后裔', 'work': 'ADC', 'atk': 150, 'speed': 450}
print(hero2_obj.__dict__) # {'name': '程咬金', 'work': 'ADC', 'atk': 150, 'speed': 450}
print(hero3_obj.__dict__) # {'name': '妲己', 'work': 'ADC', 'atk': 150, 'speed': 450}

hero1_obj.hero_info() # 我的名字是后裔,职业是ADC,攻击力是150,移动速度是450
hero2_obj.hero_info() # 我的名字是程咬金,职业是ADC,攻击力是150,移动速度是450
hero3_obj.hero_info() # 我的名字是妲己,职业是ADC,攻击力是150,移动速度是450

进一步修改代码,通过 dict [] 拿属性过于繁杂,所以也可以用到类名.属性名的方式来传值给函数,并且在代码最后,在打印函数的内存地址的时候会发现打印类里的函数是一个正常的函数,而打印对象的函数方法则是一个绑定方法,也就是说类的函数属性,绑定给对象使用了之后,就不再是一个普通函数

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
class Hero:
work = 'ADC'

def __init__(hero_obj, name, work, atk, speed):
hero_obj.name = name
hero_obj.work = work
hero_obj.atk = atk
hero_obj.speed = speed

def hero_info(hero_obj):
print(f'我的名字是{hero_obj.name},职业是{hero_obj.work},攻击力是{hero_obj.atk}'
f',移动速度是{hero_obj.speed}')

def hero_atk_up(hero_obj,atk):
hero_obj.atk += atk
print('xxx') # 在程序运行时,类的子代码会被运行,也就代表类在定义阶段就已经创建好了名称空间


# 在我们调用类自动创建对象的时候,python内部会自动调用类下面的__init__方法并传入一个空对象,所以在实际传值时仅需要传入四个参数即可
hero1_obj = Hero('后裔','ADC',150,450) # Hero.__init__(空对象,name,work,atk,speed)
hero2_obj = Hero('程咬金','ADC',150,450)
hero3_obj = Hero('妲己','ADC',150,450)



# 绑定方法
print(Hero.hero_info) # <function Hero.hero_info at 0x0000019021E39820>
print(hero1_obj.hero_info)
# <bound method Hero.hero_info of <__main__.Hero object at 0x0000019021F13BE0>>
print(hero2_obj.hero_info)
# <bound method Hero.hero_info of <__main__.Hero object at 0x0000019021F13DC0>>
print(hero3_obj.hero_info)
# <bound method Hero.hero_info of <__main__.Hero object at 0x0000019021F4F940>>

hero1_obj.hero_atk_up(50000)
hero1_obj.hero_info()
hero2_obj.hero_info()
hero3_obj.hero_info()

# 三个内存地址不一样并不代表代码就存了三份,代码本身只存了一份,因为类的作用就是为了减少计算机资源的浪费
# 可以理解为类的属性绑定给对象之后,这个函数的内存地址,就会被包装成一个绑定方法,或者也可以理解为
# 是一个装饰器,当通过对象访问这些函数的时候,访问的也是这个绑定方法的内存地址,而他在给函数传入值时
# 也会像init方法一样自动传入一个对象进去

所以就能明白,在我们实例化对象传入参数时,他默认的第一个参数是一个对象,这个对象就是 self 对象,他仅仅只是一个变量名,在代码的规范角度来讲,类里面的函数的第一个对象就是 self,仅此而已

通过这种方式,只需要在 init 方法下增添需要的绑定方法,添加对应的函数功能,就能够实现谁调用这个对象的函数方法,就给谁增添对应的数据

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
class Hero:
work = 'ADC'

def __init__(self, name, work, atk, speed):
self.name = name
self.work = work
self.atk = atk
self.speed = speed
self.equipment = []

def hero_info(self):
print(f'我的名字是{self.name},职业是{self.work},攻击力是{self.atk}'
f',移动速度是{self.speed}')

def hero_atk_up(self,atk):
self.atk += atk


def buy_equipment(self,equipment):
self.equipment.append(equipment)
print(f'{self.name}购买了{equipment}')


hero1_obj = Hero('后裔','ADC',150,450)
hero2_obj = Hero('程咬金','ADC',150,450)
hero3_obj = Hero('妲己','ADC',150,450)


hero1_obj.buy_equipment('屠龙宝刀966666')
hero2_obj.buy_equipment('屠龙宝刀966666')
print(hero3_obj.equipment) # []

面向对象的三大特性

1. 封装

封装是将数据和方法包装在对象内部,并向外界提供必要的接口,同时隐藏实现细节的过程。

属性隐藏

Python 使用名称修饰(name mangling)实现属性隐藏,即在属性名前加上双下划线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Hero:
def __init__(self, name, work, atk, speed):
self.name = name
self.work = work
self.__atk = atk # 私有属性
self.speed = speed

def __buy_equipment(self, equipment): # 私有方法
print(f'{self.name}购买了{equipment}')

hero = Hero('后裔', 'ADC', 150, 450)
## print(hero.__atk) # AttributeError: 'Hero' object has no attribute '__atk'
## hero.__buy_equipment('无尽战刃') # AttributeError

## 但这只是名称修饰,仍然可以通过修饰后的名称访问
print(hero._Hero__atk) # 150
hero._Hero__buy_equipment('无尽战刃') # 后裔购买了无尽战刃
属性访问控制

为了更好地控制属性访问,可以使用 getter 和 setter 方法:

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
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age

def get_name(self):
return self.__name

def get_age(self):
return self.__age

def set_age(self, age):
if not isinstance(age, int):
print('年龄必须为整数')
return
self.__age = age
# 相当于Java的toString()方法
def __str__(self):
return 'Person(name={}, age={})'.format(self.__name, self.__age)

p = Person('Alice', 25)
print(p.get_name()) # Alice
print(p.get_age()) # 25
p.set_age(30) # 设置年龄为 30
print(p.get_age()) # 30
使用@property 装饰器

Python 的 @property 装饰器提供了更优雅的属性访问方式:

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
# 这段代码展示了Python中的属性装饰器(@property)的使用,它有以下意义:
# 1. 封装性:通过私有变量(__age)和属性装饰器,实现了对数据的封装和保护
# 2. 数据验证:在setter方法中可以对输入数据进行验证,确保数据的有效性
# 3. 接口一致性:虽然内部使用了方法,但对外提供了类似直接访问属性的简洁接口
# 4. 控制访问:可以分别控制属性的读取、修改和删除行为

class Person:
def __init__(self, age):
self.__age = age

@property
def age(self):
"""获取年龄"""
return self.__age

@age.setter
def age(self, age):
"""设置年龄"""
if not isinstance(age, int):
print('年龄必须为整数')
return
self.__age = age

@age.deleter
def age(self):
"""删除年龄属性"""
del self.__age


person = Person(25)
print(person.age) # 使用getter
person.age = 30 # 使用setter
del person.age # 使用deleter

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
class Animal:
def eat(self):
print("吃")

class Dog(Animal): # 单继承
def bark(self):
print("汪汪叫")

class Bird(Animal):
def fly(self):
print("飞翔")

class FlyingDog(Dog, Bird): # 多继承
pass

dog = Dog()
dog.eat() # 吃 (继承自Animal)
dog.bark() # 汪汪叫

flying_dog = FlyingDog()
flying_dog.eat() # 吃 (继承自Animal)
flying_dog.bark() # 汪汪叫 (继承自Dog)
flying_dog.fly() # 飞翔 (继承自Bird)

## 查看继承关系
print(Dog.__bases__) # (<class '__main__.Animal'>,)
print(FlyingDog.__bases__) # (<class '__main__.Dog'>, <class '__main__.Bird'>)
方法重写和调用父类方法

子类可以重写父类的方法,也可以通过 super()调用父类方法:

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
class Animal:
def __init__(self,name,age):
self.name = name
self.age = age

def info(self):
print(f"我是{self.name},今年{self.age}岁了")




class Dog(Animal):
def __init__(self,name,age,breed):
super().__init__(name,age)
self.breed = breed


def info(self):
super().info() # 这里调用父类的info方法
print(f"我是{self.name},我今年{self.age}岁了,我是属于{self.breed}品种的狗")

dog = Dog("旺财",2,"哈士奇")
dog.info()
# 输出:
# 我是旺财,今年2岁了 # 调用了父类的info方法
# 我是旺财,我今年2岁了,我是属于哈士奇品种的狗 # 重写了父类的info方法
继承的属性查找顺序 (MRO)

Python 使用 C3 算法计算方法解析顺序(MRO):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BaseClass:
def display_info(self):
print("基类的Info方法")

class FirstChild(BaseClass):
def display_info(self):
print("第一个子类的info方法")

class SecondChild(BaseClass):
def display_info(self):
print("第二个子类的info方法")

class MultipleInheritance(FirstChild, SecondChild):
pass

print(MultipleInheritance.mro())
## 输出: [<class '__main__.MultipleInheritance'>, <class '__main__.FirstChild'>, <class '__main__.SecondChild'>, <class '__main__.BaseClass'>, <class 'object'>]
# 先是自己=>再是第一个传入的类,第二个传入的类,最后是基类,最后是object
instance = MultipleInheritance()
instance.display_info() # FirstChild.display_info (按MRO顺序查找)
菱形继承问题

菱形继承(钻石继承)是指一个子类通过不同的路径继承了同一个基类:

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
class Animal:
def make_sound(self):
print("Animal.make_sound")

class Dog(Animal):
def make_sound(self):
print("Dog.make_sound")
super().make_sound()

class Cat(Animal):
def make_sound(self):
print("Cat.make_sound")
super().make_sound()

class CatDog(Dog, Cat):
def make_sound(self):
print("CatDog.make_sound")
super().make_sound()

# 菱形继承结构:
# Animal
# / \
# Dog Cat
# \ /
# CatDog

catdog = CatDog()
catdog.make_sound()
## 输出:
## CatDog.make_sound
## Dog.make_sound
## Cat.make_sound
## Animal.make_sound

在 Python 3 中,无论继承路径如何复杂,每个类在 MRO 中只会出现一次。

MixIn 设计模式

MixIn 是一种设计模式,用于为类添加功能,而不使用传统继承关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SwimMixin:
def swim(self):
print("I can swim")

class FlyMixin:
def fly(self):
print("I can fly")

class Animal:
def eat(self):
print("I can eat")

class Duck(Animal, SwimMixin, FlyMixin):
pass

duck = Duck()
duck.eat() # I can eat
duck.swim() # I can swim
duck.fly() # I can fly

3. 多态

多态是一种编程思想,在 python 中是没有多态这么一个技术的,比如汽车这种事物,他就有很多种形态,奔驰、宝马、奥拓这就叫多态、多态只是在继承下演化出来的一种概念而已,而我们要知道的是,为什么要有多态这个概念,奔驰宝马奥拓这都是可以跑的对吧,也就是说跑这个功能是汽车共有的功能,那我就在他们的共同的父类里面定义一个 run 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Car:
def run(self):
print('开始跑', end=' ')

class Benz(Car):
def run(self):
super().run()
print('奔驰跑起来好快哦!')

class BMW(Car):
def run(self):
super().run()
print('宝马跑起来好稳哦!')

class Auto(Car):
def run(self):
super().run()
print('奥拓跑起来好省油哦!')

## 多态性示例
cars = [Benz(), BMW(), Auto()]
for car in cars:
car.run() # 同一方法调用,不同行为
鸭子类型

多态性带来的好处:统一了使用标准,以不变应万变,无论对象千变万化,使用者都是使用同一种方式去调用,需要注意的是:不一定要继承父类的 run 方法才算是一种多态,引入 python 推崇的一个鸭子模型的概念,只要你长得像鸭子,走路像鸭子,那么你就是一只鸭子,所以类只要内部定义的方法一样,那么他们就同属于一种类别,这也是一种解耦合的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Duck:
def quack(self):
print("呱呱呱!")
def swim(self):
print("鸭子游泳!")

class Person:
def quack(self):
print("人类模仿鸭叫!")
def swim(self):
print("人类游泳!")

def make_it_quack_and_swim(thing):
thing.quack()
thing.swim()

duck = Duck()
person = Person()

make_it_quack_and_swim(duck)
make_it_quack_and_swim(person) # 人类没有继承Duck类,但可以表现得像鸭子
抽象基类

如果你不想使用鸭子模型,就想用父类来达到规范子类的效果,可以引入一个抽象基类概念,而这并不是 python 推崇的,python 推崇的永远是简洁至上,这样限制只会让程序变得臃肿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import abc

class Car(metaclass=abc.ABCMeta):
@abc.abstractmethod
def run(self):
pass

@abc.abstractmethod
def stop(self):
pass

class Benz(Car):
def run(self):
print("奔驰开始跑")

def stop(self):
print("奔驰停止")

## benz = Benz() # 如果没有实现所有抽象方法,会抛出TypeError

类方法与静态方法

方法类型比较

方法类型装饰器第一参数使用场景
实例方法self操作实例状态
类方法@classmethodcls操作类状态,替代构造函数
静态方法@staticmethod无特殊参数工具函数

示例场景

  • 在实际的开发中,实例方法通常用于操作与当前对象状态相关的行为。比如,我们定义一个 Person 类,该类实例方法可以访问每个对象的名称和年龄,并提供修改、获取这些属性的功能。
1
2
3
4
5
6
7
8
9
10
11
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def greet(self):
print(f"Hello, my name is {self.name} and I am {self.age} years old.")

# 创建实例并调用实例方法
person = Person("Alice", 30)
person.greet() # 输出:Hello, my name is Alice and I am 30 years old.

2. 类方法(@classmethod)

常见示例场景

  • 替代构造函数: 类方法通常用于定义一个替代构造函数,根据不同的输入创建对象。比如可以根据不同的配置文件或环境变量来初始化对象。
  • 操作类状态: 类方法通常用于修改与类相关的共享数据,比如修改所有实例的共享配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class DataBase:
IP = "127.0.0.1"
PORT = 8080

def __init__(self, ip, port):
self.ip = ip
self.port = port

def connect(self):
print(f"连接数据库成功!IP地址:{self.ip}, 端口号:{self.port}")

@classmethod
def instance_from_setting(cls):
# 从配置文件(如setting模块)或环境变量中读取IP和PORT并实例化对象
obj = cls(cls.IP, cls.PORT)
return obj

# 使用类方法来创建实例
db = DataBase.instance_from_setting()
print(db.__dict__) # 输出:{'ip': '127.0.0.1', 'port': 8080}
db.connect() # 输出:连接数据库成功!IP地址:127.0.0.1, 端口号:8080

应用场景

  • 你可以在配置或环境变化时灵活地调整类的构造逻辑,不需要每次都显式地提供参数。这使得代码更加简洁,并且易于扩展。

3. 静态方法(@staticmethod)

  • 第一参数: 无特殊参数。静态方法与类或实例无关,不需要 selfcls 参数。
  • 访问实例变量: 静态方法无法访问实例或类的属性。
  • 访问类变量: 静态方法只能做独立于实例和类的操作。它通常用于工具函数,或者处理与对象本身无关的逻辑。

常见示例场景

  • 静态方法通常用来封装一些独立的工具函数,这些函数与类或实例的状态无关。例如,数学计算、字符串处理等功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
class MathTools:

@staticmethod
def add(a, b):
return a + b

@staticmethod
def multiply(a, b):
return a * b

# 调用静态方法,无需实例化类
print(MathTools.add(5, 3)) # 输出:8
print(MathTools.multiply(4, 2)) # 输出:8

反射机制

反射(Reflection)是指在程序运行时,能够检查、访问、修改对象的属性和方法的能力。在 Python 中,反射允许我们在运行时动态地操作类和对象,特别是在无法事先确定对象类型或者对象的属性和方法时,反射提供了极大的灵活性。

常用反射函数

函数描述示例
hasattr(obj, name)检查对象是否有属性hasattr(obj, 'age')
getattr(obj, name, default)获取对象的属性getattr(obj, 'age', 0)
setattr(obj, name, value)设置对象的属性setattr(obj, 'age', 25)
delattr(obj, name)删除对象的属性delattr(obj, 'age')
dir(obj)列出对象的所有属性dir(obj)
type(obj)获取对象的类型type(obj)
isinstance(obj, cls)检查对象是否为类的实例isinstance(obj, Person)
issubclass(cls, parent)检查类是否为另一个类的子类issubclass(Dog, Animal)
callable(obj)检查对象是否可调用callable(obj.method)
vars(obj)获取对象的 __dict__vars(obj)

1. hasattr() — 检查对象是否有指定属性

hasattr(obj, name) 用于检查一个对象 obj 是否有指定名称的属性 name,返回 TrueFalse

示例

1
2
if hasattr(p, 'name'):  # 检查 Person 对象是否有 'name' 属性
print(getattr(p, 'name')) # 输出: Alice

2. getattr() — 获取对象的属性

getattr(obj, name, default) 用于获取对象的属性。如果该属性存在,返回其值;如果不存在,则返回指定的 default 值。如果没有提供 default,当属性不存在时会抛出 AttributeError

示例

1
2
3
4
5
6
name = getattr(p, 'name')  # 获取属性 'name' 的值
print(name) # 输出: Alice

# 如果属性不存在,使用默认值
age = getattr(p, 'age', 0) # 属性 'age' 存在,返回 30
print(age) # 输出: 30

3. setattr() — 设置对象的属性

setattr(obj, name, value) 用于给对象的指定属性 name 设置新值 value。如果该属性不存在,则会动态创建一个新的属性。

示例

1
2
setattr(p, 'city', 'New York')  # 动态设置新的属性 'city'
print(p.city) # 输出: New York

4. delattr() — 删除对象的属性

delattr(obj, name) 用于删除对象的指定属性。删除属性后,访问该属性会抛出 AttributeError

示例

1
2
delattr(p, 'city')  # 删除属性 'city'
# print(p.city) # 会抛出 AttributeError: 'Person' object has no attribute 'city'

5. dir() — 列出对象的所有属性

dir(obj) 返回一个列表,包含对象 obj 的所有属性和方法(包括其继承的属性)。

示例

1
print(dir(p))  # 列出 Person 对象的所有属性

6. type() — 获取对象的类型

type(obj) 返回对象 obj 的类型(即类)。

示例

1
print(type(p))  # 输出: <class '__main__.Person'>

7. isinstance() — 检查对象是否为类的实例

isinstance(obj, cls) 用于检查对象 obj 是否为类 cls 的实例或其子类的实例。

示例

1
2
if isinstance(p, Person):  # 检查 p 是否是 Person 类的实例
print("p 是 Person 类的实例") # 输出: p 是 Person 类的实例

8. issubclass() — 检查类是否为另一个类的子类

issubclass(cls, parent) 用于检查类 cls 是否是类 parent 的子类。

示例

1
2
3
4
5
6
7
class Animal:
pass

class Dog(Animal):
pass

print(issubclass(Dog, Animal)) # 输出: True

9. callable() — 检查对象是否可调用

callable(obj) 用于检查对象 obj 是否可以调用,即它是否是一个函数或具有 __call__() 方法的对象。

示例

1
2
3
4
5
def greet():
return "Hello"

print(callable(greet)) # 输出: True
print(callable(p)) # 输出: False,因为 Person 对象不可调用

10. vars() — 获取对象的 __dict__

vars(obj) 返回一个字典,包含对象 obj 的所有属性和对应的值。这是访问对象的实例变量的一种方式。

示例

1
print(vars(p))  # 输出: {'name': 'Alice', 'age': 30}

反射示例

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
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def greet(self):
return f"Hello, I'm {self.name}"

p = Person("Alice", 30)

## 检查和获取属性
if hasattr(p, 'name'): # 检查 p 对象是否有 'name' 属性
name = getattr(p, 'name') # 获取 'name' 属性的值
print(name) # 输出: Alice

## 设置新属性
setattr(p, 'city', 'New York') # 动态为 p 对象设置 'city' 属性
print(p.city) # 输出: New York

## 删除属性
delattr(p, 'city') # 删除 'city' 属性
# print(p.city) # 如果取消注释,会抛出 AttributeError: 'Person' object has no attribute 'city'

## 动态调用方法
if hasattr(p, 'greet') and callable(getattr(p, 'greet')): # 检查 greet 方法是否可调用
method = getattr(p, 'greet') # 获取 greet 方法
print(method()) # 输出: Hello, I'm Alice

## 获取所有属性
print(dir(p)) # 输出:['name', 'age', 'greet', ...],列出所有属性和方法

## 通过字符串获取类
class_name = 'Person'
if class_name in globals(): # 检查 Person 类是否在全局命名空间中
cls = globals()[class_name] # 动态获取 Person 类
new_person = cls('Bob', 25) # 使用动态获取的类实例化对象
print(new_person.name) # 输出: Bob

通用枚举以及抽象接口定义

enum 模块:实现枚举类型

enum 模块 (Python 3.4+引入) 提供了一种创建枚举(Enumerations)的方法。枚举是一组绑定的符号名称(成员),这些名称是常量,并且具有唯一的(通常是整数)值。使用枚举可以使代码更具可读性、更易于维护,并能有效防止因使用魔法数字或字符串而导致的错误。

enum 常用功能
类/函数/装饰器描述
enum.Enum创建基本枚举类型的基类。
enum.IntEnumEnum 的子类,其成员也是整数,可以和整数直接比较。
enum.FlagEnum 的子类,其成员可以使用位运算符 (`
enum.IntFlagFlag 的子类,其成员也是整数,并支持位运算。
enum.auto()在定义枚举成员时,自动为其分配合适的值 (通常是递增的整数)。
@enum.unique一个类装饰器,确保枚举中没有重复值的成员。
MyEnum.MEMBER_NAME访问枚举成员。
MyEnum['MEMBER_NAME']通过字符串名称访问枚举成员。
MyEnum(value)通过值获取枚举成员。
member.name(枚举成员属性) 获取成员的名称 (字符串)。
member.value(枚举成员属性) 获取成员的值。

批注:核心记忆功能 (enum 模块)

  • 继承 enum.Enum (或 IntEnum, Flag) 来定义自己的枚举。
  • 使用 enum.auto() 自动分配值,非常方便。
  • 通过 MyEnum.MEMBER_NAMEMyEnum(value) 来引用和获取枚举成员。
  • member.namemember.value 是最常用的成员属性。
  • @unique 装饰器有助于保证值的唯一性。
enum 模块代码示例
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
from print_utils import *
from enum import Enum, IntEnum, Flag, auto, unique
from typing import Any # 用于类型注解

## =============================================================
## 0. enum 模块演示准备
## =============================================================
print_header("enum 模块功能演示")

## =============================================================
## 1. 基本枚举 (Enum)
## =============================================================
print_subheader("1. 基本枚举 (Enum)")

class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# # YELLOW = 1 # 如果没有 @unique,这不会报错,但可能不是期望的行为

print_info(f"枚举成员 Color.RED: {Color.RED}")
print_info(f" Color.RED 的名称 (name): '{Color.RED.name}'")
print_info(f" Color.RED 的值 (value): {Color.RED.value}")
print_info(f" 通过值获取成员 Color(2): {Color(2)}")
print_info(f" 通过名称获取成员 Color['GREEN']: {Color['GREEN']}")
print_info(f" Color.RED is Color.GREEN: {Color.RED is Color.GREEN}")
print_info(f" Color.RED == Color(1): {Color.RED == Color(1)}")
print_info(f" 遍历枚举 Color:")
for color_member in Color:
print(f" - {color_member.name} -> {color_member.value}")

## =============================================================
## 2. 使用 auto() 自动分配值 和 @unique 确保值唯一
## =============================================================
print_subheader("2. 使用 auto() 和 @unique")

@unique # 确保枚举成员的值是唯一的
class Weekday(Enum):
MONDAY = auto() # 值通常从 1 开始递增
TUESDAY = auto()
WEDNESDAY = auto()
THURSDAY = auto()
FRIDAY = auto()
SATURDAY = auto()
SUNDAY = auto()

print_info("使用 auto() 和 @unique 的 Weekday 枚举:")
for day in Weekday:
print(f" - {day.name}: {day.value}")

print_info(f"Weekday['MONDAY'].value: {Weekday.MONDAY.value}")


## =============================================================
## 3. 整数枚举 (IntEnum)
## =============================================================
print_subheader("3. 整数枚举 (IntEnum)")
class ErrorCode(IntEnum): # 成员可以直接与整数比较
OK = 0
NOT_FOUND = 404
SERVER_ERROR = 500

print_info(f"ErrorCode.NOT_FOUND: {ErrorCode.NOT_FOUND}")
print_info(f" ErrorCode.NOT_FOUND == 404: {ErrorCode.NOT_FOUND == 404}") # True
print_info(f" ErrorCode.OK < ErrorCode.SERVER_ERROR: {ErrorCode.OK < ErrorCode.SERVER_ERROR}") # True
print_info(f" int(ErrorCode.SERVER_ERROR): {int(ErrorCode.SERVER_ERROR)}") # 500

## =============================================================
## 4. 标志枚举 (Flag 和 IntFlag) - 支持位运算
## =============================================================
print_subheader("4. 标志枚举 (Flag 和 IntFlag)")

class Permissions(Flag): # Flag 成员的值通常是 2 的幂
NONE = 0 # 通常定义一个 "无" 或 "空" 标志
READ = auto() # 1
WRITE = auto() # 2
EXECUTE = auto() # 4
READ_WRITE = READ | WRITE # 组合标志
ALL = READ | WRITE | EXECUTE

user1_perms: Permissions = Permissions.READ | Permissions.WRITE
user2_perms: Permissions = Permissions.READ
admin_perms: Permissions = Permissions.ALL

print_info(f"User1 权限 (READ | WRITE): {user1_perms} (值为: {user1_perms.value})")
print_info(f" User1 是否有 READ 权限: {Permissions.READ in user1_perms}") # True
print_info(f" User1 是否有 EXECUTE 权限: {Permissions.EXECUTE in user1_perms}") # False
print_info(f" User1 是否只有 READ_WRITE 权限: {user1_perms == Permissions.READ_WRITE}") # True

print_info(f"Admin 权限 (ALL): {admin_perms} (值为: {admin_perms.value})")
print_info(f" Admin 是否包含所有权限 (READ|WRITE|EXECUTE): {admin_perms == (Permissions.READ | Permissions.WRITE | Permissions.EXECUTE)}")

## IntFlag 类似,但其成员也是整数
class FileAccess(IntFlag):
R_OK = os.R_OK # 使用 os 模块的常量 (通常是 4)
W_OK = os.W_OK # (通常是 2)
X_OK = os.X_OK # (通常是 1)
RW_OK = R_OK | W_OK

file_mode: FileAccess = FileAccess.R_OK | FileAccess.W_OK
print_info(f"\nFileAccess 模式 (R_OK | W_OK): {file_mode} (值为: {file_mode.value})")
print_info(f" 文件是否可读 (R_OK in file_mode): {FileAccess.R_OK in file_mode}")

坑点与建议 (enum 模块):

  • 可读性与维护性: 使用枚举可以替代代码中散落的魔法数字或字符串常量,使得代码意图更清晰,更易于维护。当需要修改某个状态的含义或值时,只需在枚举定义处修改一处即可。
  • 成员的唯一性:
    • 默认情况下,Enum 允许不同的成员名称指向相同的值(别名)。
    • 使用 @unique 类装饰器可以强制枚举中的所有成员值必须唯一,如果存在重复值则会在定义时抛出 ValueError
  • 成员的比较: 枚举成员是单例。你可以使用 is== 来比较两个枚举成员是否相同。IntEnumIntFlag 的成员还可以直接与整数进行比较。
  • 迭代与访问: 可以迭代枚举类的所有成员。可以通过成员名 (MyEnum.MEMBER)、方括号加成员名字符串 (MyEnum['MEMBER']) 或成员值 (MyEnum(value)) 来访问特定的枚举成员。
  • auto() 的行为: enum.auto() 会自动为枚举成员分配一个值。默认情况下,对于 Enum,它从1开始递增。对于 Flag,它会分配2的幂 (1, 2, 4, 8, …)。你可以通过重写 _generate_next_value_ 特殊方法来自定义 auto() 的行为。
  • Flag 与位运算: FlagIntFlag 类型的枚举成员支持位运算符 (|, &, ^, ~),非常适合表示权限、状态组合等场景。可以使用 in 操作符来检查一个组合标志是否包含某个特定标志。
  • JSON 序列化: 默认情况下,枚举成员在 JSON 序列化时可能不会直接变成期望的字符串或整数。你可能需要自定义序列化逻辑,或者在序列化前获取成员的 .name.value 属性。
abc 模块:定义抽象基类 (Abstract Base Classes)

abc 模块 (Abstract Base Classes) 用于帮助定义和使用抽象基类。抽象基类是一种不能被直接实例化的类,它存在的目的是作为其他类的“模板”或“接口约定”。子类如果继承了抽象基类,就必须实现抽象基类中声明的所有抽象方法和抽象属性,否则子类本身也会成为抽象类,无法实例化。

这有助于强制执行接口规范,实现多态,并构建更健壮、更松耦合的系统。

abc 常用功能
类/装饰器描述
abc.ABC一个辅助类,用于通过继承来创建抽象基类。
@abc.abstractmethod一个装饰器,用于声明一个抽象方法,子类必须实现它。
@abc.abstractproperty(Python 3.3+) 一个装饰器,用于声明一个抽象属性 (getter, setter, deleter),子类必须实现。在旧版本中,通常通过组合 @property@abstractmethod 实现。
@abc.abstractclassmethod(Python 3.3+) 声明抽象类方法。
@abc.abstractstaticmethod(Python 3.3+) 声明抽象静态方法。
isinstance(obj, MyABC)可以检查一个对象 obj 是否是某个抽象基类 MyABC 的实例 (即使是通过虚拟子类注册的)。
MyABC.register(MyClass)(类方法) 将 MyClass 注册为 MyABC 的虚拟子类,即使 MyClass 没有显式继承 MyABC

批注:核心记忆功能 (abc 模块)

  • 继承 ABC 来定义抽象基类。
  • 使用 @abstractmethod 来标记那些必须在具体子类中被重写的方法。
  • 抽象基类不能直接实例化。
  • 它的主要目的是定义一个清晰的接口或契约,供子类遵循。
abc 模块代码示例
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from print_utils import * # 假设 print_utils.py 可用
from abc import ABC, abstractmethod, abstractproperty # Python 3.3+ for abstractproperty
from typing import Tuple, Type, Any # 用于类型注解

## =============================================================
## 0. abc 模块演示准备
## =============================================================
print_header("abc 模块 (抽象基类) 功能演示")

## =============================================================
## 1. 定义一个抽象基类 Shape
## =============================================================
print_subheader("1. 定义抽象基类 Shape")

class Shape(ABC): # 继承自 ABC,表明这是一个抽象基类
"""一个表示几何形状的抽象基类。"""

@abstractmethod # 标记 area() 为抽象方法
def area(self) -> float:
"""计算并返回形状的面积。子类必须实现此方法。"""
pass # 抽象方法通常只有 pass 或 raise NotImplementedError()

@abstractmethod # 标记 perimeter() 为抽象方法
def perimeter(self) -> float:
"""计算并返回形状的周长。子类必须实现此方法。"""
raise NotImplementedError("子类必须实现 perimeter 方法")

@abstractproperty # Python 3.3+ (旧版本用 @property + @abstractmethod)
def shape_type(self) -> str:
"""返回形状的类型名称 (例如 'Circle', 'Rectangle')。子类必须实现此属性。"""
pass

# # 这是一个具体方法,子类可以直接继承或重写
def describe(self) -> str:
"""返回对形状的描述。"""
return f"这是一个 '{self.shape_type}',面积为 {self.area():.2f},周长为 {self.perimeter():.2f}。"

print_info("抽象基类 Shape 已定义。")

## 尝试实例化抽象基类 (会失败)
try:
s = Shape()
print_error("错误:抽象基类 Shape 被实例化了!(不应发生)")
except TypeError as e:
print_success(f"尝试实例化 Shape 时捕获到预期的 TypeError: {e}")


## =============================================================
## 2. 创建具体子类 Circle 和 Rectangle
## =============================================================
print_subheader("2. 创建具体子类 Circle 和 Rectangle")

class Circle(Shape):
"""表示圆形的具体类。"""
PI: float = 3.1415926535

def __init__(self, radius: float):
if radius <= 0:
raise ValueError("半径必须为正数。")
self._radius: float = radius

def area(self) -> float: # 实现抽象方法
return self.PI * (self._radius ** 2)

def perimeter(self) -> float: # 实现抽象方法
return 2 * self.PI * self._radius

@property # 实现抽象属性
def shape_type(self) -> str:
return "圆形"

class Rectangle(Shape):
"""表示矩形的具体类。"""
def __init__(self, width: float, height: float):
if width <= 0 or height <= 0:
raise ValueError("宽度和高度必须为正数。")
self._width: float = width
self._height: float = height

def area(self) -> float:
return self._width * self._height

def perimeter(self) -> float:
return 2 * (self._width + self._height)

@property
def shape_type(self) -> str:
return "矩形"

print_info("具体子类 Circle 和 Rectangle 已定义。")

## 实例化具体子类
circle_instance = Circle(radius=5.0)
rectangle_instance = Rectangle(width=4.0, height=6.0)

print_success(f"圆的描述: {circle_instance.describe()}")
print_success(f"矩形的描述: {rectangle_instance.describe()}")

## =============================================================
## 3. 演示未完全实现抽象方法的子类
## =============================================================
print_subheader("3. 演示未完全实现抽象方法的子类")
class IncompleteSquare(Shape): # Square 应该也实现 perimeter 和 shape_type
def __init__(self, side: float):
self._side: float = side

def area(self) -> float:
return self._side ** 2

# # 故意不实现 perimeter 和 shape_type
# # @property
# # def shape_type(self) -> str:
# # return "不完整的正方形"

print_info("定义了一个未完全实现抽象方法的 IncompleteSquare 类。")
try:
incomplete_sq = IncompleteSquare(side=3)
print_error("错误:不完整的抽象子类 IncompleteSquare 被实例化了!(不应发生)")
# # incomplete_sq.describe() # 如果能实例化,这里调用 perimeter() 会出错
except TypeError as e:
print_success(f"尝试实例化 IncompleteSquare 时捕获到预期的 TypeError: {e}")


if __name__ == '__main__':
pass # 演示已在各节内进行
print_header("abc 模块演示结束。")

坑点与建议 (abc 模块):

  • 强制接口实现: abc 模块的主要目的是强制子类实现特定的方法和属性,从而确保它们都遵循一个共同的“契约”或“接口”。这在大型项目或框架设计中非常有用,可以提高代码的稳定性和可维护性。
  • 实例化错误: 如果一个类继承了 ABC 并有未被实现的抽象方法/属性(通过 @abstractmethod@abstractproperty 标记),那么尝试实例化这个类(或其同样未完全实现的子类)会在运行时抛出 TypeError
  • @abstractproperty: 从 Python 3.3 开始,可以直接使用 @abstractproperty。在此之前,定义抽象属性通常需要组合使用 @property@abstractmethod
    1
    2
    3
    4
    @property
    @abstractmethod
    def my_abstract_prop(self):
    pass
  • 虚拟子类 (register): 除了通过继承,你还可以使用 MyABC.register(AnotherClass) 将一个完全不相关的类 AnotherClass 注册为抽象基类 MyABC 的“虚拟子类”。这意味着 isinstance(obj_of_another_class, MyABC) 会返回 True,但 AnotherClass 并不会真正从 MyABC 继承任何方法或属性,也不会被强制实现抽象方法。这种方式主要用于在不修改现有类继承关系的情况下,将其归类到某个抽象接口下。
  • 不仅仅是“接口”: 虽然抽象基类的作用与某些语言中的“接口”概念相似,但 Python 的抽象基类可以包含具体的方法实现(如示例中的 describe() 方法),子类可以直接继承这些实现。
  • 何时使用: 当你希望定义一组类应该共同拥有的行为,但具体实现方式因类而异时,抽象基类是一个很好的选择。例如,定义一个通用的 DataStorage 接口,然后有 DatabaseStorage, FileStorage, InMemoryStorage 等具体实现。

元类编程

元类是创建类的类,type 是 Python 的内置元类。

元类的核心方法

方法描述调用时机
__new__(mcs, name, bases, attrs)创建类对象定义类时
__init__(cls, name, bases, attrs)初始化类对象类创建后
__call__(cls, *args, **kwargs)创建类的实例实例化类时

使用 type 动态创建类

在我们使用 class 定义关键字时,其实是 Python 底层执行了以下四步操作为我们定义了一个类,分别是:

  • 1.定义类名
  • 2.定义基类
  • 3.执行类子代码,产生名称空间
  • 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

# 1.定义类名
class_name = "Human"

#2.定义基类
class_bases = (object,)

# 3.执行子类代码,产生名称空间
class_dic = {}
class_body = """
def __init__(self, name, age):
self.name = name
self.age = age

def say_hello(self):
print(f"你好,我是{self.name},今年{self.age}岁")
"""
# exec函数语法:exec(object[, globals[, locals]])
# object: 要执行的Python代码字符串或代码对象
# globals: 可选参数,指定代码执行时的全局命名空间,默认为当前全局命名空间
# locals: 可选参数,指定代码执行时的局部命名空间,默认与globals相同

# 这里传入三个参数:
# 1. class_body: 包含类方法定义的Python代码字符串
# 2. {}: 空字典作为全局命名空间,避免访问外部全局变量
# 3. class_dic: 作为局部命名空间,执行后class_body中定义的方法会存储在这个字典中

# 这样做的目的是隔离执行环境,并将执行结果(类的方法定义)收集到class_dic字典中,
# 之后可以将这个字典传给type函数来动态创建类
exec(class_body, {}, class_dic)

# 4.调用原类
Human = type(class_name,class_bases,class_dic)


# 5. 创建实例对象
person = Human("张三", 25)
# 6. 调用实例方法
person.say_hello() # 你好,我是张三,今年25岁

自定义元类

对于元类来说,最关键的步骤也就是在第四步,在我们调用原类之前可以对这个类做一些自定义化操作,如 类名不能带特殊符号,类必须写文档注释...... 我们通过一个自定义类来实现这个功能

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
class MyType(type):
def __init__(self, class_name, class_bases, class_attrs):
if "_" in class_name:
raise NameError("类名不能包含下划线")

if not class_attrs.get("__doc__"):
raise ValueError("类必须有文档注释")

# 检查类属性名是否符合规范
for attr_name in class_attrs:
if attr_name.startswith("__") and attr_name.endswith("__"):
continue
if not attr_name.islower():
raise NameError("属性名必须全部小写")

# 检查是否有必要的方法
if "info" not in class_attrs:
raise ValueError("类必须实现info方法")

# 检查类的基类
if not class_bases:
raise TypeError("类必须继承至少一个父类")



class Human(object, metaclass=MyType):
"""人类的基本信息类,如果没有这个注释会报错"""
def __init__(self, name, age):
self.name = name
self.age = age

def info(self):
print(f"name:{self.name},age:{self.age}")

元类的 __new__ 方法

在我们调用类产生一个空对象的时候,在执行 __init__ 方法之前一定执行了其他方法,我们都知道在 Python 中,所有的类都默认继承自 object 基类,但在下述的例子,当我们没有给自定义元类传入 object,他报错了,这是为什么呢?

本质上我们的 self,也就指的是 Human 这个类,对于 class_bases 来说,他只是一个形参而已,真正为我们继承父类的方法是 __new__ 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyType(type):
def __init__(self, class_name, class_bases, class_attrs):
print(self.__bases__) # (<class 'object'>,)
# 检查类的基类
if not class_bases:
raise TypeError("类必须继承至少一个父类")




class Human(metaclass=MyType):
def __init__(self, name, age):
self.name = name
self.age = age

def info(self):
print(f"name:{self.name},age:{self.age}")

我们可以通过进一步剖析 __new__ 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyType(type):
def __init__(self, class_name, class_bases, class_attrs):
print(self.__bases__) # (<class 'object'>,)
# 检查类的基类
if not class_bases:
raise TypeError("类必须继承至少一个父类")

def __new__(cls, *args, **kwargs):
print("cls==========>", cls) # <class '__main__.MyType'>
print("args=========>", args) # ('Human', (), {'__module__': '__main__', '__qualname__': 'Human', '__init__': <function Human.__init__ at 0x000001D726C02CA0>, 'info': <function Human.info at 0x000001D726B9BEE0>})
print("kwargs=========>", kwargs) # {}
return super().__new__(cls, *args, **kwargs) # 通过调用父类的__new__方法,他会在底层帮我们继承object对象,存放__()__属性等


class Human(metaclass=MyType):
def __init__(self, name, age):
self.name = name
self.age = age

def info(self):
print(f"name:{self.name},age:{self.age}")

元类的 __call__ 方法实例

当调用类(如 Person())创建实例时,实际上是调用元类的 __call__ 方法。自定义此方法可以控制实例化过程:

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
class SingletonMeta(type):
_instances = {}

def __call__(cls, *args, **kwargs):
"""控制类实例化过程"""
if cls not in cls._instances:
print(f"创建 {cls.__name__} 的新实例")
# 正常的实例化过程:创建实例并初始化
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
else:
print(f"返回 {cls.__name__} 的现有实例")
return cls._instances[cls]

class Database(metaclass=SingletonMeta):
def __init__(self, host, port):
self.host = host
self.port = port

## 第一次调用,创建新实例
db1 = Database("localhost", 5432)
print(f"DB1: {db1.host}:{db1.port}")

## 第二次调用,返回现有实例
db2 = Database("example.com", 8080) # 参数被忽略!
print(f"DB2: {db2.host}:{db2.port}") # 仍然是localhost:5432

## 验证是同一个实例
print(db1 is db2) # True

元类链和类创建过程

步骤描述涉及方法
1收集类定义信息Python 解释器执行类体
2调用元类的 __new__创建类对象
3调用元类的 __init__初始化类对象
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
class TraceMeta(type):
def __new__(mcs, name, bases, attrs):
print(f"[元类__new__] 创建类 {name}")
return super().__new__(mcs, name, bases, attrs)

def __init__(cls, name, bases, attrs):
print(f"[元类__init__] 初始化类 {name}")
super().__init__(name, bases, attrs)

def __call__(cls, *args, **kwargs):
print(f"[元类__call__] 调用类 {cls.__name__}")

# 1. 创建实例(调用类的__new__)
instance = cls.__new__(cls, *args, **kwargs)

# 2. 初始化实例(调用类的__init__)
if isinstance(instance, cls):
print(f"[元类__call__] 初始化 {cls.__name__} 实例")
cls.__init__(instance, *args, **kwargs)

return instance

class MyClass(metaclass=TraceMeta):
def __new__(cls, *args, **kwargs):
print(f"[类__new__] 创建 {cls.__name__} 实例")
return super().__new__(cls)

def __init__(self, x=None):
print(f"[类__init__] 初始化 {self.__class__.__name__} 实例,x={x}")
self.x = x

print("定义完成,开始创建实例...")
obj = MyClass(42)

内置的元类属性和方法表

属性/方法描述示例
__metaclass__指定类的元类class A(metaclass=MyMeta):
__mro__方法解析顺序A.__mro__
__bases__直接父类元组A.__bases__
__subclasses__()直接子类列表A.__subclasses__()
__class__对象的类obj.__class__
mro()返回方法解析顺序A.mro()

Python 特殊方法(魔术方法)

Python 的特殊方法(也称为魔术方法或双下方法)是 Python 面向对象编程中的核心概念,它们允许自定义类行为以适应 Python 的语言特性。这些方法以双下划线开始和结束(如 __init__),当特定操作发生时会被自动调用

1. 对象创建与销毁
方法名描述触发方式使用场景
__init__(self, ...)初始化对象创建实例后调用设置实例属性
__new__(cls, ...)创建对象实例化时调用(比 init 先)自定义实例创建过程,实现单例模式
__del__(self)析构方法对象被销毁时调用释放资源,关闭文件或连接
实际应用场景:
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
class DatabaseConnection:
_instance = None # 类变量用于存储单例实例

def __new__(cls, *args, **kwargs): # 这里的cls,也就是我们当前的类DatabaseConnection
# 实现单例模式,确保只创建一个数据库连接实例
if cls._instance is None: # 若我们的类里面的_instance变量没有被初始化,则创建一个新的实例
cls._instance = super().__new__(cls) # 调用父类的__new__方法,创建实例
print("创建新的数据库连接实例") # 打印日志
return cls._instance # 返回单例实例

def __init__(self, host, user, password):
if not hasattr(self,"initialized"): # 如果类对象身上
self.host = host
self.user = user
self.password = password
self.initialized = True # 初始化完成
self.connection = None # 数据库连接对象

def connect(self):
# 模拟数据库连接
if not self.connection:
print(f"连接到数据库{self.host} 成功 \n 用户名:{self.user} \n 密码:{self.password}")
self.connection = True # 连接成功
return self.connection

def __del__(self):
# 对象销毁时关闭连接
if self.connection:
print(f"关闭数据库连接:{self.host}")
self.connection = None


# 使用示例
db1 = DatabaseConnection("localhost", "admin", "password")
db2 = DatabaseConnection("localhost", "admin", "newpassword") # 不会创建新实例
# 判断两者是否为同一个实例
print(db1 is db2) # True
db1.connect() # 连接到数据库
2. 对象表示
方法名描述触发方式使用场景
__str__(self)可读字符串表示str(obj), print(obj)在直接输出类时标准化的输出类上面的所有属性
__repr__(self)正式字符串表示repr(obj)为开发者提供详细信息,理想情况下应返回可重新创建对象的代码
实际应用场景:
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
class Product:
def __init__(self, id, name, price):
self.id = id
self.name = name
self.price = price

def __str__(self):
# 返回用户友好的描述
return f"{self.name}{self.price:.2f})"

def __repr__(self):
# 返回可重新创建此对象的代码
return f"Product(id={self.id!r}, name={self.name!r}, price={self.price!r})"


# 使用示例
apple = Product(1, "苹果", 5.99)
print(apple) # 输出: 苹果 (¥5.99)
print(repr(apple)) # 输出: Product(id=1, name='苹果', price=5.99)

# 在调试中的价值
products = [
Product(1, "苹果", 5.99),
Product(2, "香蕉", 3.50)
]
print(products) # 会显示完整的repr信息,有助于调试
  • __str__ 输出的是一个友好易读的字符串,适合用户查看。

  • __repr__ 输出的是一个更正式的字符串,通常能够通过 eval() 来重建该对象。

  • 示例 2:交互式环境中的 __repr__

    在 Python 的交互式环境中(比如直接在命令行输入 python),当你打印一个对象时,如果对象有 __repr__ 方法,通常会看到 __repr__ 的返回值,而不是 __str__

    1
    2
    obj = MyClass("Bob", 25)
    obj # 在交互式环境中,自动调用 __repr__
    1
    MyClass('Bob', 25)
  1. __repr__ 的目标

    __repr__ 的设计目标是让对象的字符串表示能够作为一种明确、无歧义的代码表达式,最好能够让开发者通过 eval() 来重新构建该对象。例如,__repr__ 返回的字符串应该是:

    1
    MyClass('Bob', 25)

    如果你运行 eval("MyClass('Bob', 25)"),它应该能够重新创建出一个与 obj 相同的 MyClass 实例。


3. 集合类操作
方法名描述触发方式使用场景
__len__(self)获取对象长度len(obj)自定义集合的大小
__getitem__(self, key)索引访问obj[key]使对象可通过索引或键访问
__setitem__(self, key, value)索引赋值obj[key] = value允许通过索引设置值
__delitem__(self, key)删除索引项del obj[key]允许通过索引删除项
__iter__(self)返回迭代器for x in obj使对象可迭代
__next__(self)返回下一个值next(obj)实现迭代器协议
__contains__(self, item)成员检查item in obj自定义成员检查逻辑
实际应用场景:自定义商品购物车
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
class Product:
def __init__(self, id, name, price):
self.id = id
self.name = name
self.price = price

def __str__(self):
"""返回用户友好的描述"""
return f"{self.name}{self.price:.2f})"

def __repr__(self):
"""返回可重新创建此对象的代码"""
return f"Product(id={self.id!r}, name={self.name!r}, price={self.price!r})"


class ShoppingCart:
"""
购物车类:管理用户选择的商品及其数量
"""
def __init__(self):
"""初始化空购物车"""
self.items = {} # 商品ID -> (商品, 数量)的映射

def add_item(self, product, quantity=1):
"""
增加商品到购物车

参数:
product (Product): 要添加的商品
quantity (int): 添加的数量,默认为1
"""
if product.id in self.items:
# 如果商品已经在购物车中,更新数量
self.items[product.id] = (product, self.items[product.id][1] + quantity)
else:
# 如果商品不在购物车中,直接添加
self.items[product.id] = (product, quantity)

def __getitem__(self, product_id):
"""
支持通过索引访问: cart[product_id]
返回商品和数量的元组
"""
if product_id not in self.items:
raise KeyError(f"商品ID {product_id} 不在购物车中")
return self.items[product_id]

def __setitem__(self, product_id, quantity):
"""
支持通过索引设置数量: cart[product_id] = quantity
"""
if product_id not in self.items:
raise KeyError(f"商品ID {product_id} 不在购物车中")
product = self.items[product_id][0]
self.items[product_id] = (product, quantity)

def __delitem__(self, product_id):
"""
支持通过del删除商品: del cart[product_id]
"""
if product_id not in self.items:
raise KeyError(f"商品ID {product_id} 不在购物车中")
del self.items[product_id]

def __len__(self):
"""返回购物车中的商品种类数量"""
return len(self.items)

def __iter__(self):
"""使购物车可迭代,返回商品和数量"""
for product_id in self.items:
yield self.items[product_id]

def __contains__(self, product_id):
"""支持成员检查: product_id in cart"""
print("__contains__被调用")
return product_id in self.items

def total_price(self):
"""计算购物车中所有商品的总价"""
return sum(product.price * quantity for product, quantity in self.items.values())

def __str__(self):
"""用户友好的购物车展示"""
if not self.items:
return "购物车为空"

cart_str = "购物车内容:\n"
for product, quantity in self.items.values():
cart_str += f" {product.name} x {quantity}: ¥{product.price * quantity:.2f}\n"
cart_str += f"总计: ¥{self.total_price():.2f}"
return cart_str


# ===== 测试代码 =====
print("===== 创建商品 =====")
apple = Product(1, "苹果", 5.99)
banana = Product(2, "香蕉", 3.50)
orange = Product(3, "橙子", 4.25)
print(f"商品1: {apple}")
print(f"商品2: {banana}")
print(f"商品3: {orange}")
print(f"商品repr: {repr(apple)}")

print("\n===== 购物车基本操作 =====")
cart = ShoppingCart()
print("空购物车:", cart)

# 添加商品测试
print("\n添加商品:")
cart.add_item(apple, 3)
cart.add_item(banana, 2)
print(cart)

# 添加已有商品测试
print("\n添加已有商品:")
cart.add_item(apple, 2) # 现在应该有5个苹果
print(cart)

# 通过索引访问测试
print("\n通过索引访问:")
apple_info = cart[1] # 获取ID为1的商品信息
print(f"ID为1的商品信息: {apple_info[0].name}, 数量: {apple_info[1]}")

# 通过索引修改数量测试
print("\n通过索引修改数量:")
cart[2] = 4 # 将香蕉的数量改为4
print(cart)

# 删除商品测试
print("\n删除商品:")
del cart[1] # 删除苹果
print(cart)

# 长度测试
print("\n购物车商品种类:", len(cart))

# 迭代购物车测试
print("\n迭代购物车中的商品:")
for product, quantity in cart:
print(f" {product.name}: {quantity}个")

# 成员检查测试
print("\n成员检查:")
print(f"香蕉在购物车中? {2 in cart}") # True
print(f"苹果在购物车中? {1 in cart}") # False
print(f"橙子在购物车中? {3 in cart}") # False

# 添加新商品测试
print("\n添加新商品:")
cart.add_item(orange, 1)
print(cart)

# 计算总价测试
print("\n购物车总价: ¥{:.2f}".format(cart.total_price()))

# 错误处理测试
print("\n===== 错误处理测试 =====")
try:
cart[4] # 尝试访问不存在的商品
except KeyError as e:
print(f"预期的错误: {e}")

4. 调用与比较操作

我们这里主要讲 __call__ 这个方法,有利于我们理解为什么 __new__ 方法可以在对象实例化的时候调用

当我们调用一个对象,也就相当于 ↓

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person:
def __init__(self,name,age):
self.name = name
self.age = age

def __call__(self, *args, **kwargs):
print("__call__ 被调用了")
return self.name, self.age


p = Person("张三", 20)

print(p()) # __call__ 被调用了 ('张三', 20)

那么 Python 就会自动调用 obj 中的 __call__ 方法,同样的,__call__ 内的返回值也会传递给 obj

那么也就是说,如果想把一个对象变成一个可以加括号调用的对象,就在对象的类里面增添一个 __call__ 方法

方法名描述触发方式使用场景
__call__(self, ...)使对象可调用obj()创建可调用对象,函数式编程
__eq__(self, other)相等比较obj1 == obj2自定义对象相等性
__lt__(self, other)小于比较obj1 < obj2自定义对象排序
__gt__(self, other)大于比较obj1 > obj2自定义对象排序
__le__(self, other)小于等于obj1 <= obj2自定义对象排序
__ge__(self, other)大于等于obj1 >= obj2自定义对象排序
__hash__(self)计算哈希值hash(obj)允许对象作为字典键或集合元素
实际应用场景:数据分析器
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
class DataAnalyzer:
def __init__(self, name):
self.name = name
self.data = []

def add_data(self, value):
# 添加数据点
if not isinstance(value, (int, float)):
raise TypeError("数据必须是数字类型")
self.data.append(value)

def __call__(self, new_data=None):
# 调用对象时可以添加新数据并返回分析结果
if new_data is not None:
if isinstance(new_data, list):
for value in new_data:
self.add_data(value)
else:
self.add_data(new_data)

if not self.data:
return {"count": 0, "sum": 0, "avg": 0, "min": None, "max": None}

return {
"count": len(self.data),
"sum": sum(self.data),
"avg": sum(self.data) / len(self.data),
"min": min(self.data),
"max": max(self.data)
}

def __eq__(self, other):
# 比较两个分析器的数据是否相同
if not isinstance(other, DataAnalyzer):
return False
return self.data == other.data

def __lt__(self, other):
# 根据数据平均值比较分析器
if not isinstance(other, DataAnalyzer):
raise TypeError("只能与另一个DataAnalyzer比较")
if not self.data or not other.data:
raise ValueError("无法比较空数据集")
return sum(self.data)/len(self.data) < sum(other.data)/len(other.data)

def __hash__(self):
# 允许对象作为字典键,基于名称和数据的散列
return hash((self.name, tuple(self.data)))

# 使用示例
analyzer1 = DataAnalyzer("温度分析器")
analyzer1.add_data(22.5)
analyzer1.add_data(23.1)
analyzer1.add_data(21.8)

# 通过调用对象获取分析结果
print(analyzer1()) # 输出分析结果

# 函数式用法 - 添加新数据并获取结果
result = analyzer1([24.2, 22.9])
print(f"更新后的分析结果: {result}")

# 创建另一个分析器用于比较
analyzer2 = DataAnalyzer("湿度分析器")
analyzer2([45, 48, 51, 47])

# 比较分析器
if analyzer1 < analyzer2:
print(f"{analyzer1.name}的平均值低于{analyzer2.name}")
else:
print(f"{analyzer1.name}的平均值高于或等于{analyzer2.name}")

# 使用分析器作为字典键
analyzers = {
analyzer1: "温度数据",
analyzer2: "湿度数据"
}
print(f"字典中的分析器: {list(analyzers.values())}")
5. 算术运算操作

在我们通过内置的运算符需要频繁实现一些非常规四则运算时,可以重写四则运算方法,通过这样来快速的进行数学运算分析

方法名描述触发方式使用场景
__add__(self, other)加法obj1 + obj2自定义对象相加
__sub__(self, other)减法obj1 - obj2自定义对象相减
__mul__(self, other)乘法obj1 * obj2自定义对象相乘
__truediv__(self, other)除法obj1 / obj2自定义对象相除
实际应用场景:向量运算
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
class Vector2D:
def __init__(self, x=0, y=0):
self.x = x
self.y = y

def __add__(self, other):
# 向量加法
if isinstance(other, Vector2D):
return Vector2D(self.x + other.x, self.y + other.y)
elif isinstance(other, (int, float)):
# 支持向量与标量相加
return Vector2D(self.x + other, self.y + other)
else:
raise TypeError("不支持的操作数类型")

def __sub__(self, other):
# 向量减法
if isinstance(other, Vector2D):
return Vector2D(self.x - other.x, self.y - other.y)
elif isinstance(other, (int, float)):
return Vector2D(self.x - other, self.y - other)
else:
raise TypeError("不支持的操作数类型")

def __mul__(self, other):
# 向量乘法(点积或标量乘法)
if isinstance(other, Vector2D):
# 向量点积
return self.x * other.x + self.y * other.y
elif isinstance(other, (int, float)):
# 向量与标量相乘
return Vector2D(self.x * other, self.y * other)
else:
raise TypeError("不支持的操作数类型")

def __truediv__(self, other):
# 向量除法(仅支持标量除法)
if isinstance(other, (int, float)):
if other == 0:
raise ZeroDivisionError("除数不能为零")
return Vector2D(self.x / other, self.y / other)
else:
raise TypeError("向量只能被标量除")

def magnitude(self):
# 向量长度
return (self.x ** 2 + self.y ** 2) ** 0.5

def normalize(self):
# 单位向量
mag = self.magnitude()
if mag == 0:
return Vector2D(0, 0)
return self / mag

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

def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"

# 使用示例
v1 = Vector2D(3, 4)
v2 = Vector2D(1, 2)

# 向量加法
v3 = v1 + v2
print(f"v1 + v2 = {v3}") # Vector2D(4, 6)

# 向量减法
v4 = v1 - v2
print(f"v1 - v2 = {v4}") # Vector2D(2, 2)

# 向量点积
dot_product = v1 * v2
print(f"v1 · v2 = {dot_product}") # 11

# 向量缩放
v5 = v1 * 2
print(f"v1 * 2 = {v5}") # Vector2D(6, 8)

# 向量除法
v6 = v1 / 2
print(f"v1 / 2 = {v6}") # Vector2D(1.5, 2)

# 向量长度和归一化
print(f"v1的长度: {v1.magnitude()}") # 5.0
v7 = v1.normalize()
print(f"v1的单位向量: {v7}") # Vector2D(0.6, 0.8)
print(f"单位向量的长度: {v7.magnitude()}") # 1.0
6. 属性访问控制
方法名描述触发方式使用场景
__getattr__(self, name)获取不存在的属性obj.name (name 不存在)处理不存在的属性访问
__setattr__(self, name, value)设置属性obj.name = value拦截所有属性赋值
__delattr__(self, name)删除属性del obj.name拦截属性删除
实际应用场景:属性验证与保护
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
class ProtectedObject:
def __init__(self):
self._data = {} # 存储实际数据
self._protected = ['_data', '_protected'] # 保护的属性列表

def __getattr__(self, name):
# 当访问不存在的属性时调用
if name in self._data:
return self._data[name]

# 记录未知属性访问并返回None
print(f"警告: 尝试访问不存在的属性 '{name}'")
return None

def __setattr__(self, name, value):
# 拦截所有属性赋值
if name.startswith('_'):
# 允许设置内部属性
super().__setattr__(name, value)
return

if name in self._protected:
raise AttributeError(f"'{name}' 是受保护的属性,不能直接赋值")

# 验证并存储数据
if name == 'age' and not isinstance(value, int):
raise TypeError("年龄必须是整数")
elif name == 'age' and (value < 0 or value > 150):
raise ValueError("年龄必须在0-150之间")
elif name == 'email' and '@' not in value:
raise ValueError("无效的电子邮件格式")

# 存储验证后的数据
self._data[name] = value
print(f"属性 '{name}' 设置为 {value}")

def __delattr__(self, name):
# 拦截属性删除
if name in self._protected:
raise AttributeError(f"'{name}' 是受保护的属性,不能删除")

if name in self._data:
del self._data[name]
print(f"已删除属性 '{name}'")
else:
raise AttributeError(f"'{name}' 属性不存在,无法删除")

def __str__(self):
return f"ProtectedObject(attributes={list(self._data.keys())})"

# 使用示例
person = ProtectedObject()

# 设置属性(经过验证)
try:

person.age = 30 # 通过验证
person.email = "zhangsan@example.com" # 通过验证

person.age = "三十" # 类型错误
except (TypeError, ValueError) as e:
print(f"错误: {e}")

# 尝试访问不存在的属性
print(person.address) # 返回None并显示警告

# 尝试删除属性
try:
del person.name # 正常删除
del person._data # 尝试删除受保护属性
except AttributeError as e:
print(f"错误: {e}")

print(person) # 显示当前属性
7. 上下文管理(with 语句)
方法名描述触发方式使用场景
__enter__(self)进入上下文with obj as x:资源获取,连接建立等
__exit__(self, exc_type, exc_val, exc_tb)退出上下文with 块结束资源释放,异常处理等
实际应用场景:数据库连接管理
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
class DatabaseConnection:
def __init__(self, host, user, password, database):
self.host = host
self.user = user
self.password = password
self.database = database
self.connection = None
self.transaction_active = False

def __enter__(self):
# 进入上下文时建立连接
print(f"连接到数据库 {self.host}/{self.database}...")
self.connection = f"Connection to {self.host}/{self.database} as {self.user}"
return self

def __exit__(self, exc_type, exc_val, exc_tb):
# 退出上下文时处理异常并关闭连接
if exc_type is not None:
# 发生异常时回滚事务
if self.transaction_active:
print(f"发生异常: {exc_val},回滚事务")
self.rollback()
print(f"异常信息: {exc_type.__name__}: {exc_val}")
else:
# 没有异常时提交事务
if self.transaction_active:
print("提交事务")
self.commit()

print(f"关闭数据库连接: {self.host}")
self.connection = None
# 返回False表示不抑制异常,让异常继续传播
# 返回True表示抑制异常,异常不会传播到with语句之外
return False # 不抑制异常

def begin_transaction(self):
if not self.connection:
raise RuntimeError("数据库未连接")
print("开始事务")
self.transaction_active = True

def execute(self, sql):
if not self.connection:
raise RuntimeError("数据库未连接")
print(f"执行SQL: {sql}")
# 模拟SQL错误
if "ERROR" in sql:
print("SQL语法错误")
raise ValueError("SQL语法错误")
return f"执行结果: {sql}"

def commit(self):
if not self.connection:
raise RuntimeError("数据库未连接")
print("提交事务")
self.transaction_active = False

def rollback(self):
if not self.connection:
raise RuntimeError("数据库未连接")
print("回滚事务")
self.transaction_active = False


# 使用示例 - 正常情况
print("=== 正常执行 ===")
with DatabaseConnection("localhost", "admin", "password", "mydb") as db:
db.begin_transaction()
db.execute("SELECT * FROM users")
db.execute("UPDATE users SET active = TRUE")
# 自动提交事务并关闭连接

# 使用示例 - 异常情况
print("\n=== 有异常情况 ===")
try:
with DatabaseConnection("localhost", "admin", "password", "mydb") as db:
db.begin_transaction()
db.execute("SELECT * FROM users")
db.execute("UPDATE users SET ERROR = TRUE") # 这会触发异常
print("这一行不会执行") # 由于异常,这行不会执行
except ValueError as e:
print(f"捕获到异常: {e}")
8. 描述符与属性装饰器
描述符协议方法
方法描述触发条件使用场景
__get__(self, instance, owner)获取属性值访问属性时触发自定义属性访问行为
__set__(self, instance, value)设置属性值属性赋值时触发验证或转换属性值
__delete__(self, instance)删除属性删除属性时触发自定义属性删除行为
实际应用场景:数据验证描述符
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
class Person:
name = StringValidator(min_length=2, max_length=50)
age = NumberValidator(min_value=0, max_value=150, type_=int)
email = StringValidator(pattern=r'^[\w.-]+@[\w.-]+\.\w+$')

def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email

def __str__(self):
return f"Person(name='{self.name}', age={self.age}, email='{self.email}')"


# 使用示例
try:
# 创建有效的Person对象
p1 = Person("张三", 30, "zhangsan@example.com")
print(p1)

# 尝试设置无效的值
p1.name = "李" # 名字太短
except ValueError as e:
print(f"验证错误: {e}")

try:
p1.age = "三十" # 类型错误
except TypeError as e:
print(f"类型错误: {e}")

try:
p1.email = "invalid-email" # 无效的邮箱格式
except ValueError as e:
print(f"验证错误: {e}")

# 设置有效值
p1.name = "李四"
p1.age = 25
p1.email = "lisi@example.com"
print(f"更新后: {p1}")
属性装饰器(@property)实际应用

属性装饰器是 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
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class Student:
def __init__(self, name, student_id):
self._name = name
self._student_id = student_id
self._scores = {} # 课程 -> 分数的映射
self._attendance = {} # 日期 -> 出勤状态的映射

@property
def name(self):
"""学生姓名,只读属性"""
return self._name

@property
def student_id(self):
"""学生ID,只读属性"""
return self._student_id

@property
def scores(self):
"""获取成绩副本,防止外部直接修改"""
return self._scores.copy()

def add_score(self, course, score):
"""添加或更新课程成绩"""
if not isinstance(score, (int, float)):
raise TypeError("成绩必须是数字")
if not 0 <= score <= 100:
raise ValueError("成绩必须在0到100之间")
self._scores[course] = score

@property
def average_score(self):
"""计算平均成绩"""
if not self._scores:
return 0
return sum(self._scores.values()) / len(self._scores)

@property
def grade_point(self):
"""计算绩点(GPA)"""
if not self._scores:
return 0

# 成绩到绩点的映射
grade_map = {
'A': 4.0, # 90-100
'B': 3.0, # 80-89
'C': 2.0, # 70-79
'D': 1.0, # 60-69
'F': 0.0 # 0-59
}

# 计算每门课的绩点
points = []
for score in self._scores.values():
if score >= 90:
points.append(grade_map['A'])
elif score >= 80:
points.append(grade_map['B'])
elif score >= 70:
points.append(grade_map['C'])
elif score >= 60:
points.append(grade_map['D'])
else:
points.append(grade_map['F'])

return sum(points) / len(points)

def record_attendance(self, date, status):
"""记录出勤状态"""
if status not in ['present', 'absent', 'late']:
raise ValueError("出勤状态必须是 'present', 'absent' 或 'late'")
self._attendance[date] = status

@property
def attendance_rate(self):
"""计算出勤率"""
if not self._attendance:
return 1.0 # 默认100%

present_count = sum(1 for status in self._attendance.values() if status == 'present')
late_count = sum(1 for status in self._attendance.values() if status == 'late')

# 迟到计为0.5次出勤
return (present_count + 0.5 * late_count) / len(self._attendance)

def __str__(self):
return f"学生: {self._name} (ID: {self._student_id}), 平均分: {self.average_score:.1f}, GPA: {self.grade_point:.2f}"


# 使用示例
student = Student("王小明", "S12345")

# 添加课程成绩
student.add_score("数学", 92)
student.add_score("物理", 85)
student.add_score("化学", 78)
student.add_score("语文", 88)

# 记录出勤
student.record_attendance("2025-04-01", "present")
student.record_attendance("2025-04-02", "present")
student.record_attendance("2025-04-03", "late")
student.record_attendance("2025-04-04", "absent")

# 访问属性
print(student)
print(f"学生姓名: {student.name}")
print(f"学生ID: {student.student_id}")
print(f"课程成绩: {student.scores}")
print(f"平均分: {student.average_score:.1f}")
print(f"GPA: {student.grade_point:.2f}")
print(f"出勤率: {student.attendance_rate:.1%}")

# 尝试修改只读属性
try:
student.name = "张三" # 这会失败,因为name是只读属性
except AttributeError as e:
print(f"错误: {e}")

# 显示所有课程及分数
print("\n所有课程成绩:")
for course, score in student.scores.items():
grade = ""
if score >= 90: grade = "A"
elif score >= 80: grade = "B"
elif score >= 70: grade = "C"
elif score >= 60: grade = "D"
else: grade = "F"

print(f" {course}: {score} ({grade})")
10. 常见特殊方法使用陷阱与最佳实践
陷阱与注意事项
  1. __init__ vs __new__
    • 陷阱:混淆这两个方法的用途和调用顺序。
    • 最佳实践:除非实现元类或单例模式,一般只需重写 __init__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 错误示例
class BadVector:
def __init__(self, x, y):
self.x = x
self.y = y

def __add__(self, other):
# 错误:返回元组而非同类型的对象
return (self.x + other.x, self.y + other.y)

# 正确示例
class GoodVector:
def __init__(self, x, y):
self.x = x
self.y = y

def __add__(self, other):
# 正确:返回同类型对象
return GoodVector(self.x + other.x, self.y + other.y)
  1. __eq____hash__
    • 陷阱:实现 __eq__ 而不实现 __hash__,导致对象无法用作字典键。
    • 最佳实践:如果重写 __eq__,也应该重写 __hash__ 或设置 __hash__ = None 使对象不可哈希。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def __eq__(self, other):
if not isinstance(other, Person):
return False
return self.name == other.name and self.age == other.age

# 如果不实现__hash__,对象将不可哈希
# 选项1:使对象不可哈希
__hash__ = None

# 选项2:实现兼容的哈希方法
# def __hash__(self):
# return hash((self.name, self.age))
  1. 递归问题
    • 陷阱:在特殊方法中无限递归调用自身。
    • 最佳实践:避免在特殊方法中使用可能触发同一方法的操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class BadString:
def __init__(self, value):
self.value = value

def __str__(self):
# 错误:这会导致无限递归
return f"BadString: {self}" # 递归调用__str__

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

def __str__(self):
# 正确:使用原始值
return f"GoodString: {self.value}"
  1. __bool____len__
    • 陷阱:不了解这两个方法的优先级和用途。
    • 最佳实践__bool__ 优先于 __len__;对于集合类,实现 __len__;对于真假逻辑,实现 __bool__
1
2
3
4
5
6
7
8
9
10
11
class DataCollection:
def __init__(self, data=None):
self.data = data or []

def __len__(self):
# 返回集合长度
return len(self.data)

def __bool__(self):
# 特殊逻辑:即使长度为0,如果有特定标记也认为是True
return bool(self.data) or hasattr(self, 'special_flag')
  1. 反向方法失效
    • 陷阱:忽略实现反向操作方法(如 __radd__)。
    • 最佳实践:为算术操作同时实现普通方法和反向方法。
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
class SafeNumber:
def __init__(self, value):
self.value = value

def __add__(self, other):
# 普通加法: self + other
if isinstance(other, (int, float)):
return SafeNumber(self.value + other)
if isinstance(other, SafeNumber):
return SafeNumber(self.value + other.value)
return NotImplemented # 告诉Python尝试反向操作

def __radd__(self, other):
# 反向加法: other + self
if isinstance(other, (int, float)):
return SafeNumber(other + self.value)
return NotImplemented

def __str__(self):
return f"SafeNumber({self.value})"


# 测试
num = SafeNumber(5)
print(num + 3) # SafeNumber + int
print(3 + num) # int + SafeNumber (触发__radd__)
11. 总结:特殊方法的选择指南
需求推荐实现的特殊方法注意事项
初始化和配置对象__init__保持简单,仅做必要设置
自定义对象创建__new__谨慎使用,主要用于元类和单例
字符串表示__str__, __repr____repr__ 应提供准确代表对象的信息
集合或容器行为__len__, __getitem__, __setitem__, __delitem__, __iter__保持与 Python 内置类型一致的行为
对象比较和哈希__eq__, __lt__, __gt__, __hash__注意 __eq____hash__ 的兼容性
算术运算__add__, __sub__, __mul__, 以及对应的反向方法对不兼容类型返回 NotImplemented
属性访问控制__getattr__, __setattr__, __getattribute__注意避免递归调用
资源管理__enter__, __exit__确保资源正确释放,处理异常
描述符协议__get__, __set__, __delete__用于创建可重用的属性行为

对象与类的高级概念

深入理解实例、类和元类关系

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
# 定义一个普通类
class MyClass:
def __init__(self, value):
self.value = value

def display(self):
return f"Value: {self.value}"

# 创建类的实例
obj = MyClass(42)

# 打印并验证三个层次的关系
print("=== 三个层次的关系 ===")
print(f"obj.__class__ is MyClass: {obj.__class__ is MyClass}") # True - 实例的类是MyClass
print(f"MyClass.__class__ is type: {MyClass.__class__ is type}") # True - 类的类是type(元类)
print(f"type.__class__ is type: {type.__class__ is type}") # True - type是自己的元类

# 打印并验证继承关系
print("\n=== 继承关系 ===")
print(f"issubclass(MyClass, object): {issubclass(MyClass, object)}") # True - 所有类都继承自object
print(f"issubclass(type, object): {issubclass(type, object)}") # True - type也继承自object

# 打印并验证元类关系
print("\n=== 元类关系 ===")
print(f"type(obj) is MyClass: {type(obj) is MyClass}") # True - 实例的类型是MyClass
print(f"type(MyClass) is type: {type(MyClass) is type}") # True - 类的类型是type
print(f"type(type) is type: {type(type) is type}") # True - type的类型也是type



# 展示类型检查
print("\n=== 类型检查 ===")
print(f"isinstance(obj, MyClass): {isinstance(obj, MyClass)}") # True - obj是MyClass的实例
print(f"isinstance(MyClass, type): {isinstance(MyClass, type)}") # True - MyClass是type的实例
print(f"isinstance(type, type): {isinstance(type, type)}") # True - type是自己的实例

属性查找的复杂情况

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
class Mytype(type):
'''实现自定义元类,并定义元类方法'''
attr = '元类属性'

def meta_method(cls):
return "调用元方法 " + cls.__name__


class Parent:
attr = '父类属性'

def parent_method(self):
return "调用父类方法"


class Child(Parent, metaclass=Mytype):
attr = '子类属性'

def child_method(self):
return "调用子类方法"

obj = Child()
## 对象属性查找:
print(obj.attr) # '子类属性'(首先在实例中查找,然后是类,然后是父类)

## 类属性查找:
print(Child.attr) # '子类属性'(首先在类中查找,然后是父类,然后是元类)

## 方法查找:
print(obj.child_method()) # '调用子类方法'
print(obj.parent_method()) # '调用父类方法'(从父类继承)

## 元类方法:
print(Child.meta_method()) # '调用元方法 Child'(从元类获取)

## 删除子类属性,回退到父类:
del Child.attr
print(Child.attr) # '父类属性'

## 删除父类属性,回退到元类:
del Parent.attr
print(Child.attr) # '元类属性'

内存管理与对象生命周期

对象创建和销毁的完整过程

阶段触发方法描述
创建__new__分配内存并创建实例
初始化__init__初始化实例属性
表示__repr__, __str__定义对象的字符串表示
使用各种操作符和方法对象的正常使用期
销毁__del__对象不再被引用时的清理
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
class ResourceManager:
def __new__(cls, *args, **kwargs):
print("1. __new__: 分配内存")
instance = super().__new__(cls)
return instance

def __init__(self, name, value):
print("2. __init__: 初始化对象")
self.name = name
self.value = value
self._resource = None

def __repr__(self):
return f"ResourceManager(name='{self.name}', value={self.value})"

def __str__(self):
return f"Resource '{self.name}' with value {self.value}"

def acquire(self):
print(f"Acquiring resource: {self.name}")
self._resource = f"RESOURCE_{self.name}_{id(self)}"
return self._resource

def release(self):
if self._resource:
print(f"Explicitly releasing: {self._resource}")
self._resource = None

def __del__(self):
print(f"3. __del__: 清理 '{self.name}'")
if self._resource:
print(f"Warning: 资源 {self._resource} 在销毁时仍未释放")
self.release()

## 创建和使用对象
rm = ResourceManager("db_connection", 42)
print(rm) # 调用__str__
print(repr(rm)) # 调用__repr__

## 使用资源
res = rm.acquire()
print(f"使用资源: {res}")

## 两种清理方式:
## 1. 显式释放 (推荐)
rm.release()

## 2. 依赖垃圾回收 (不推荐)
## rm = None # 删除引用,触发垃圾回收

引用计数和循环引用

Python 的内存管理主要依靠引用计数

  • 每个对象都有一个引用计数器,记录有多少个引用指向该对象
  • 当引用计数降为 0 时,对象会被自动销毁,内存被释放
  • sys.getrefcount() 函数可以查看对象的引用计数
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
import sys
import gc


class Node:
def __init__(self, name):
self.name = name
self.children = []
print(f"创建节点: {name}")

def add_child(self, child):
self.children.append(child)

def __del__(self):
print(f"删除节点: {self.name}")


## 创建节点
node1 = Node("A")
node2 = Node("B")
node3 = Node("C")

## 创建循环引用
node1.add_child(node2)
node2.add_child(node3)
node3.add_child(node1) # 循环引用!
# +-------+
# | v
# node1(A) → node2(B)
# ^ |
# | v
# +--- node3(C)


## 创建弱引用或保存ID以便后续检查
import weakref

node_refs = [weakref.ref(node1), weakref.ref(node2), weakref.ref(node3)]
node_ids = [id(node1), id(node2), id(node3)]

## 删除外部引用
print("删除对节点的外部引用...")
del node1
del node2
del node3

print("垃圾回收前...")
# 检查对象是否还存在
for i, ref in enumerate(node_refs):
obj = ref()
if obj is not None:
print(f"节点{chr(65 + i)}仍然存在于内存中")

print("手动触发垃圾回收...")
gc.collect() # 强制运行垃圾回收
print("垃圾回收后...")

# 再次检查对象是否存在
for i, ref in enumerate(node_refs):
obj = ref()
if obj is None:
print(f"节点{chr(65 + i)}已被回收") # 不可能进这个判断,因为存在计数引用和循环引用问题

高级常用设计模式

单例模式

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
class Singleton:
_instance = None

def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

## ======================================或使用元类实现========================================================
class SingletonMeta(type):
_instance = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instance: # 若cls没有实例化过,则新建一个实例
cls._instance[cls] = super().__call__(*args, **kwargs)
return cls._instance[cls] # 返回cls的实例

class Database(metaclass=SingletonMeta):
def __init__(self, connection_string=None):
if connection_string:
self.connection_string = connection_string

db1 = Database('mysql://user:password@localhost/database')
db2 = Database('sqlite:///database.db')

print(db1 is db2) # True


## ======================================或使用装饰器实现========================================================
def Singleton(cls):
"""
单例模式装饰器

参数 cls 是被装饰的类,当使用 @Singleton 语法时,Python 解释器会自动将
被装饰的类作为第一个参数传递给装饰器函数。

装饰器工作原理:
1. 当使用 @Singleton 装饰 MyClass 时,Python 实际上执行了 MyClass = Singleton(MyClass)
2. Singleton 函数接收 MyClass 作为 cls 参数
3. Singleton 返回 get_instance 函数,该函数替代了原始的 MyClass
4. 当我们调用 MyClass() 时,实际上调用的是 get_instance()
5. get_instance 函数内部可以访问 cls,因为它是一个闭包,能够访问外部函数的变量
"""
_instance = {} # 用于存储每个类的单例实例

def get_instance(*args, **kwargs):
if cls not in _instance:
# 如果该类的实例不存在,则创建一个新实例
_instance[cls] = cls(*args, **kwargs)
# 返回该类的单例实例
return _instance[cls]

return get_instance

@Singleton # 这里相当于 MyClass = Singleton(MyClass)
class MyClass:
def __init__(self, name):
self.name = name


class1 = MyClass("John")
class2 = MyClass("Mary")
print(class1.name) # John
print(class2.name) # John

懒汉式单例模式的使用场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# singleton.py
class Singleton:
def __init__(self):
self.value = "我是单例的"

def some_method(self):
return self.value

# 在模块级别创建实例
instance = Singleton()

# 其他文件中使用
from singleton import instance

print(instance.some_method()) # 输出: 我是单例的

懒汉式单例模式在以下情况特别适用:

  1. 资源密集型对象:当创建实例需要消耗大量资源(如数据库连接、文件系统操作等),懒汉式可以推迟实例化,直到真正需要时才创建。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class DatabaseConnection:
_instance = None

def __new__(cls, *args, **kwargs):
if cls._instance is None:
print("创建新的数据库连接...")
cls._instance = super().__new__(cls)
return cls._instance

def __init__(self, connection_string=None):
# 初始化只在首次创建时执行
if not hasattr(self, 'initialized'):
self.connection_string = connection_string
self.initialized = True
print(f"连接到: {connection_string}")
  1. 配置管理器:应用程序的配置管理器通常是单例的,且配置加载可能很耗时。
1
2
3
4
5
6
7
8
9
10
11
12
class ConfigManager:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.load_config()
return cls._instance

def load_config(self):
print("从文件加载配置...")
# 模拟加载配置文件的耗时操作
  1. 应用程序状态管理:当需要全局访问应用状态但又不希望在程序启动时立即初始化时。

元类实现方式适合需要更灵活控制实例化过程的场景,特别是需要根据参数动态决定实例化行为的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 日志管理器单例,可以根据不同的日志级别创建不同配置的单例
class LoggerMeta(type):
_instances = {}

def __call__(cls, log_level=None):
key = (cls, log_level) # 组合键,针对每个日志级别创建唯一实例
if key not in cls._instances:
cls._instances[key] = super().__call__(log_level)
return cls._instances[key]

class Logger(metaclass=LoggerMeta):
def __init__(self, log_level):
self.log_level = log_level
print(f"创建{log_level}级别的日志记录器")

装饰器实现方式适合需要将现有类转换为单例而不修改其内部代码的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 第三方库类转换为单例
def singleton(cls):
instances = {}

def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]

return get_instance

# 将外部库中的类转换为单例
@singleton
class ThirdPartyClass:
pass
饿汉式单例模式的使用场景

饿汉式单例模式最适合以下场景:

  1. 必然会使用的核心服务:如应用程序中必定会使用的服务组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# app_services.py
class EventBus:
def __init__(self):
print("初始化事件总线...")
self.listeners = {}

def subscribe(self, event, callback):
if event not in self.listeners:
self.listeners[event] = []
self.listeners[event].append(callback)

def publish(self, event, data):
if event in self.listeners:
for callback in self.listeners[event]:
callback(data)

# 模块级别立即创建实例
event_bus = EventBus()

# 在其他文件中使用
from app_services import event_bus
  1. 应用程序设置和常量:包含应用配置的单例对象。
1
2
3
4
5
6
7
8
9
10
# settings.py
class AppSettings:
def __init__(self):
print("加载应用程序设置...")
self.debug_mode = False
self.api_url = "https://api.example.com"
self.max_connections = 100

# 立即创建全局设置对象
settings = AppSettings()
  1. 线程安全要求高的场景:饿汉式单例天然线程安全,适合多线程环境。
1
2
3
4
5
6
7
8
9
10
11
12
13
# thread_pool.py
class ThreadPoolManager:
def __init__(self):
print("初始化线程池...")
self.max_workers = 10
# 线程池初始化代码

def submit_task(self, task):
print(f"提交任务到线程池: {task}")
# 任务提交逻辑

# 程序启动时即创建线程池
thread_pool = ThreadPoolManager()

工厂模式

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
from abc import ABC, abstractmethod


## 抽象产品
class Animal(ABC):
@abstractmethod
def speak(self):
pass


## 具体产品
class Dog(Animal):
def speak(self):
return "Woof!"


class Cat(Animal):
def speak(self):
return "Meow!"


class Duck(Animal):
def speak(self):
return "Quack!"


## 工厂类
class AnimalFactory:
@staticmethod
def create_animal(animal_type):
match animal_type.lower():
case "dog":
return Dog()
case "cat":
return Cat()
case "duck":
return Duck()
case _:
raise ValueError(f"Invalid animal type: {animal_type}")
## 使用工厂
factory = AnimalFactory()
animals = [
factory.create_animal("dog"),
factory.create_animal("cat"),
factory.create_animal("duck")
]
for animal in animals:
print(animal.speak()) # Output: Woof! Meow! Quack!

工厂模式在以下场景中特别有用:

  1. 插件架构:当系统需要动态加载和使用不同的插件或扩展时。
1
2
3
4
5
6
7
├── app.py                 # 主应用程序,演示如何使用插件
├── plugin_interface.py # 定义插件接口
├── plugin_factory.py # 工厂方法模式的实现
├── plugins/ # 插件目录
│ ├── __init__.py # 插件包初始化文件
│ ├── image_processor.py # 图片处理插件
│ └── text_analyzer.py # 文本分析插件

首先我们定义一个插件接口 plugin_interface.py

1
2
3
4
5
6
7
8
9
10
from abc import ABC, abstractmethod

class PluginInterface(ABC):
@abstractmethod
def process(self, data):
raise NotImplementedError("插件必须实现process方法")

@abstractmethod
def get_name(self):
raise NotImplementedError("插件必须实现get_name方法")

依照插件接口新建目录文件夹 plugins

1
2
3
4
5
6
7
8
# 使plugins目录成为一个Python包
# 这允许我们使用 from plugins import xxx 的导入方式

# 列出所有可用的插件模块名,方便外部遍历
available_plugins = [
"image_processor",
"text_analyzer"
]

图片处理插件 image_processor

1
2
3
4
5
6
7
8
9
10
11
from plugin_interface import PluginInterface

class ImageProcessorPlugin(PluginInterface):
def process(self, image_data):
print(f"处理图片数据: {image_data[:30]}...")
# 实际项目中这里会有图片处理逻辑
# 例如:调整大小、滤镜、裁剪等
return f"已处理的图片: {image_data[:10]}..."

def get_name(self):
return "图片处理插件"

文本处理插件 text_analyzer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from plugin_interface import PluginInterface

class TextAnalyzerPlugin(PluginInterface):
def process(self, text):
word_count = len(text.split())
print(f"分析文本: 共{word_count}个单词")

# 实际项目中这里会有更复杂的文本分析逻辑
# 例如:情感分析、关键词提取、语法检查等
return {
"word_count": word_count,
"sentiment": "positive" if "good" in text.lower() else "neutral",
"characters": len(text),
"has_question": "?" in text
}

def get_name(self):
return "文本分析插件"

重点在我们的 plugin_factory.py

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
from abc import ABC, abstractmethod
from plugin_interface import PluginInterface

class PluginCreator(ABC):
"""
插件创建者的抽象基类 - 工厂方法模式的核心
"""
@abstractmethod
def create_plugin(self) -> PluginInterface:
pass

def get_plugin_info(self):
plugin = self.create_plugin()
return {
"name": plugin.get_name(),
"type": self.__class__.__name__
}


class DynamicPluginCreator(PluginCreator):
def __init__(self, plugin_name):
self.plugin_name = plugin_name

def create_plugin(self) -> PluginInterface:
try:
# 动态导入插件模块
module = __import__(f"plugins.{self.plugin_name}", fromlist=["plugins"])
# 获取插件类(约定:插件类名为模块名首字母大写+Plugin)
plugin_class_name = f"{self.plugin_name.capitalize()}Plugin"
plugin_class = getattr(module, plugin_class_name)
# 创建并返回插件实例
return plugin_class()
except (ImportError, AttributeError) as e:
raise ValueError(f"Plugin {self.plugin_name} not found: {str(e)}")


class ImagePluginCreator(PluginCreator):
def create_plugin(self) -> PluginInterface:
# 这里直接导入具体类,不再使用动态导入
from plugins.image_processor import ImageProcessorPlugin
return ImageProcessorPlugin()


class TextPluginCreator(PluginCreator):
def create_plugin(self) -> PluginInterface:
# 这里直接导入具体类,不再使用动态导入
from plugins.text_analyzer import TextAnalyzerPlugin
return TextAnalyzerPlugin()


# 工厂注册表,用于根据名称获取对应的工厂
class PluginFactory:
# 插件工厂注册表
_factories = {
"image_processor": ImagePluginCreator,
"text_analyzer": TextPluginCreator
}

@classmethod
def get_factory(cls, plugin_name):
if plugin_name in cls._factories:
return cls._factories[plugin_name]()
else:
# 如果没有具体工厂,则使用动态加载工厂
return DynamicPluginCreator(plugin_name)

@classmethod
def register_factory(cls, plugin_name, factory_class):
cls._factories[plugin_name] = factory_class

使用我们的插件工厂与插件 app.py

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
from plugin_factory import PluginFactory
from plugins import available_plugins

def main():
"""
主应用程序,演示如何使用插件系统
"""
print("=== 插件系统演示 - 工厂方法模式 ===")

# 存储已加载的插件
loaded_plugins = {}
plugin_infos = []

# 列出所有可用插件
print(f"\n可用插件列表: {available_plugins}")

# 使用工厂方法模式加载所有可用插件
for plugin_name in available_plugins:
try:
# 获取插件创建者
plugin_creator = PluginFactory.get_factory(plugin_name)
# 获取插件信息
plugin_info = plugin_creator.get_plugin_info()
plugin_infos.append(plugin_info)
# 创建插件实例
plugin = plugin_creator.create_plugin()
loaded_plugins[plugin_name] = plugin
print(f"已加载插件: {plugin.get_name()} (类型: {plugin_info['type']})")
except ValueError as e:
print(f"加载插件 {plugin_name} 失败: {e}")

print("\n=== 使用图片处理插件 ===")
# 使用图片处理插件
if "image_processor" in loaded_plugins:
image_plugin = loaded_plugins["image_processor"]
# 模拟图片数据
image_data = "iVBORw0KGgoAAAANSUhEUgAA..."
result = image_plugin.process(image_data)
print(f"处理结果: {result}")
else:
print("图片处理插件未加载")

print("\n=== 使用文本分析插件 ===")
# 使用文本分析插件
if "text_analyzer" in loaded_plugins:
text_plugin = loaded_plugins["text_analyzer"]
# 要分析的文本
text_data = "This is a good example of factory method pattern. Isn't it?"
result = text_plugin.process(text_data)
print("分析结果:")
for key, value in result.items():
print(f" - {key}: {value}")
else:
print("文本分析插件未加载")

print("\n=== 演示自定义插件创建 ===")
# 演示自定义插件创建
try:
# 直接使用特定的创建者
from plugin_factory import TextPluginCreator
text_creator = TextPluginCreator()
custom_text_plugin = text_creator.create_plugin()
print(f"通过具体工厂直接创建插件: {custom_text_plugin.get_name()}")

# 动态注册一个新工厂
class CustomPluginCreator(TextPluginCreator):
def get_plugin_info(self):
info = super().get_plugin_info()
info["custom"] = True
return info

# 注册自定义工厂
PluginFactory.register_factory("custom_text", CustomPluginCreator)
custom_creator = PluginFactory.get_factory("custom_text")
print(f"自定义工厂信息: {custom_creator.get_plugin_info()}")

except Exception as e:
print(f"自定义插件创建失败: {e}")

print("\n=== 演示完成 ===")

if __name__ == "__main__":
main()
  1. 跨平台应用程序:根据不同操作系统创建适当的组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import platform

class UIFactory:
@staticmethod
def create_button(label):
system = platform.system()
if system == "Windows":
return WindowsButton(label)
elif system == "Darwin": # macOS
return MacButton(label)
elif system == "Linux":
return LinuxButton(label)
else:
return GenericButton(label)

# 使用场景
login_button = UIFactory.create_button("Login")
login_button.render() # 根据当前操作系统渲染适当的按钮
  1. 数据库访问层:根据配置选择不同的数据库实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DatabaseFactory:
@staticmethod
def get_database(db_type, connection_string):
if db_type.lower() == "mysql":
return MySQLDatabase(connection_string)
elif db_type.lower() == "postgresql":
return PostgreSQLDatabase(connection_string)
elif db_type.lower() == "mongodb":
return MongoDBDatabase(connection_string)
else:
raise ValueError(f"Unsupported database type: {db_type}")

# 使用场景 - 从配置创建适当的数据库连接
config = {"db_type": "postgresql", "connection": "postgresql://user:pass@localhost/mydb"}
db = DatabaseFactory.get_database(config["db_type"], config["connection"])
users = db.query("SELECT * FROM users")
  1. 测试替身创建:在测试环境中替换真实依赖项。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class PaymentProcessorFactory:
@staticmethod
def create(environment="production"):
if environment == "production":
return StripePaymentProcessor()
elif environment == "sandbox":
return SandboxPaymentProcessor()
elif environment == "test":
return MockPaymentProcessor()
else:
raise ValueError(f"Unknown environment: {environment}")

# 测试场景
def test_payment_flow():
processor = PaymentProcessorFactory.create("test")
result = processor.process_payment(100.00, "4242424242424242")
assert result.success == True

观察者模式

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
class Subject:
def __init__(self):
self._observers = [] # 观察者列表
self._state = None # 当前状态

def attach(self,observer):
""" 注册观察者 """
if observer not in self._observers:
self._observers.append(observer)
print(f"{observer} 注册成功")

def detach(self,observer):
""" 注销观察者 """
try:
self._observers.remove(observer)
print(f"{observer} 注销成功")
except ValueError:
print(f"{observer} 不在观察者列表中")

def notify(self):
""" 通知观察者 """
for observer in self._observers:
observer.update(self)

# 定义Getter和Setter方法
@property
def state(self):
return self._state

@state.setter
def state(self, value):
self._state = value
self.notify()


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

def update(self,subject):
print(f"名称为{self.name}的观察者收到{subject.state}的更新")


## 使用观察者模式
subject = Subject()
observer1 = Observer("观察者1")
observer2 = Observer("观察者2")
subject.attach(observer1) # 注册观察者
subject.attach(observer2) # 注册观察者
subject.state = "状态1" # 通知观察者
subject.state = "状态2" # 通知观察者
subject.detach(observer1)

观察者模式在以下场景中非常有效:

  1. 事件驱动的系统:如 GUI 应用程序,响应用户操作。
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLabel, QLineEdit, QMessageBox


# 主题(Subject) - 被观察者
class ButtonSubject:
def __init__(self):
self._observers = []

def attach(self, observer):
"""添加观察者"""
if observer not in self._observers:
self._observers.append(observer)

def detach(self, observer):
"""移除观察者"""
if observer in self._observers:
self._observers.remove(observer)

def notify(self, button, username=None, password=None):
"""通知所有观察者"""
for observer in self._observers:
observer.update(button, username, password)


# 观察者接口
class Observer:
def update(self, button, username=None, password=None):
"""观察者接收通知后执行的方法"""
pass


# 具体观察者 - 登录处理器
class LoginHandler(Observer):
def __init__(self, status_label):
self.status_label = status_label
# 模拟用户数据库
self.users = {
"admin": "admin123",
"user": "password"
}

def update(self, button, username=None, password=None):
if button.text() == "登录":
self.process_login(username, password)

def process_login(self, username, password):
if not username or not password:
self.status_label.setText("请输入用户名和密码")
return

if username in self.users and self.users[username] == password:
self.status_label.setText(f"登录成功,欢迎 {username}!")
QMessageBox.information(None, "登录成功", f"欢迎回来,{username}!")
else:
self.status_label.setText("用户名或密码错误")
QMessageBox.warning(None, "登录失败", "用户名或密码错误")


# 创建PyQt应用程序
def create_observer_pattern_demo():
app = QApplication(sys.argv)
window = QMainWindow()
window.setWindowTitle("登录示例")
window.setGeometry(100, 100, 400, 250)

# 创建中央部件和布局
central_widget = QWidget()
layout = QVBoxLayout(central_widget)

# 创建用户名和密码输入框
username_label = QLabel("用户名:")
username_input = QLineEdit()
username_input.setPlaceholderText("请输入用户名")

password_label = QLabel("密码:")
password_input = QLineEdit()
password_input.setPlaceholderText("请输入密码")
password_input.setEchoMode(QLineEdit.Password)

# 创建状态标签
status_label = QLabel("请登录...")

# 创建按钮
login_button = QPushButton("登录")

# 创建主题
button_subject = ButtonSubject()

# 创建观察者
login_handler = LoginHandler(status_label)

# 注册观察者
button_subject.attach(login_handler)

# 连接按钮点击事件
login_button.clicked.connect(
lambda: button_subject.notify(
login_button,
username_input.text(),
password_input.text()
)
)

# 添加部件到布局
layout.addWidget(username_label)
layout.addWidget(username_input)
layout.addWidget(password_label)
layout.addWidget(password_input)
layout.addWidget(login_button)
layout.addWidget(status_label)

# 设置中央部件
window.setCentralWidget(central_widget)

# 显示窗口
window.show()

return app, window, button_subject


if __name__ == "__main__":
app, window, subject = create_observer_pattern_demo()
sys.exit(app.exec_())
  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
class NewsPublisher:
def __init__(self):
self._subscribers = []
self._latest_news = None

def attach(self, subscriber):
if subscriber not in self._subscribers:
self._subscribers.append(subscriber)

def detach(self, subscriber):
if subscriber in self._subscribers:
self._subscribers.remove(subscriber)

def notify(self):
for subscriber in self._subscribers:
subscriber.update(self._latest_news)

def add_news(self, news):
self._latest_news = news
self.notify()

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

def update(self, news):
print(f"{self.name} 收到新闻: {news}")

# 使用场景 - 新闻分发系统
publisher = NewsPublisher()

subscriber1 = NewsSubscriber("用户1")
subscriber2 = NewsSubscriber("用户2")

publisher.attach(subscriber1)
publisher.attach(subscriber2)

publisher.add_news("今天是个好日子!") # 所有订阅者都会收到通知
  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
class DataModel:
def __init__(self, value=0):
self._observers = []
self._value = value

def register_observer(self, observer):
self._observers.append(observer)

def notify_observers(self):
for observer in self._observers:
observer.update(self)

@property
def value(self):
return self._value

@value.setter
def value(self, new_value):
if self._value != new_value:
self._value = new_value
self.notify_observers()

class ChartView:
def update(self, model):
print(f"图表更新为新值: {model.value}")

class TableView:
def update(self, model):
print(f"表格更新为新值: {model.value}")

# 使用场景 - MVC架构中的数据更新
model = DataModel(10)
chart = ChartView()
table = TableView()

model.register_observer(chart)
model.register_observer(table)

model.value = 20 # 自动通知所有视图更新
  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
class ServerMonitor:
def __init__(self):
self._observers = []
self._status = "正常"

def add_observer(self, observer):
self._observers.append(observer)

def set_status(self, status):
old_status = self._status
self._status = status
if old_status != status:
self.notify_observers()

def notify_observers(self):
for observer in self._observers:
observer.update(self._status)

class EmailAlert:
def update(self, status):
print(f"发送邮件警报: 服务器状态变为 {status}")

class SMSAlert:
def update(self, status):
print(f"发送短信警报: 服务器状态变为 {status}")

# 使用场景 - 服务器监控系统
monitor = ServerMonitor()
email_alert = EmailAlert()
sms_alert = SMSAlert()

monitor.add_observer(email_alert)
monitor.add_observer(sms_alert)

monitor.set_status("严重错误") # 触发所有警报

代理模式

代理模式是一种结构型设计模式,它为其他对象提供了一种代理以控制对这些对象的访问;可以把代理模式理解为一个“守门员”,在客户(Client)和真实对象(RealSubject)之间充当中介,控制请求的传递和处理—— 就像你想看一部付费电影时,需要先通过一个“守门员”确认你是否支付了费用,这名守门员就是代理,他会在你访问电影资源之前先做一些检查,确保你有权利观看。

接下来,我们通过代码展示如何在 Python 中实现一些常用的代理模式——静态代理、动态代理、保护代理、虚拟代理。

1.静态代理实现

静态代理在编译时确定代理行为,非常适合结构固定的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class RealSubject:
def request(self):
print("真实对象,请求被调用")

class Proxy:
def __init__(self,real_subject:RealSubject):
self.real_subject = real_subject

def request(self):
print("代理:在真实对象之前检查权限")
# 代理控制访问
self.real_subject.request()
print("代理:在真实对象之后记录日志")

# 使用代理
real_subject = RealSubject()
proxy = Proxy(real_subject)
proxy.request()
2.动态代理实现

动态代理可以在运行时动态地为对象添加功能,而不需要在编译时确定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
from functools import wraps

def timing_proxy(func):
'''定义一个装饰器,打印函数执行时间'''
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 执行时间:{end_time - start_time:.4f} 秒。")
return result
return wrapper

class SomeService:
@timing_proxy
def perform_task(self):
print("正在执行任务...")
time.sleep(2)
# 使用动态代理
service = SomeService()
service.perform_task()
3.保护代理实现

保护代理是一种常见的应用场景,用于控制对对象的访问权限,下面的例子展示了如何实现一个简单的保护代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class RealSubject:
def request(self,user_role):
print(f"{user_role} 访问 执行敏感操作")

class ProtectionProxy:
def __init__(self,real_subject:RealSubject):
self.real_subject = real_subject

def request(self,user_role):
if user_role == "admin":
self.real_subject.request(user_role)
else:
raise Exception("权限不足")

# 使用保护代理
real_subject = RealSubject()
proxy = ProtectionProxy(real_subject)
proxy.request("admin")
proxy.request("user") # 权限不足
4.虚拟代理实现

虚拟代理用于控制昂贵资源的延迟加载,只有在真正需要时才创建对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HeavyResource:
def load(self):
print("加载大量数据...")

class VirtualProxy:
def __init__(self):
self._real_resource = None

def request(self):
if self._real_resource is None:
self._real_resource = HeavyResource()
self._real_resource.load()
print("资源已加载,执行操作。")
# 使用虚拟代理
proxy = VirtualProxy()
proxy.request() # 首次调用会加载数据
proxy.request() # 第二次调用不会再次加载数据

SOLID 原则

SOLID 原则概述

SOLID 是面向对象设计的五个核心原则的首字母缩写,由罗伯特·C·马丁(Robert C. Martin)在 21 世纪早期提出。这些原则旨在帮助开发者创建易于维护和扩展的软件系统。SOLID 原则包括:

原则全称简述
S单一职责原则 (Single Responsibility Principle)一个类应该只有一个引起它变化的原因
O开放封闭原则 (Open-Closed Principle)软件实体应该对扩展开放,对修改关闭
L里氏替换原则 (Liskov Substitution Principle)子类必须能够替换其基类使用
I接口隔离原则 (Interface Segregation Principle)不应强制客户依赖于它们不使用的接口
D依赖倒置原则 (Dependency Inversion Principle)依赖抽象而不是具体实现

遵循这些原则可以创建出更加灵活、可维护、可扩展的代码,减少系统的脆弱性和紧耦合性,提高代码的复用性和可测试性。接下来,我们将通过电商系统的实例,逐一深入解析这些原则。

1. 单一职责原则 (SRP)

1.1 原则解析

定义:一个类应该只有一个引起它变化的原因。

核心思想:每个类或模块只负责一项职责,将不同的职责分离到不同的类中。

优势

  • 提高代码的内聚性
  • 降低类的复杂度
  • 增强代码的可维护性
  • 降低变更引起的风险
1.2 电商系统中的应用

让我们看看电商系统中如何应用单一职责原则。在许多不遵循 SRP 的系统中,我们可能会看到一个 “超级类” 包含了所有与订单相关的功能:

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
### 违反单一职责原则的示例
class Order:
def __init__(self, order_id, customer_info):
self.order_id = order_id
self.customer_info = customer_info
self.items = []
self.status = "created"

def add_item(self, product, quantity):
# 添加商品
pass

def remove_item(self, product):
# 移除商品
pass

def calculate_total(self):
# 计算总价
pass

def process_payment(self, payment_method, payment_details):
# 处理支付
pass

def send_confirmation_email(self):
# 发送确认邮件
pass

def ship_order(self):
# 处理配送
pass

def save_to_database(self):
# 保存到数据库
pass

这个类违反了单一职责原则,它同时承担了多项职责:处理订单项目、计算金额、处理支付、发送通知、处理配送、数据存储等。

1.3 遵循 SRP 的实现

按照单一职责原则,我们应该将不同职责分离到不同的类中:

  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
### models/order.py
class Order:
def __init__(self, order_id, customer_email):
self.id = order_id
self.customer_email = customer_email
self.items = []
self.status = "created"
self.shipping_address = None

def add_item(self, product, quantity):
# 仅负责添加商品到订单
self.items.append({
"product": product,
"quantity": quantity,
"price": product.price
})

def remove_item(self, product):
# 仅负责从订单中移除商品
original_length = len(self.items)
self.items = [item for item in self.items if item["product"].id != product.id]
return len(self.items) < original_length

def calculate_total(self):
# 仅负责计算订单总价
return sum(item["price"] * item["quantity"] for item in self.items)
  1. 支付处理类:专门负责支付相关功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
### models/payment.py
class CreditCardProcessor:
def process_payment(self, amount, payment_data):
# 专门处理信用卡支付
print(f"处理 ¥{amount:.2f} 的信用卡支付")
return True

class AlipayProcessor:
def process_payment(self, amount, payment_data):
# 专门处理支付宝支付
print(f"处理 ¥{amount:.2f} 的支付宝支付")
return True

class WechatPayProcessor:
def process_payment(self, amount, payment_data):
# 专门处理微信支付
print(f"处理 ¥{amount:.2f} 的微信支付")
return True
  1. 通知服务类:专门负责通知功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
### services/notification.py
class EmailOrderNotifier:
def notify_creation(self, order):
# 通知订单创建
print(f"向 {order.customer_email} 发送订单创建通知")

def notify_shipping(self, order):
# 通知订单发货
delivery_date = order.get_estimated_delivery()
print(f"向 {order.customer_email} 发送订单发货通知")

def notify_cancellation(self, order):
# 通知订单取消
print(f"向 {order.customer_email} 发送订单取消通知")
  1. 订单仓储类:专门负责数据存储
1
2
3
4
5
6
7
8
9
### repositories/order_repository.py
class SqliteOrderRepository:
def save(self, order):
# 专门负责订单持久化
print(f"将订单 {order.id} 保存到SQLite数据库")

def find_by_id(self, order_id):
# 专门负责查询订单
print(f"从SQLite数据库查询订单 {order_id}")

通过这样的设计,我们将不同的职责分离到不同的类中,每个类只负责一个特定的功能,符合单一职责原则。

2. 开放封闭原则 (OCP)

2.1 原则解析

定义:软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。

核心思想:当需要添加新功能时,应该通过扩展现有代码(如添加新类、新方法)而不是修改现有代码。

优势

  • 提高系统稳定性
  • 减少现有代码的改动和破坏
  • 提高代码的可复用性
  • 降低维护成本
2.2 电商系统中的应用

考虑支付处理的场景。如果没有遵循开放封闭原则,我们可能会这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
### 违反开放封闭原则的示例
class PaymentProcessor:
def process_payment(self, amount, payment_method, payment_data):
if payment_method == "credit_card":
# 处理信用卡支付
print(f"处理 ¥{amount:.2f} 的信用卡支付")
return True
elif payment_method == "alipay":
# 处理支付宝支付
print(f"处理 ¥{amount:.2f} 的支付宝支付")
return True
elif payment_method == "wechat":
# 处理微信支付
print(f"处理 ¥{amount:.2f} 的微信支付")
return True
else:
raise ValueError(f"不支持的支付方式: {payment_method}")

这个设计违反了开放封闭原则,因为每当我们想添加新的支付方式(如 PayPal),都需要修改这个类的代码,添加新的条件分支。

2.3 遵循 OCP 的实现

按照开放封闭原则,我们应该通过抽象接口和多态来实现支付处理:

  1. 定义支付处理接口
1
2
3
4
5
6
7
8
9
10
11
12
13
### interfaces/payment.py
from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount, payment_data):
"""处理支付"""
pass

@abstractmethod
def get_payment_method_name(self):
"""获取支付方式名称"""
pass
  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
### models/payment.py
from ecommerce.interfaces.payment import PaymentProcessor

class CreditCardProcessor(PaymentProcessor):
def process_payment(self, amount, payment_data):
print(f"处理 ¥{amount:.2f} 的信用卡支付")
return True

def get_payment_method_name(self):
return "信用卡支付"

class AlipayProcessor(PaymentProcessor):
def process_payment(self, amount, payment_data):
print(f"处理 ¥{amount:.2f} 的支付宝支付")
return True

def get_payment_method_name(self):
return "支付宝支付"

class WechatPayProcessor(PaymentProcessor):
def process_payment(self, amount, payment_data):
print(f"处理 ¥{amount:.2f} 的微信支付")
return True

def get_payment_method_name(self):
return "微信支付"
  1. 创建支付处理器工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
### models/payment.py
class PaymentProcessorFactory:
@staticmethod
def create_processor(payment_method):
processors = {
"credit_card": CreditCardProcessor(),
"alipay": AlipayProcessor(),
"wechat": WechatPayProcessor()
}

if payment_method not in processors:
raise ValueError(f"不支持的支付方式: {payment_method}")

return processors[payment_method]

现在,如果我们想添加新的支付方式(如 PayPal),只需要:

  1. 创建一个新的 PayPalProcessor 类实现 PaymentProcessor 接口
  2. 将其添加到 PaymentProcessorFactory 的映射中

无需修改现有代码,只需扩展新功能,完全符合开放封闭原则。

3. 里氏替换原则 (LSP)

3.1 原则解析

定义:子类型必须能够替换其基类型使用。

核心思想:继承类应当能够替代其父类使用,而不改变程序的正确性。子类应当保持父类的行为约定。

优势

  • 确保类的层次结构设计合理
  • 增强代码的可复用性
  • 提高系统的健壮性
  • 保证面向对象设计的正确性
3.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
### 违反里氏替换原则的示例
class ShippingMethod:
def calculate_cost(self, weight, distance):
# 计算标准运费
return weight * 0.5 + distance * 0.1

def get_estimated_days(self, distance):
# 获取估计送达天数
return max(1, int(distance / 100))

class ExpressShipping(ShippingMethod):
def calculate_cost(self, weight, distance):
# 快递费用比标准运费高
return weight * 1.0 + distance * 0.2

def get_estimated_days(self, distance):
# 快递比标准配送快
return max(1, int(distance / 200))

class FreeShipping(ShippingMethod):
def calculate_cost(self, weight, distance):
# 免费配送
return 0

def get_estimated_days(self, distance):
# 不提供送达时间估计,返回None而不是整数
return None # 违反了父类的行为约定

在上面的例子中,FreeShipping 类违反了里氏替换原则,因为它的 get_estimated_days 方法返回了 None 而不是整数,这与父类的行为约定不一致。如果有代码依赖于 ShippingMethod 的返回值是整数,那么使用 FreeShipping 时可能会导致错误。

3.3 遵循 LSP 的实现

以下是遵循里氏替换原则的配送方式实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
### interfaces/shipping.py
from abc import ABC, abstractmethod

class ShippingMethod(ABC):
@abstractmethod
def calculate_cost(self, weight, distance):
"""计算运费"""
pass

@abstractmethod
def get_estimated_days(self, distance):
"""获取预计配送天数"""
pass
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
### models/shipping.py
from ecommerce.interfaces.shipping import ShippingMethod

class StandardShipping(ShippingMethod):
def calculate_cost(self, weight, distance):
return weight * 0.5 + distance * 0.1

def get_estimated_days(self, distance):
return max(1, int(distance / 100))

def __str__(self):
return "标准配送"

class ExpressShipping(ShippingMethod):
def calculate_cost(self, weight, distance):
return weight * 1.0 + distance * 0.2

def get_estimated_days(self, distance):
return max(1, int(distance / 200))

def __str__(self):
return "快速配送"

class FreeShipping(ShippingMethod):
def calculate_cost(self, weight, distance):
# 免费配送
return 0

def get_estimated_days(self, distance):
# 仍然返回整数,保持与父类一致的行为约定
return max(5, int(distance / 50)) # 免费配送较慢

def __str__(self):
return "免费配送"

现在,所有的配送方式类都符合里氏替换原则,它们都保持了父类的行为约定:calculate_cost 返回数值,get_estimated_days 返回整数。

4. 接口隔离原则 (ISP)

4.1 原则解析

定义:客户端不应该被迫依赖于它们不使用的接口。

核心思想:一个类不应该依赖于它不需要的接口。接口应该是小而精的,一个大而全的接口应该被拆分为多个小接口。

优势

  • 提高接口的聚合度
  • 减少冗余的接口实现
  • 提高系统的灵活性和可扩展性
  • 降低接口之间的耦合度
4.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
### 违反接口隔离原则的示例
from abc import ABC, abstractmethod

class OrderNotifier(ABC):
@abstractmethod
def notify_creation(self, order):
"""通知订单创建"""
pass

@abstractmethod
def notify_shipping(self, order):
"""通知订单配送"""
pass

@abstractmethod
def notify_cancellation(self, order):
"""通知订单取消"""
pass

@abstractmethod
def send_sms(self, phone_number, message):
"""发送短信通知"""
pass

@abstractmethod
def send_email(self, email, subject, message):
"""发送邮件通知"""
pass

@abstractmethod
def send_push_notification(self, device_id, message):
"""发送推送通知"""
pass

这个接口违反了接口隔离原则,因为它强制所有实现类都必须实现所有方法。例如,一个邮件通知实现类可能不需要实现 SMS 或推送通知的方法。

4.3 遵循 ISP 的实现

按照接口隔离原则,我们应该将大接口拆分为多个小接口:

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
### interfaces/notification.py
from abc import ABC, abstractmethod

class OrderNotifier(ABC):
@abstractmethod
def notify_creation(self, order):
"""通知订单创建"""
pass

@abstractmethod
def notify_shipping(self, order):
"""通知订单配送"""
pass

@abstractmethod
def notify_cancellation(self, order):
"""通知订单取消"""
pass

class EmailNotifier(ABC):
@abstractmethod
def send_email(self, email, subject, message):
"""发送邮件"""
pass

class SMSNotifier(ABC):
@abstractmethod
def send_sms(self, phone_number, message):
"""发送短信"""
pass

class PushNotifier(ABC):
@abstractmethod
def send_push_notification(self, device_id, message):
"""发送推送通知"""
pass

然后,实现类可以选择性地实现它们需要的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
### services/notification.py
from ecommerce.interfaces.notification import OrderNotifier, EmailNotifier

class EmailOrderNotifier(OrderNotifier, EmailNotifier):
def notify_creation(self, order):
print(f"向 {order.customer_email} 发送订单创建通知")
self.send_email(order.customer_email, "订单已创建", f"您的订单 {order.id} 已创建")

def notify_shipping(self, order):
print(f"向 {order.customer_email} 发送订单配送通知")
self.send_email(order.customer_email, "订单已发货", f"您的订单 {order.id} 已发货")

def notify_cancellation(self, order):
print(f"向 {order.customer_email} 发送订单取消通知")
self.send_email(order.customer_email, "订单已取消", f"您的订单 {order.id} 已取消")

def send_email(self, email, subject, message):
print(f"发送邮件到 {email},主题: {subject}")
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
### services/notification.py
from ecommerce.interfaces.notification import OrderNotifier, SMSNotifier

class SMSOrderNotifier(OrderNotifier, SMSNotifier):
def notify_creation(self, order):
phone = self._get_phone_from_order(order)
print(f"向手机号 {phone} 发送订单创建短信通知")
self.send_sms(phone, f"您的订单 {order.id} 已创建")

def notify_shipping(self, order):
phone = self._get_phone_from_order(order)
print(f"向手机号 {phone} 发送订单配送短信通知")
self.send_sms(phone, f"您的订单 {order.id} 已发货")

def notify_cancellation(self, order):
phone = self._get_phone_from_order(order)
print(f"向手机号 {phone} 发送订单取消短信通知")
self.send_sms(phone, f"您的订单 {order.id} 已取消")

def send_sms(self, phone_number, message):
print(f"发送短信到 {phone_number}: {message}")

def _get_phone_from_order(self, order):
# 实际应用中应该从订单中获取电话号码
return "1234567890"

通过这种方式,每个实现类只需要实现它关心的接口,符合接口隔离原则。

5. 依赖倒置原则 (DIP)

5.1 原则解析

定义:高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

核心思想:通过引入抽象层(如接口、抽象类)来解耦高层和低层模块,实现可扩展性和可维护性。

优势

  • 降低模块间的耦合度
  • 提高代码的可重用性
  • 方便系统的单元测试
  • 提高系统的可扩展性
5.2 电商系统中的应用

考虑订单服务与数据持久化的关系。违反依赖倒置原则的设计可能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
### 违反依赖倒置原则的示例
import sqlite3

class OrderService:
def __init__(self):
# 直接依赖具体的SQLite实现
self.connection = sqlite3.connect('orders.db')

def create_order(self, order_data):
cursor = self.connection.cursor()
# 插入订单数据
cursor.execute(
"INSERT INTO orders (id, customer_email, status) VALUES (?, ?, ?)",
(order_data['id'], order_data['email'], 'created')
)
self.connection.commit()

def get_order(self, order_id):
cursor = self.connection.cursor()
# 查询订单数据
cursor.execute("SELECT * FROM orders WHERE id = ?", (order_id,))
return cursor.fetchone()

这个设计违反了依赖倒置原则,高层模块 OrderService 直接依赖于低层模块 sqlite3。如果我们想切换到其他数据库,需要修改 OrderService 类的代码。

5.3 遵循 DIP 的实现

按照依赖倒置原则,我们应该让高层和低层模块都依赖于抽象:

  1. 定义订单仓储接口(抽象)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
### interfaces/repository.py
from abc import ABC, abstractmethod
from typing import List, Optional

class OrderRepository(ABC):
@abstractmethod
def save(self, order):
"""保存订单"""
pass

@abstractmethod
def find_by_id(self, order_id):
"""根据ID查找订单"""
pass

@abstractmethod
def list_orders(self):
"""列出所有订单"""
pass
  1. 实现具体的订单仓储类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
### repositories/order_repository.py
import sqlite3
from ecommerce.interfaces.repository import OrderRepository

class SqliteOrderRepository(OrderRepository):
def __init__(self):
# 具体的SQLite实现
self.connection = sqlite3.connect('orders.db')

def save(self, order):
cursor = self.connection.cursor()
# 保存订单到SQLite
print(f"将订单 {order.id} 保存到SQLite数据库")
# 实际的SQL操作...

def find_by_id(self, order_id):
print(f"从SQLite数据库查询订单 {order_id}")
# 实际的SQL查询...

def list_orders(self):
print("从SQLite数据库查询所有订单")
# 实际的SQL查询...
  1. 修改订单服务,依赖抽象接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
### services/order.py
from ecommerce.interfaces.repository import OrderRepository
from ecommerce.interfaces.notification import OrderNotifier

class OrderService:
def __init__(self, repository: OrderRepository, notifier: OrderNotifier):
# 依赖抽象接口,而不是具体实现
self._repository = repository
self._notifier = notifier

def create_order(self, order):
self._repository.save(order)
self._notifier.notify_creation(order)

def get_order(self, order_id):
return self._repository.find_by_id(order_id)

通过这种设计,OrderService 依赖于抽象接口 OrderRepositoryOrderNotifier,而不是具体实现。这样我们可以轻松地替换具体实现,例如从 SQLite 切换到 MySQL,只需要提供一个新的实现类,而不需要修改 OrderService 的代码。

Python 中的 __slots__

__slots__ 是一个特殊的类变量,它可以限制类的实例能拥有的属性,节省内存并提高性能:

__slots__ 的工作原理与性能优势

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
import sys

## 不使用__slots__的普通类
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

## 使用__slots__的类
class PersonWithSlots:
__slots__ = ['name', 'age']

def __init__(self, name, age):
self.name = name
self.age = age

## 创建实例
p1 = Person("Alice", 30)
p2 = PersonWithSlots("Bob", 25)

## 检查内存使用
print(f"Person实例大小: {sys.getsizeof(p1)} 字节")
print(f"PersonWithSlots实例大小: {sys.getsizeof(p2)} 字节")

## 检查属性字典
print(f"Person有__dict__: {hasattr(p1, '__dict__')}")
print(f"PersonWithSlots有__dict__: {hasattr(p2, '__dict__')}")

## 属性访问性能比较
import timeit

def access_person():
p = Person("Alice", 30)
for _ in range(1000000):
x = p.name
y = p.age

def access_person_with_slots():
p = PersonWithSlots("Alice", 30)
for _ in range(1000000):
x = p.name
y = p.age

## 测量访问性能
print(f"访问Person属性时间: {timeit.timeit(access_person, number=5):.4f}秒")
print(f"访问PersonWithSlots属性时间: {timeit.timeit(access_person_with_slots, number=5):.4f}秒")

__slots__ 的限制和继承行为

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
class Base:
__slots__ = ['x']

def __init__(self, x):
self.x = x

class Child(Base):
__slots__ = ['y']

def __init__(self, x, y):
super().__init__(x)
self.y = y

## 行为测试
c = Child(1, 2)
print(f"Child实例可以访问x: {c.x}")
print(f"Child实例可以访问y: {c.y}")

try:
c.z = 3 # 尝试设置未在__slots__中定义的属性
except AttributeError as e:
print(f"无法设置z属性: {e}")

## 检查实例字典
print(f"Child有__dict__: {hasattr(c, '__dict__')}")