第十一章:面向对象编程
对象的本质
对象本质上是一个 “容器”,用来存放数据和功能的集合体。面向对象编程的核心思想是将数据和操作数据的方法封装在一起,形成独立的实体。
为什么需要对象
以开发 MOBA 游戏为例,每个英雄都有自己的属性(如攻击力、移动速度)和功能(如技能)。如果不使用对象,代码会变得非常冗余:
## 不使用对象的方式
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 这个对象里
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 类里面,在面向对象编程里面,是先创建一个类,再将对象创建出来,所以类也算是一种容器,他也可以被称作是对象
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__
方法
为了减少代码冗余,我们可以定义一个初始化方法,在创建对象时自动设置属性:
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 这个功能,完成对象属性的初始化操作,这时候拿到的对象就是有自己独有属性的对象
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 [] 拿属性过于繁杂,所以也可以用到类名.属性名的方式来传值给函数,并且在代码最后,在打印函数的内存地址的时候会发现打印类里的函数是一个正常的函数,而打印对象的函数方法则是一个绑定方法,也就是说类的函数属性,绑定给对象使用了之后,就不再是一个普通函数
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 方法下增添需要的绑定方法,添加对应的函数功能,就能够实现谁调用这个对象的函数方法,就给谁增添对应的数据
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)实现属性隐藏,即在属性名前加上双下划线:
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 方法:
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
装饰器提供了更优雅的属性访问方式:
# 这段代码展示了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. 继承
继承允许一个类(子类)继承另一个类(父类)的属性和方法。
单继承和多继承
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()调用父类方法:
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):
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顺序查找)
菱形继承问题
菱形继承(钻石继承)是指一个子类通过不同的路径继承了同一个基类:
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 是一种设计模式,用于为类添加功能,而不使用传统继承关系:
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 方法
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 推崇的一个鸭子模型的概念,只要你长得像鸭子,走路像鸭子,那么你就是一只鸭子,所以类只要内部定义的方法一样,那么他们就同属于一种类别,这也是一种解耦合的方法
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 推崇的永远是简洁至上,这样限制只会让程序变得臃肿
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 | 操作实例状态 |
类方法 | @classmethod | cls | 操作类状态,替代构造函数 |
静态方法 | @staticmethod | 无特殊参数 | 工具函数 |
示例场景:
- 在实际的开发中,实例方法通常用于操作与当前对象状态相关的行为。比如,我们定义一个
Person
类,该类实例方法可以访问每个对象的名称和年龄,并提供修改、获取这些属性的功能。
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)
常见示例场景:
- 替代构造函数: 类方法通常用于定义一个替代构造函数,根据不同的输入创建对象。比如可以根据不同的配置文件或环境变量来初始化对象。
- 操作类状态: 类方法通常用于修改与类相关的共享数据,比如修改所有实例的共享配置。
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)
- 第一参数: 无特殊参数。静态方法与类或实例无关,不需要
self
或cls
参数。 - 访问实例变量: 静态方法无法访问实例或类的属性。
- 访问类变量: 静态方法只能做独立于实例和类的操作。它通常用于工具函数,或者处理与对象本身无关的逻辑。
常见示例场景:
- 静态方法通常用来封装一些独立的工具函数,这些函数与类或实例的状态无关。例如,数学计算、字符串处理等功能。
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
,返回 True
或 False
。
示例:
if hasattr(p, 'name'): # 检查 Person 对象是否有 'name' 属性
print(getattr(p, 'name')) # 输出: Alice
2. getattr()
— 获取对象的属性
getattr(obj, name, default)
用于获取对象的属性。如果该属性存在,返回其值;如果不存在,则返回指定的 default
值。如果没有提供 default
,当属性不存在时会抛出 AttributeError
。
示例:
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
。如果该属性不存在,则会动态创建一个新的属性。
示例:
setattr(p, 'city', 'New York') # 动态设置新的属性 'city'
print(p.city) # 输出: New York
4. delattr()
— 删除对象的属性
delattr(obj, name)
用于删除对象的指定属性。删除属性后,访问该属性会抛出 AttributeError
。
示例:
delattr(p, 'city') # 删除属性 'city'
# print(p.city) # 会抛出 AttributeError: 'Person' object has no attribute 'city'
5. dir()
— 列出对象的所有属性
dir(obj)
返回一个列表,包含对象 obj
的所有属性和方法(包括其继承的属性)。
示例:
print(dir(p)) # 列出 Person 对象的所有属性
6. type()
— 获取对象的类型
type(obj)
返回对象 obj
的类型(即类)。
示例:
print(type(p)) # 输出: <class '__main__.Person'>
7. isinstance()
— 检查对象是否为类的实例
isinstance(obj, cls)
用于检查对象 obj
是否为类 cls
的实例或其子类的实例。
示例:
if isinstance(p, Person): # 检查 p 是否是 Person 类的实例
print("p 是 Person 类的实例") # 输出: p 是 Person 类的实例
8. issubclass()
— 检查类是否为另一个类的子类
issubclass(cls, parent)
用于检查类 cls
是否是类 parent
的子类。
示例:
class Animal:
pass
class Dog(Animal):
pass
print(issubclass(Dog, Animal)) # 输出: True
9. callable()
— 检查对象是否可调用
callable(obj)
用于检查对象 obj
是否可以调用,即它是否是一个函数或具有 __call__()
方法的对象。
示例:
def greet():
return "Hello"
print(callable(greet)) # 输出: True
print(callable(p)) # 输出: False,因为 Person 对象不可调用
10. vars()
— 获取对象的 __dict__
vars(obj)
返回一个字典,包含对象 obj
的所有属性和对应的值。这是访问对象的实例变量的一种方式。
示例:
print(vars(p)) # 输出: {'name': 'Alice', 'age': 30}
反射示例
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.IntEnum | Enum 的子类,其成员也是整数,可以和整数直接比较。 |
enum.Flag | Enum 的子类,其成员可以使用位运算符 (` |
enum.IntFlag | Flag 的子类,其成员也是整数,并支持位运算。 |
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_NAME
或MyEnum(value)
来引用和获取枚举成员。member.name
和member.value
是最常用的成员属性。@unique
装饰器有助于保证值的唯一性。
enum
模块代码示例
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
或==
来比较两个枚举成员是否相同。IntEnum
和IntFlag
的成员还可以直接与整数进行比较。- 迭代与访问: 可以迭代枚举类的所有成员。可以通过成员名 (
MyEnum.MEMBER
)、方括号加成员名字符串 (MyEnum['MEMBER']
) 或成员值 (MyEnum(value)
) 来访问特定的枚举成员。auto()
的行为:enum.auto()
会自动为枚举成员分配一个值。默认情况下,对于Enum
,它从1开始递增。对于Flag
,它会分配2的幂 (1, 2, 4, 8, …)。你可以通过重写_generate_next_value_
特殊方法来自定义auto()
的行为。Flag
与位运算:Flag
和IntFlag
类型的枚举成员支持位运算符 (|
,&
,^
,~
),非常适合表示权限、状态组合等场景。可以使用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
模块代码示例
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
:@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.定义类名
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岁
自定义元类
对于元类来说,最关键的步骤也就是在第四步,在我们调用原类之前可以对这个类做一些自定义化操作,如 类名不能带特殊符号,类必须写文档注释......
我们通过一个自定义类来实现这个功能
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__
方法
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__
方法
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__
方法。自定义此方法可以控制实例化过程:
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 | 返回创建的类 | 类可以使用了 |
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) | 析构方法 | 对象被销毁时调用 | 释放资源,关闭文件或连接 |
实际应用场景:
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) | 为开发者提供详细信息,理想情况下应返回可重新创建对象的代码 |
实际应用场景:
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__
。obj = MyClass("Bob", 25) obj # 在交互式环境中,自动调用 __repr__
MyClass('Bob', 25)
-
__repr__
的目标__repr__
的设计目标是让对象的字符串表示能够作为一种明确、无歧义的代码表达式,最好能够让开发者通过eval()
来重新构建该对象。例如,__repr__
返回的字符串应该是: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 | 自定义成员检查逻辑 |
实际应用场景:自定义商品购物车
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__
方法可以在对象实例化的时候调用
当我们调用一个对象,也就相当于 ↓
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) | 允许对象作为字典键或集合元素 |
实际应用场景:数据分析器
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 | 自定义对象相除 |
实际应用场景:向量运算
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 | 拦截属性删除 |
实际应用场景:属性验证与保护
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 块结束 | 资源释放,异常处理等 |
实际应用场景:数据库连接管理
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) | 删除属性 | 删除属性时触发 | 自定义属性删除行为 |
实际应用场景:数据验证描述符
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 中实现属性的更简洁方式,它允许我们用方法行为来定义属性。以下是一个成绩管理系统的示例:
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. 常见特殊方法使用陷阱与最佳实践
陷阱与注意事项
__init__
vs__new__
:- 陷阱:混淆这两个方法的用途和调用顺序。
- 最佳实践:除非实现元类或单例模式,一般只需重写
__init__
。
# 错误示例
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)
__eq__
和__hash__
:- 陷阱:实现
__eq__
而不实现__hash__
,导致对象无法用作字典键。 - 最佳实践:如果重写
__eq__
,也应该重写__hash__
或设置__hash__ = None
使对象不可哈希。
- 陷阱:实现
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))
- 递归问题:
- 陷阱:在特殊方法中无限递归调用自身。
- 最佳实践:避免在特殊方法中使用可能触发同一方法的操作。
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}"
__bool__
和__len__
:- 陷阱:不了解这两个方法的优先级和用途。
- 最佳实践:
__bool__
优先于__len__
;对于集合类,实现__len__
;对于真假逻辑,实现__bool__
。
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')
- 反向方法失效:
- 陷阱:忽略实现反向操作方法(如
__radd__
)。 - 最佳实践:为算术操作同时实现普通方法和反向方法。
- 陷阱:忽略实现反向操作方法(如
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__ | 用于创建可重用的属性行为 |
对象与类的高级概念
深入理解实例、类和元类关系
# 定义一个普通类
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是自己的实例
属性查找的复杂情况
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__ | 对象不再被引用时的清理 |
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()
函数可以查看对象的引用计数
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)}已被回收") # 不可能进这个判断,因为存在计数引用和循环引用问题
高级常用设计模式
单例模式
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
懒汉式单例模式的使用场景
# 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()) # 输出: 我是单例的
懒汉式单例模式在以下情况特别适用:
- 资源密集型对象:当创建实例需要消耗大量资源(如数据库连接、文件系统操作等),懒汉式可以推迟实例化,直到真正需要时才创建。
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}")
- 配置管理器:应用程序的配置管理器通常是单例的,且配置加载可能很耗时。
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("从文件加载配置...")
# 模拟加载配置文件的耗时操作
- 应用程序状态管理:当需要全局访问应用状态但又不希望在程序启动时立即初始化时。
元类实现方式适合需要更灵活控制实例化过程的场景,特别是需要根据参数动态决定实例化行为的情况:
# 日志管理器单例,可以根据不同的日志级别创建不同配置的单例
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}级别的日志记录器")
装饰器实现方式适合需要将现有类转换为单例而不修改其内部代码的场景:
# 第三方库类转换为单例
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
饿汉式单例模式的使用场景
饿汉式单例模式最适合以下场景:
- 必然会使用的核心服务:如应用程序中必定会使用的服务组件。
# 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
- 应用程序设置和常量:包含应用配置的单例对象。
# 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()
- 线程安全要求高的场景:饿汉式单例天然线程安全,适合多线程环境。
# thread_pool.py
class ThreadPoolManager:
def __init__(self):
print("初始化线程池...")
self.max_workers = 10
# 线程池初始化代码
def submit_task(self, task):
print(f"提交任务到线程池: {task}")
# 任务提交逻辑
# 程序启动时即创建线程池
thread_pool = ThreadPoolManager()
工厂模式
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!
工厂模式在以下场景中特别有用:
- 插件架构:当系统需要动态加载和使用不同的插件或扩展时。
├── app.py # 主应用程序,演示如何使用插件
├── plugin_interface.py # 定义插件接口
├── plugin_factory.py # 工厂方法模式的实现
├── plugins/ # 插件目录
│ ├── __init__.py # 插件包初始化文件
│ ├── image_processor.py # 图片处理插件
│ └── text_analyzer.py # 文本分析插件
首先我们定义一个插件接口 plugin_interface.py
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
# 使plugins目录成为一个Python包
# 这允许我们使用 from plugins import xxx 的导入方式
# 列出所有可用的插件模块名,方便外部遍历
available_plugins = [
"image_processor",
"text_analyzer"
]
图片处理插件 image_processor
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
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
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
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()
- 跨平台应用程序:根据不同操作系统创建适当的组件。
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() # 根据当前操作系统渲染适当的按钮
- 数据库访问层:根据配置选择不同的数据库实现。
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")
- 测试替身创建:在测试环境中替换真实依赖项。
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
观察者模式
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)
观察者模式在以下场景中非常有效:
- 事件驱动的系统:如 GUI 应用程序,响应用户操作。
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_())
- 发布-订阅系统:多个组件需要对特定事件做出响应。
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("今天是个好日子!") # 所有订阅者都会收到通知
- 数据变更监控:当对象状态变化需要通知其他对象时。
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 # 自动通知所有视图更新
- 系统监控:监控系统状态变化并触发警报。
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.静态代理实现
静态代理在编译时确定代理行为,非常适合结构固定的场景。
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.动态代理实现
动态代理可以在运行时动态地为对象添加功能,而不需要在编译时确定:
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.保护代理实现
保护代理是一种常见的应用场景,用于控制对对象的访问权限,下面的例子展示了如何实现一个简单的保护代理:
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.虚拟代理实现
虚拟代理用于控制昂贵资源的延迟加载,只有在真正需要时才创建对象:
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 的系统中,我们可能会看到一个 “超级类” 包含了所有与订单相关的功能:
### 违反单一职责原则的示例
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 的实现
按照单一职责原则,我们应该将不同职责分离到不同的类中:
- 订单模型类:只负责订单的基本属性和状态
### 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)
- 支付处理类:专门负责支付相关功能
### 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
- 通知服务类:专门负责通知功能
### 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} 发送订单取消通知")
- 订单仓储类:专门负责数据存储
### 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 电商系统中的应用
考虑支付处理的场景。如果没有遵循开放封闭原则,我们可能会这样实现:
### 违反开放封闭原则的示例
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 的实现
按照开放封闭原则,我们应该通过抽象接口和多态来实现支付处理:
- 定义支付处理接口
### 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
- 实现具体支付处理器
### 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 "微信支付"
- 创建支付处理器工厂
### 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),只需要:
- 创建一个新的
PayPalProcessor
类实现PaymentProcessor
接口 - 将其添加到
PaymentProcessorFactory
的映射中
无需修改现有代码,只需扩展新功能,完全符合开放封闭原则。
3. 里氏替换原则 (LSP)
3.1 原则解析
定义:子类型必须能够替换其基类型使用。
核心思想:继承类应当能够替代其父类使用,而不改变程序的正确性。子类应当保持父类的行为约定。
优势:
- 确保类的层次结构设计合理
- 增强代码的可复用性
- 提高系统的健壮性
- 保证面向对象设计的正确性
3.2 电商系统中的应用
考虑配送方式的实现。如果违反里氏替换原则,我们可能会遇到这样的情况:
### 违反里氏替换原则的示例
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 的实现
以下是遵循里氏替换原则的配送方式实现:
### 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
### 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 电商系统中的应用
考虑订单通知的场景。违反接口隔离原则的设计可能如下:
### 违反接口隔离原则的示例
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 的实现
按照接口隔离原则,我们应该将大接口拆分为多个小接口:
### 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
然后,实现类可以选择性地实现它们需要的接口:
### 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}")
### 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 电商系统中的应用
考虑订单服务与数据持久化的关系。违反依赖倒置原则的设计可能如下:
### 违反依赖倒置原则的示例
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 的实现
按照依赖倒置原则,我们应该让高层和低层模块都依赖于抽象:
- 定义订单仓储接口(抽象)
### 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
- 实现具体的订单仓储类
### 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查询...
- 修改订单服务,依赖抽象接口
### 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
依赖于抽象接口 OrderRepository
和 OrderNotifier
,而不是具体实现。这样我们可以轻松地替换具体实现,例如从 SQLite 切换到 MySQL,只需要提供一个新的实现类,而不需要修改 OrderService
的代码。
Python 中的 __slots__
__slots__
是一个特殊的类变量,它可以限制类的实例能拥有的属性,节省内存并提高性能:
__slots__
的工作原理与性能优势
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__
的限制和继承行为
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__')}")
第十二章: 异常处理
异常处理是 Python 编程中的重要环节,它允许程序在遇到错误时优雅地恢复或退出,而不是直接崩溃。
12.1 基本异常处理
异常处理的核心是 try-except
结构,它允许程序捕获并处理运行时错误。
12.1.1 异常的概念与意义
异常是程序运行时发生的错误,会打断正常的程序执行流程。Python 提供了强大的异常处理机制,使程序能够:
- 预测可能的错误并妥善处理
- 提供用户友好的错误信息
- 防止程序意外终止
- 实现优雅的错误恢复策略
12.1.2 基础 try-except 结构
try:
# 可能引发异常的代码
num = int(input("请输入一个数字: ")) # 可能引发 ValueError
result = 10 / num # 可能引发 ZeroDivisionError
print(f"结果是: {result}")
except ValueError:
# 处理特定异常
print("输入必须是数字!")
except ZeroDivisionError:
# 处理另一种特定异常
print("不能除以零!")
except:
# 捕获所有其他异常(不推荐这种写法)
print("发生了其他错误!")
常见内置异常 | 触发场景 | 示例 |
---|---|---|
ValueError | 传入无效值 | int("abc") |
TypeError | 类型不匹配 | "2" + 2 |
ZeroDivisionError | 除数为零 | 10/0 |
IndexError | 索引超出范围 | [1,2,3][10] |
KeyError | 字典中不存在的键 | {"a":1}["b"] |
FileNotFoundError | 文件不存在 | open("不存在.txt") |
ImportError | 导入模块失败 | import 不存在模块 |
AttributeError | 对象没有特定属性 | "hello".append(1) |
12.2 完整的异常处理结构
完整的异常处理结构包括 try
, except
, else
, 和 finally
四个部分,每个部分负责不同的功能。
12.2.1 try-except-else-finally 完整结构
try:
# 可能引发异常的代码
num = int(input("请输入一个数字: "))
result = 10 / num
print(f"结果是: {result}")
except ValueError as e:
# 处理特定异常,e 包含异常的详细信息
print(f"输入错误: {e}") # e 可能显示: "invalid literal for int() with base 10: 'abc'"
except ZeroDivisionError as e:
# 处理另一种特定异常
print(f"除零错误: {e}") # e 可能显示: "division by zero"
except Exception as e:
# 处理所有其他异常(这种方式比空except更好)
print(f"其他错误: {e}")
else:
# 只有当try块中的代码执行成功且没有异常发生时执行
print("计算成功完成!")
finally:
# 无论是否有异常都会执行的代码块
print("异常处理结束")
12.2.2 各代码块执行条件总结
代码块 | 执行条件 | 典型用途 |
---|---|---|
try | 必定执行 | 放置可能出错的代码 |
except | 对应类型异常发生时 | 处理特定类型错误 |
else | try 块无异常发生时 | 执行成功后的操作 |
finally | 无论有无异常均执行 | 资源清理、释放 |
12.3 自定义异常
虽然 Python 提供了丰富的内置异常,但在开发特定应用时,创建自定义异常可以使代码更具可读性和针对性。
12.3.1 创建自定义异常类
# 自定义异常类,继承自 Exception
class InsufficientFundsError(Exception):
"""当账户余额不足时引发的异常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
# 创建有意义的错误消息
self.message = f"余额不足: 当前余额 {balance} 元,尝试提取 {amount} 元"
super().__init__(self.message)
def __str__(self):
return self.message
12.3.2 使用自定义异常
# 在业务逻辑中使用自定义异常
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
# 在适当的条件下抛出自定义异常
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
return amount
# 处理自定义异常的实际场景
account = BankAccount(100)
try:
# 尝试提取超过余额的金额
account.withdraw(150)
except InsufficientFundsError as e:
# 针对性地处理特定业务异常
print(f"操作失败: {e}")
print(f"您需要再存入至少 {e.amount - e.balance} 元")
# 可以在这里提供补救措施,比如自动转入资金或提供贷款选项
自定义异常命名惯例 | 示例 | 适用场景 |
---|---|---|
以 “Error” 结尾 | ValidationError | 程序错误,需纠正 |
以 “Warning” 结尾 | DeprecationWarning | 警告级别的问题 |
以具体领域开头 | DatabaseConnectionError | 特定领域的异常 |
12.4 异常的传播与重新抛出
了解异常如何在调用栈中传播以及如何重新抛出异常对于构建稳健的错误处理系统至关重要。
12.4.1 异常传播机制
当异常发生时,Python 会沿着调用栈向上查找,直到找到相应的 except
子句处理该异常,如果没有处理程序,程序将终止。
# 异常传播示例
def func_inner():
# 这里的异常会向上传播
return 10 / 0 # 引发 ZeroDivisionError
def func_middle():
# 没有处理异常,所以异常继续传播
return func_inner()
def func_outer():
try:
# 在这里捕获来自更深层次函数的异常
return func_middle()
except ZeroDivisionError:
print("捕获了除零错误!")
return None
# 调用最外层函数
result = func_outer() # 输出: 捕获了除零错误!
12.4.2 重新抛出异常
重新抛出异常有两种方式:
- 直接使用
raise
语句不带参数 - 使用
raise ... from ...
结构表明异常的因果关系
def process_data(data):
try:
# 尝试处理数据
result = data[0] / data[1]
return result
except ZeroDivisionError:
# 记录错误并重新抛出当前异常
print("除数不能为零!")
raise # 直接重新抛出当前捕获的异常
except IndexError as e:
# 捕获后转换为更具体的应用级异常,并保留原始错误信息
raise ValueError("数据格式不正确,需要至少两个元素") from e
# 调用函数并处理异常
try:
# 尝试处理带有问题的数据
result = process_data([10]) # 数组只有一个元素,会引发 IndexError
except ValueError as e:
# 处理转换后的异常
print(f"发生错误: {e}")
# 访问原始异常
if e.__cause__:
print(f"原始错误: {e.__cause__}")
重新抛出方式 | 语法 | 适用场景 |
---|---|---|
简单重抛 | raise | 仅记录错误后继续传播 |
转换异常 | raise NewError() from original_error | 将低级异常转换为应用级异常 |
清除上下文 | raise NewError() from None | 隐藏原始异常(不推荐) |
12.5 使用上下文管理器
上下文管理器是 Python 的一种强大机制,通过 with
语句实现自动资源管理,特别适合处理需要显式打开和关闭的资源。
12.5.1 with 语句和资源管理
# 文件操作 - 最常见的上下文管理器应用场景
with open('file.txt', 'w') as f:
f.write('Hello, World!')
# 可能发生异常的代码
# raise ValueError("演示异常")
# 即使发生异常,文件也会自动关闭
12.5.2 自定义上下文管理器
class DatabaseConnection:
def __init__(self, connection_string):
self.connection_string = connection_string
self.connection = None
def __enter__(self):
"""进入上下文时调用,返回值被赋给as后的变量"""
print(f"连接到数据库: {self.connection_string}")
# 在实际应用中,这里会创建真正的数据库连接
self.connection = "已连接"
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
"""离开上下文时调用,无论是正常退出还是异常退出
参数: 异常类型、异常值、异常回溯信息"""
print("关闭数据库连接")
# 释放资源
self.connection = None
# 返回值决定异常处理:
# - True: 表示异常已处理,不再传播
# - False/None: 表示需要继续传播异常
return False # 让异常继续传播
12.5.3 实际应用场景
# 使用自定义上下文管理器进行数据库操作
try:
with DatabaseConnection("mysql://localhost/mydb") as conn:
print(f"使用连接: {conn}")
# 数据库操作代码
# 模拟操作失败
# raise ValueError("数据插入失败")
except Exception as e:
print(f"捕获到异常: {e}")
# 处理数据库操作异常
# 可能的恢复策略: 重试、记录日志、发送报警等
常见上下文管理器 | 示例 | 自动管理的资源 |
---|---|---|
open() | with open('file.txt') as f: | 文件句柄 |
threading.Lock() | with lock: | 线程锁 |
contextlib.suppress() | with suppress(FileNotFoundError): | 忽略特定异常 |
tempfile.NamedTemporaryFile() | with NamedTemporaryFile() as tmp: | 临时文件 |
requests.Session() | with Session() as session: | HTTP 会话 |
12.5.4 使用 contextlib 简化上下文管理器创建
from contextlib import contextmanager
@contextmanager
def file_manager(filename, mode):
"""一个使用生成器函数创建的上下文管理器"""
try:
# 设置阶段 - 获取资源
f = open(filename, mode)
print(f"文件 {filename} 已打开")
# yield 语句将控制权传递给 with 块内的代码
yield f
finally:
# 清理阶段 - 释放资源
f.close()
print(f"文件 {filename} 已关闭")
# 使用自定义上下文管理器
with file_manager('example.txt', 'w') as file:
file.write('这是一个使用contextlib创建的上下文管理器示例')
12.6 异常处理最佳实践
掌握异常处理的模式和反模式对于编写健壮的代码至关重要。
12.6.1 不良实践与改进
# 不好的做法:过于宽泛的异常捕获
def bad_practice():
try:
# 大量不同类型的操作混在一起
config = open("config.ini").read()
settings = parse_config(config)
result = process_data(settings)
save_result(result)
except:
# 捕获所有异常,无法区分不同错误
print("出错了")
# 无法提供有价值的错误信息
# 无法针对性恢复
# 好的做法:精确捕获和处理异常
def good_practice():
config = None
try:
# 只包含读取配置文件的代码
config = open("config.ini")
config_text = config.read()
except FileNotFoundError:
# 针对性处理配置文件缺失
print("配置文件不存在,将使用默认配置")
config_text = DEFAULT_CONFIG
except PermissionError:
# 针对性处理权限问题
print("没有读取配置文件的权限")
# 可以请求提升权限或使用备用方案
return None
finally:
# 确保文件被关闭
if config:
config.close()
try:
# 解析配置的代码单独放在try块中
settings = parse_config(config_text)
except ValueError as e:
# 处理配置格式错误
print(f"配置格式错误: {e}")
return None
# 后续操作...
12.6.2 实际开发中的异常处理策略
# 分层异常处理示例 - Web应用请求处理
# 1. 底层数据访问层: 转换为应用层可理解的异常
def fetch_user_data(user_id):
try:
# 数据库操作
connection = get_db_connection()
cursor = connection.cursor()
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
result = cursor.fetchone()
return result
except MySQLError as e:
# 转换为应用级异常
if e.errno == 1045: # 访问被拒绝
raise DatabaseAccessError("数据库访问被拒绝") from e
elif e.errno == 2003: # 连接失败
raise DatabaseConnectionError("无法连接到数据库") from e
else:
raise DatabaseError(f"数据库错误: {e}") from e
finally:
# 资源清理
cursor.close()
connection.close()
# 2. 业务逻辑层: 处理应用级异常
def get_user_profile(user_id):
try:
user_data = fetch_user_data(user_id)
if not user_data:
# 应用逻辑异常
raise UserNotFoundError(f"用户ID {user_id} 不存在")
return format_user_profile(user_data)
except DatabaseError as e:
# 日志记录并决定是否传播
logger.error(f"获取用户数据失败: {e}")
# 可能的重试策略
if isinstance(e, DatabaseConnectionError) and retry_count < MAX_RETRIES:
return get_user_profile_with_retry(user_id, retry_count + 1)
# 传播异常供上层处理
raise
# 3. 接口层: 向用户展示友好错误
def api_get_user(request, user_id):
try:
profile = get_user_profile(user_id)
return JSONResponse(status_code=200, content=profile)
except UserNotFoundError:
# 返回适当的HTTP状态码
return JSONResponse(status_code=404, content={"error": "用户不存在"})
except DatabaseConnectionError:
# 返回服务暂时不可用
return JSONResponse(status_code=503, content={"error": "服务暂时不可用"})
except Exception as e:
# 意外错误: 记录并返回通用错误
logger.critical(f"未处理的错误: {e}", exc_info=True)
return JSONResponse(status_code=500, content={"error": "服务器内部错误"})
12.7 高级异常处理技术
12.7.1 使用装饰器简化异常处理
import functools
import time
def retry(max_attempts=3, delay=1):
"""一个用于自动重试的装饰器
参数:
max_attempts: 最大尝试次数
delay: 重试之间的延迟(秒)
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except (ConnectionError, TimeoutError) as e:
attempts += 1
if attempts >= max_attempts:
raise # 达到最大尝试次数,重新抛出异常
print(f"操作失败: {e},{delay}秒后重试 ({attempts}/{max_attempts})")
time.sleep(delay)
return None # 不会执行到这里
return wrapper
return decorator
# 使用重试装饰器
@retry(max_attempts=3, delay=2)
def connect_to_server(url):
"""连接到远程服务器,可能会失败"""
import random
if random.random() < 0.7: # 模拟70%的失败率
raise ConnectionError("连接服务器失败")
return "连接成功"
# 调用带重试功能的函数
try:
result = connect_to_server("https://example.com")
print(f"结果: {result}")
except ConnectionError:
print("连接服务器最终失败")
12.7.2 异常链与异常组
Python 3.10+ 引入了异常组(ExceptionGroup)和 except*语法,用于处理多个异常同时存在的情况:
from typing import List
# Python 3.10+ 特性:异常组
def process_multiple_tasks():
# 用于收集任务处理过程中的错误
exceptions: List[tuple[str, Exception]] = []
tasks = [("task1", 0), ("task2", 2), ("task3", "not_a_number")]
for task_name, value in tasks:
try:
# 尝试处理任务
print(f"处理任务 {task_name},输入值为 {value}")
result = 10 / value
print(f"任务 {task_name} 处理结果为 {result}")
except Exception as e:
exceptions.append((task_name, e))
# 如果有错误,以异常组的形式抛出
if exceptions:
raise ExceptionGroup("处理任务过程中发生错误",
[ValueError(f"任务 {name} 处理失败:{err}") for name, err in exceptions])
# 使用except*处理异常组
try:
process_multiple_tasks()
except* ZeroDivisionError as eg:
# 处理所有除零错误
print(f"除零错误: {eg.exceptions}") # 这里就可以抓到task1的异常
except* TypeError as eg:
# 处理所有类型错误
print(f"类型错误: {eg.exceptions}") # 这里就可以抓到task3的异常
except* Exception as eg:
# 处理其他所有错误
print(f"其他错误: {eg.exceptions}")
12.7.3 EAFP vs LBYL 编程风格
Python 通常推崇 EAFP(“Easier to Ask Forgiveness than Permission”)而非 LBYL(“Look Before You Leap”):
# LBYL风格(先检查后操作)
if 'key' in my_dict and my_dict['key'] is not None:
value = my_dict['key']
else:
value = 'default'
# EAFP风格(先操作后处理异常)
try:
value = my_dict['key']
except (KeyError, TypeError):
value = 'default'
第十三章: 高级数据处理
Python 提供了多种处理不同类型数据的工具和库,能够轻松处理结构化和非结构化数据。本章将深入探讨 Python 中常用的数据格式处理技术,包括 JSON、CSV、XML 和配置文件等。
13.1 JSON 处理
JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写,也易于机器解析和生成。Python 通过内置的 json
模块提供了 JSON 的序列化和反序列化功能。
方法 | 描述 |
---|---|
json.dump(obj, fp) | 将 Python 对象 obj 编码为 JSON 格式并写入文件 fp 。 |
json.dumps(obj) | 将 Python 对象 obj 编码为 JSON 格式并返回字符串。 |
json.load(fp) | 从文件 fp 读取 JSON 数据并解码为 Python 对象。 |
json.loads(s) | 将字符串 s 解码为 Python 对象。 |
13.1.1 基本操作
import json
# Python对象转JSON
data = {
"name": "张三",
"age": 30,
"is_student": False,
"courses": ["Python", "数据分析", "机器学习"],
"scores": {"Python": 95, "数据分析": 88}
}
# 转换为JSON字符串
json_str = json.dumps(data, ensure_ascii=False, indent=4)
print(json_str)
# 写入JSON文件
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
# 从JSON字符串解析
parsed_data = json.loads(json_str)
print(parsed_data["name"]) # 张三
# 从JSON文件读取
with open("data.json", "r", encoding="utf-8") as f:
loaded_data = json.load(f)
print(loaded_data["scores"]["Python"]) # 95
13.1.2 重要参数说明
参数 | 说明 | 用法示例 |
---|---|---|
ensure_ascii | 是否转义非 ASCII 字符,False 时保留原始字符 | json.dumps(data, ensure_ascii=False) |
indent | 缩进格式,美化输出 | json.dumps(data, indent=4) |
separators | 指定分隔符,用于紧凑输出 | json.dumps(data, separators=(',', ':')) |
sort_keys | 是否按键排序 | json.dumps(data, sort_keys=True) |
default | 指定序列化函数,处理不可序列化对象 | json.dumps(obj, default=lambda o: o.__dict__) |
13.1.3 自定义对象序列化
Python 的 json
模块默认无法直接序列化自定义类对象,但提供了多种方式解决:
import json
# ========== 方法一:提供default参数 ==========
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def person_to_dict(person):
"""将Person对象转换为字典"""
return {
"name": person.name,
"age": person.age
}
# 示例:使用default参数序列化自定义对象
person = Person("李四", 25)
json_str = json.dumps(person, default=person_to_dict, ensure_ascii=False)
print(json_str) # {"name": "李四", "age": 25}
# ========== 方法二:通过自定义编码器 ==========
class PersonEncoder(json.JSONEncoder):
"""自定义JSON编码器处理Person类"""
def default(self, obj):
if isinstance(obj, Person):
return {"name": obj.name, "age": obj.age}
return super().default(obj)
# 示例:使用自定义编码器序列化对象
json_str = json.dumps(person, cls=PersonEncoder, ensure_ascii=False)
print(json_str) # {"name": "李四", "age": 25}
# ========== 方法三:添加to_json方法 ==========
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def __repr__(self):
return f"Student('{self.name}', {self.grade})"
def to_json(self):
"""返回可JSON序列化的字典"""
return {
"name": self.name,
"grade": self.grade
}
# 示例:使用对象的to_json方法序列化
students = [Student("小明", 90), Student("小红", 88)]
json_str = json.dumps([s.to_json() for s in students], ensure_ascii=False)
print(json_str) # [{"name": "小明", "grade": 90}, {"name": "小红", "grade": 88}]
13.1.4 JSON 解码为自定义对象
import json
from typing import Dict
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def __str__(self):
return f"{self.name}({self.age})"
def dict_to_person(data: Dict) -> Person:
return Person(data["name"], data["age"])
# 使用 json.loads() 的 object_hook 参数将 JSON 字符串直接转换为自定义对象
# object_hook 的用途:
# 1. 自动将 JSON 解析出的字典转换为自定义类的实例
# 2. 在解析 JSON 时进行数据转换和验证
# 3. 简化从 JSON 到对象模型的映射过程
# 4. 避免手动创建对象的繁琐步骤
# 工作原理:
# - json.loads() 首先将 JSON 字符串解析为 Python 字典
# - 然后对每个解析出的字典调用 object_hook 函数
# - object_hook 函数返回的对象将替代原始字典
# 实际应用场景:
# - API 响应数据转换为应用程序对象模型
# - 配置文件解析为配置对象
# - 数据导入时的格式转换
person_data = '{"name": "Alice", "age": 25}'
person = json.loads(person_data, object_hook=dict_to_person)
print(type(person)) # <class '__main__.Person'>
print([person.name, person.age]) # ['Alice', 25]
print(person) # Alice(25)
13.1.5 处理复杂 JSON 数据
# 处理嵌套结构
nested_json = '''
{
"company": "ABC Corp",
"employees": [
{"name": "张三", "department": "技术", "skills": ["Python", "Java"]},
{"name": "李四", "department": "市场", "skills": ["营销", "策划"]}
],
"locations": {
"headquarters": "北京",
"branches": ["上海", "广州", "深圳"]
}
}
'''
data = json.loads(nested_json)
# 访问嵌套数据
print(data["employees"][0]["name"]) # 张三
print(data["employees"][0]["skills"][0]) # Python
print(data["locations"]["branches"][1]) # 广州
# 修改嵌套数据
data["employees"][0]["skills"].append("C++")
data["locations"]["branches"].append("成都")
# 保存修改后的数据
updated_json = json.dumps(data, ensure_ascii=False, indent=2)
print(updated_json)
13.1.6 性能优化
处理大型 JSON 文件时,可以使用流式解析来提高性能:
import ijson # 需安装: pip install ijson
# 流式解析大型JSON文件
with open("large_file.json", "rb") as f:
# 只提取特定字段
for item in ijson.items(f, "items.item"):
print(item["id"], item["name"])
# 处理一项后继续,不必载入整个文件
13.1.7 JSON Schema 验证
验证 JSON 数据是否符合预期格式:
from jsonschema import validate # 需安装: pip install jsonschema
# 定义JSON Schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
"email": {"type": "string", "format": "email"}
},
"required": ["name", "age"]
}
# 验证数据
valid_data = {"name": "张三", "age": 30, "email": "zhangsan@example.com"}
invalid_data = {"name": "李四", "age": -5}
try:
validate(instance=valid_data, schema=schema)
print("有效数据")
except Exception as e:
print(f"验证失败: {e}")
try:
validate(instance=invalid_data, schema=schema)
print("有效数据")
except Exception as e:
print(f"验证失败: {e}") # 会因age小于0而失败
13.2 CSV 处理
CSV (Comma-Separated Values) 是一种常见的表格数据格式。Python 的 csv
模块提供了读写 CSV 文件的功能,适用于处理电子表格和数据库导出数据。
在我们写入中文数据时,尽量将编码更换为
GBK
否则写入 CSV 会导致一些乱码问题
13.2.1 基本读写操作
import csv
# 写入CSV文件
data = [
["姓名", "年龄", "城市"],
["张三", 30, "北京"],
["李四", 25, "上海"],
["王五", 28, "广州"]
]
with open("people.csv", "w", newline="", encoding="gbk") as f:
writer = csv.writer(f)
writer.writerows(data) # 一次写入多行
# 逐行写入
with open("people_row.csv", "w", newline="", encoding="gbk") as f:
writer = csv.writer(f)
for row in data:
writer.writerow(row) # 一次写入一行
# 读取CSV文件
with open("people.csv", "r", encoding="gbk") as f:
reader = csv.reader(f)
for row in reader:
print(row)
13.2.2 使用字典处理 CSV 文件
# 使用字典写入CSV
import csv
dict_data = [
{"姓名": "张三", "年龄": 30, "城市": "北京"},
{"姓名": "李四", "年龄": 25, "城市": "上海"},
{"姓名": "王五", "年龄": 28, "城市": "广州"}
]
with open("people_dict.csv", "w", newline="", encoding="gbk") as f:
fieldnames = ["姓名", "年龄", "城市"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader() # 写入表头
writer.writerows(dict_data) # 写入多行数据
# 使用字典读取CSV
with open("people_dict.csv", "r", encoding="gbk") as f:
reader = csv.DictReader(f)
for row in reader:
print(f"{row['姓名']} ({row['年龄']}岁) 来自 {row['城市']}")
13.2.3 CSV 方言与格式化选项
# 自定义CSV方言
csv.register_dialect(
'tab_dialect',
delimiter='\t', # 使用制表符作为分隔符
quotechar='"', # 引号字符
escapechar='\\', # 转义字符
doublequote=False, # 不使用双引号转义
quoting=csv.QUOTE_MINIMAL # 最小引用策略
)
# 使用自定义方言
with open("tab_data.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f, dialect='tab_dialect')
writer.writerows(data)
# 常见格式化选项
with open("formatted.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(
f,
delimiter=',', # 分隔符
quotechar='"', # 引号字符
quoting=csv.QUOTE_NONNUMERIC, # 为非数值字段添加引号
escapechar='\\', # 转义字符
lineterminator='\n' # 行终止符
)
writer.writerows(data)
13.2.4 处理特殊情况
# 处理含有引号和逗号的数据
complex_data = [
["产品", "描述", "价格"],
["笔记本", "14\" 高配, i7处理器", 5999.99],
["手机", "5.5\" 屏幕, 双卡双待", 2999.50]
]
with open("complex.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f, quoting=csv.QUOTE_ALL) # 所有字段加引号
writer.writerows(complex_data)
# 跳过特定行
with open("complex.csv", "r", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader) # 跳过表头
for row in reader:
print(row)
# 处理缺失值
with open("missing.csv", "r", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader:
# 将空字符串转换为None
processed_row = [None if cell == '' else cell for cell in row]
print(processed_row)
13.2.5 CSV 文件的高级操作
# 过滤行
with open("people.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
# 筛选年龄大于25的记录
filtered_data = [row for row in reader if int(row["年龄"]) > 25]
# 计算统计值
with open("grades.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
# 计算平均分
scores = [float(row["分数"]) for row in reader]
avg_score = sum(scores) / len(scores)
print(f"平均分: {avg_score:.2f}")
# 合并多个CSV文件
import glob
def merge_csv_files(file_pattern, output_file):
# 获取所有匹配的文件
all_files = glob.glob(file_pattern)
with open(output_file, "w", newline="", encoding="utf-8") as outfile:
# 假设所有文件结构相同
for i, filename in enumerate(all_files):
with open(filename, "r", encoding="utf-8") as infile:
reader = csv.reader(infile)
if i == 0:
# 第一个文件,保留表头
for row in reader:
csv.writer(outfile).writerow(row)
else:
# 跳过后续文件的表头
next(reader, None)
for row in reader:
csv.writer(outfile).writerow(row)
# 使用示例
# merge_csv_files("data_*.csv", "merged_data.csv")
13.3 XML 处理
XML (eXtensible Markup Language) 是一种用于存储和传输数据的标记语言。Python 提供多种处理 XML 的方法,最常用的是 xml.etree.ElementTree
模块。
13.3.1 创建和写入 XML
import xml.etree.ElementTree as ET
# 创建XML根元素
root = ET.Element("data")
# 添加子元素
items = ET.SubElement(root, "items")
# 添加多个项目
for i in range(1, 4):
item = ET.SubElement(items, "item")
item.set("id", str(i)) # 设置属性
item.text = f"第{i}项" # 设置文本内容
# 添加嵌套元素
detail = ET.SubElement(item, "detail")
detail.text = f"项目{i}的详情"
# 创建用户信息部分
users = ET.SubElement(root, "users")
# 添加用户
user = ET.SubElement(users, "user")
user.set("name", "张三")
ET.SubElement(user, "age").text = "30"
ET.SubElement(user, "city").text = "北京"
user2 = ET.SubElement(users, "user")
user2.set("name", "李四")
ET.SubElement(user2, "age").text = "25"
ET.SubElement(user2, "city").text = "上海"
# 生成XML字符串
xml_str = ET.tostring(root, encoding="utf-8").decode("utf-8")
print(xml_str)
# 写入XML文件
tree = ET.ElementTree(root)
tree.write("data.xml", encoding="utf-8", xml_declaration=True)
13.3.2 解析和读取 XML
# 从文件解析XML
tree = ET.parse("data.xml")
root = tree.getroot()
# 从字符串解析XML
xml_string = '<data><item id="1">测试</item></data>'
root = ET.fromstring(xml_string)
# 获取元素标签和属性
print(f"根元素标签: {root.tag}")
# 遍历子元素
for child in root:
print(f"子元素: {child.tag}, 属性: {child.attrib}")
# 查找特定元素 - find()查找第一个匹配元素
items = root.find("items")
if items is not None:
# 使用findall()查找所有匹配的子元素
for item in items.findall("item"):
print(f"项目ID: {item.get('id')}, 内容: {item.text}")
# 获取嵌套元素
detail = item.find("detail")
if detail is not None:
print(f" 详情: {detail.text}")
# 使用XPath查询
# 查找所有用户名称
users = root.findall(".//user")
for user in users:
print(f"用户: {user.get('name')}")
print(f" 年龄: {user.find('age').text}")
print(f" 城市: {user.find('city').text}")
# 更复杂的XPath查询 - 查找北京的用户
beijing_users = root.findall(".//user[city='北京']")
for user in beijing_users:
print(f"北京用户: {user.get('name')}")
13.3.3 修改 XML
# 修改元素属性
user = root.find(".//user[@name='张三']")
if user is not None:
user.set("status", "active") # 添加新属性
# 修改子元素文本
age_elem = user.find("age")
if age_elem is not None:
age_elem.text = "31" # 修改年龄
# 添加新元素
ET.SubElement(user, "email").text = "zhangsan@example.com"
# 删除元素
users = root.find("users")
if users is not None:
for user in users.findall("user"):
if user.get("name") == "李四":
users.remove(user)
break
# 保存修改
tree.write("updated_data.xml", encoding="utf-8", xml_declaration=True)
13.3.4 命名空间处理
# 创建带命名空间的XML
root = ET.Element("data", {"xmlns:dt": "http://example.org/datatypes"})
# 添加带命名空间前缀的元素
item = ET.SubElement(root, "dt:item")
item.set("dt:type", "special")
item.text = "带命名空间的元素"
# 生成XML字符串
ns_xml = ET.tostring(root, encoding="utf-8").decode("utf-8")
print(ns_xml)
# 解析带命名空间的XML
ns_root = ET.fromstring(ns_xml)
# 使用带命名空间的XPath查询
namespaces = {"dt": "http://example.org/datatypes"}
ns_items = ns_root.findall(".//dt:item", namespaces)
for item in ns_items:
print(f"找到命名空间元素: {item.text}")
print(f"类型属性: {item.get('{http://example.org/datatypes}type')}")
13.4 配置文件处理
配置文件是应用程序保存设置和首选项的常用方式。Python 提供了多种处理不同格式配置文件的方法。
13.4.1 INI 配置文件处理
INI 文件是一种结构简单的配置文件格式,Python 通过 configparser
模块提供支持。
import configparser
# configparser是Python标准库中用于处理配置文件的模块
# 它可以读取、写入和修改类似INI格式的配置文件
# 配置文件通常包含节(sections)
# 如:[DEFAULT]
# 和每个节下的键值对(key-value pairs)
# 如:
# language = 中文
# theme = 默认
# auto_save = true
# save_interval = 10
# 创建一个新的配置解析器
config = configparser.ConfigParser()
# 添加默认节和配置项
config["DEFAULT"] = {
"language": "中文",
"theme": "默认",
"auto_save": "true",
"save_interval": "10"
}
# 添加应用设置节
config["应用设置"] = {}
config["应用设置"]["font_size"] = "14"
# 添加用户信息节
config["用户信息"] = {}
user_info = config["用户信息"] # 创建一个引用,方便添加多个配置项
user_info["username"] = "张三"
user_info["email"] = "zhangsan@example.com"
user_info["remember_password"] = "false" # 修改为标准布尔值字符串
# 添加数据库连接节
config["数据库"] = {}
config["数据库"]["host"] = "localhost"
config["数据库"]["port"] = "3306"
config["数据库"]["username"] = "root"
config["数据库"]["password"] = "123456"
# 将配置写入文件
with open("config.ini", "w", encoding="utf-8") as f:
config.write(f)
# 读取配置文件
config = configparser.ConfigParser()
config.read("config.ini", encoding="utf-8")
# 获取所有节名称
print("所有配置节:", config.sections()) # ['应用设置', '用户信息', '数据库']
# 获取节中的所有键
print("用户信息节中的所有键:", list(config["用户信息"].keys()))
# 获取特定配置值
print("用户名:", config["用户信息"]["username"]) # 张三
# 获取默认节中的值
print("默认语言:", config.get("应用设置", "language")) # 使用DEFAULT中的值
# 类型转换方法
font_size = config.getint("应用设置", "font_size")
auto_save = config.getboolean("DEFAULT", "auto_save", fallback=True) # 将"true"转换为True
save_interval = config.getint("DEFAULT", "save_interval")
print(f"字体大小: {font_size}, 类型: {type(font_size)}") # 字体大小: 14, 类型: <class 'int'>
print(f"自动保存: {auto_save}, 类型: {type(auto_save)}") # 自动保存: True, 类型: <class 'bool'>
# 修改配置
config["用户信息"]["username"] = "李四"
# 添加新配置
if "日志设置" not in config:
config["日志设置"] = {}
config["日志设置"]["log_level"] = "INFO"
config["日志设置"]["log_file"] = "app.log"
config["日志设置"]["max_size"] = "10MB"
# 保存修改后的配置
with open("updated_config.ini", "w", encoding="utf-8") as f:
config.write(f)
13.4.2 YAML 配置文件处理
YAML 是一种人类友好的数据序列化格式,需要安装 PyYAML
库。
# 需要安装PyYAML: pip install pyyaml
import yaml
# 创建YAML数据
data = {
"server": {
"host": "example.com",
"port": 8080
},
"database": {
"host": "localhost",
"port": 5432,
"username": "admin",
"password": "secret"
},
"logging": {
"level": "INFO",
"file": "/var/log/app.log"
},
"users": [
{"name": "张三", "role": "admin"},
{"name": "李四", "role": "user"}
]
}
# 写入YAML文件
with open("config.yaml", "w", encoding="utf-8") as f:
yaml.dump(data, f, default_flow_style=False, allow_unicode=True)
# 读取YAML文件
with open("config.yaml", "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
# 访问配置
print(f"服务器地址: {config['server']['host']}") # example.com
print(f"第一个用户: {config['users'][0]['name']}") # 张三
# 修改配置
config["server"]["port"] = 9090
config["users"].append({"name": "王五", "role": "user"})
# 保存修改
with open("updated_config.yaml", "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
13.4.3 使用环境变量作为配置
环境变量是一种灵活的配置方式,尤其适用于容器化应用。
import os
from dotenv import load_dotenv # 需安装: pip install python-dotenv
# 从.env文件加载环境变量
load_dotenv() # 默认加载当前目录下的.env文件
# 读取环境变量,提供默认值
database_url = os.environ.get("DATABASE_URL", "sqlite:///default.db")
debug_mode = os.environ.get("DEBUG", "False").lower() in ("true", "1", "yes")
port = int(os.environ.get("PORT", "8000"))
print(f"数据库URL: {database_url}")
print(f"调试模式: {debug_mode}")
print(f"端口: {port}")
# 创建.env文件示例
env_content = """
# 数据库设置
DATABASE_URL=postgresql://user:pass@localhost/dbname
# 应用设置
DEBUG=True
PORT=5000
"""
with open(".env.example", "w") as f:
f.write(env_content)
13.4.4 JSON 作为配置文件
JSON 也是一种常用的配置文件格式,尤其适合需要与 Web 应用共享配置的场景。
import json
import os
# 默认配置
default_config = {
"app_name": "MyApp",
"version": "1.0.0",
"debug": False,
"database": {
"host": "localhost",
"port": 5432,
"name": "app_db"
},
"cache": {
"enabled": True,
"ttl": 3600
}
}
# 配置文件路径
config_path = "app_config.json"
# 加载配置
def load_config():
# 如果配置文件存在,则加载它
if os.path.exists(config_path):
with open(config_path, "r", encoding="utf-8") as f:
return json.load(f)
# 否则使用默认配置并创建配置文件
else:
save_config(default_config)
return default_config
# 保存配置
def save_config(config):
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=4, ensure_ascii=False)
# 更新配置
def update_config(key, value):
config = load_config()
# 处理嵌套键 (如 "database.host")
if "." in key:
parts = key.split(".")
current = config
for part in parts[:-1]:
if part not in current:
current[part] = {}
current = current[part]
current[parts[-1]] = value
else:
config[key] = value
save_config(config)
return config
# 使用示例
config = load_config()
print(f"应用名称: {config['app_name']}")
print(f"数据库主机: {config['database']['host']}")
# 更新配置
update_config("database.host", "db.example.com")
update_config("cache.ttl", 7200)
# 重新加载配置
config = load_config()
print(f"更新后的数据库主机: {config['database']['host']}")
print(f"更新后的缓存TTL: {config['cache']['ttl']}")
13.5 正则表达式
正则表达式(通常缩写为 regex 或 regexp)是一种强大的文本处理工具。它使用一种专门的语法来定义 搜索模式 (pattern),然后可以用这个模式在文本中进行查找、匹配、提取或替换操作。正则表达式在各种编程任务中都极为有用,例如:
- 数据验证: 检查用户输入是否符合特定格式(如邮箱、手机号、日期)。
- 数据提取: 从大量非结构化文本(如日志文件、网页内容)中精确地抽取所需信息(如 IP 地址、错误代码、特定标签内容)。
- 文本替换: 对文本进行复杂的查找和替换操作,例如格式化代码、屏蔽敏感信息。
- 文本分割: 根据复杂的模式分割字符串。
Python 通过内置的 re
模块提供了对正则表达式的全面支持。
核心概念: 正则表达式的核心在于使用 元字符 (metacharacters) 和普通字符组合来定义模式。元字符是具有特殊含义的字符,而普通字符则匹配它们自身。
13.5.1 常用元字符和语法
以下是一些最常用的正则表达式元字符及其含义:
元字符 | 描述 | 示例模式 | 示例匹配 |
---|---|---|---|
. | 匹配 除换行符 \n 之外 的任何单个字符 (使用 re.DOTALL 标志可匹配换行符)。 | a.c | abc , a_c , a&c (但不匹配 ac ) |
^ | 匹配字符串的 开头。在多行模式 (re.MULTILINE ) 下,也匹配每行的开头。 | ^Hello | Hello world (但不匹配 Say Hello ) |
$ | 匹配字符串的 结尾。在多行模式 (re.MULTILINE ) 下,也匹配每行的结尾。 | world$ | Hello world (但不匹配 world say ) |
* | 匹配前面的元素 零次或多次 (贪婪模式)。 | go*d | gd , god , good , goooood |
+ | 匹配前面的元素 一次或多次 (贪婪模式)。 | go+d | god , good , goooood (但不匹配 gd ) |
? | 匹配前面的元素 零次或一次 (贪婪模式)。也用于将贪婪量词变为 非贪婪 (见后文)。 | colou?r | color , colour |
{n} | 匹配前面的元素 恰好 n 次。 | \d{3} | 123 (但不匹配 12 或 1234 ) |
{n,} | 匹配前面的元素 至少 n 次 (贪婪模式)。 | \d{2,} | 12 , 123 , 12345 |
{n,m} | 匹配前面的元素 至少 n 次,但不超过 m 次 (贪婪模式)。 | \d{2,4} | 12 , 123 , 1234 (但不匹配 1 或 12345 ) |
[] | 字符集。匹配方括号中包含的 任意一个 字符。 | [abc] | a 或 b 或 c |
[^...] | 否定字符集。匹配 不在 方括号中包含的任何字符。 | [^0-9] | 任何非数字字符 |
\ | 转义符。用于转义元字符,使其匹配其字面含义 (如 \. 匹配句点 . ),或用于引入特殊序列 (如 \d )。 | \$ | $ 字符本身 |
` | ` | 或 (OR) 运算符。匹配 ` | ` 左边或右边的表达式。 |
() | 分组。将括号内的表达式视为一个整体,用于应用量词、限制 ` | ` 的范围,或 捕获 匹配的子字符串。 | (ab)+ |
踩坑提示:
- 转义: 当需要匹配元字符本身时(如
.
、*
、?
),必须在前面加上反斜杠\
进行转义。例如,要匹配 IP 地址中的点,应使用\.
。 - 原始字符串 (Raw Strings): 在 Python 中定义正则表达式模式时,强烈建议 使用原始字符串(在字符串前加
r
),如r"\d+"
。这可以避免 Python 解释器对反斜杠进行自身的转义,从而简化正则表达式的书写,尤其是包含很多\
的模式。
13.5.2 特殊序列 (预定义字符集)
re
模块提供了一些方便的特殊序列来代表常见的字符集:
特殊序列 | 描述 | 等价于 | 示例 |
---|---|---|---|
\d | 匹配任何 Unicode 数字 字符 (包括 [0-9] 和其他语言的数字)。 | [0-9] (ASCII) | 1 , 5 |
\D | 匹配任何 非数字 字符。 | [^0-9] (ASCII) | a , _ , |
\s | 匹配任何 Unicode 空白 字符 (包括 、\t 、\n 、\r 、\f 、\v 等)。 | , \t | |
\S | 匹配任何 非空白 字符。 | a , 1 , . | |
\w | 匹配任何 Unicode 词语 字符 (字母、数字和下划线 _ )。 | [a-zA-Z0-9_] (ASCII) | a , B , 5 , _ |
\W | 匹配任何 非词语 字符。 | [^a-zA-Z0-9_] (ASCII) | ! , , @ |
\b | 匹配 词语边界 (word boundary)。这是一个零宽度断言,匹配词语字符 (\w ) 和非词语字符 (\W ) 之间,或词语字符和字符串开头/结尾之间的位置。 | \bword\b | |
\B | 匹配 非词语边界。 | \Bword\B |
13.5.3 贪婪模式 vs. 非贪婪模式
默认情况下,量词 (*
, +
, ?
, {n,}
, {n,m}
) 都是 贪婪 (Greedy) 的,它们会尽可能多地匹配字符。
场景: 从 HTML 标签 <b>Bold Text</b>
中提取 <b>
。
import re
text = "<b>Bold Text</b> Regular Text <b>Another Bold</b>"
# 贪婪模式 (默认)
greedy_pattern = r"<.*>" # . 匹配任何字符,* 匹配零次或多次
match_greedy = re.search(greedy_pattern, text)
if match_greedy:
# * 会一直匹配到字符串的最后一个 >
print(f"贪婪匹配结果: {match_greedy.group(0)}")
# 输出: 贪婪匹配结果: <b>Bold Text</b> Regular Text <b>Another Bold</b>
# 非贪婪模式 (在量词后加 ?)
non_greedy_pattern = r"<.*?>" # *? 匹配零次或多次,但尽可能少地匹配
match_non_greedy = re.search(non_greedy_pattern, text)
if match_non_greedy:
# *? 遇到第一个 > 就停止匹配
print(f"非贪婪匹配结果: {match_non_greedy.group(0)}")
# 输出: 非贪婪匹配结果: <b>
# 查找所有非贪婪匹配
all_matches_non_greedy = re.findall(non_greedy_pattern, text)
print(f"所有非贪婪匹配: {all_matches_non_greedy}")
# 输出: 所有非贪婪匹配: ['<b>', '</b>', '<b>', '</b>']
何时使用非贪婪模式?
当需要匹配从某个开始标记到 最近的 结束标记之间的内容时,通常需要使用非贪婪量词 (*?
, +?
, ??
, {n,}?
, {n,m}?
)。
13.5.4 分组与捕获
使用圆括号 ()
可以将模式的一部分组合起来,形成一个 分组 (Group)。分组有几个重要作用:
- 应用量词: 将量词作用于整个分组,如
(abc)+
匹配abc
,abcabc
等。 - 限制
|
范围: 如gr(a|e)y
匹配gray
或grey
。 - 捕获内容: 默认情况下,每个分组会 捕获 (Capture) 其匹配到的子字符串,以便后续引用或提取。
场景: 从 “Name: John Doe, Age: 30” 中提取姓名和年龄。
import re
text = "Name: John Doe, Age: 30; Name: Jane Smith, Age: 25"
# 定义带有捕获组的模式
# 第一个组 (\w+\s+\w+) 捕获姓名
# 第二个组 (\d+) 捕获年龄
pattern_capture = r"Name: (\w+\s+\w+), Age: (\d+)"
# 使用 findall 查找所有匹配项
# findall 返回一个列表,如果模式中有捕获组,列表元素是包含所有捕获组内容的元组
matches = re.findall(pattern_capture, text)
print(f"\n--- 使用 findall 提取分组 ---")
print(matches) # 输出: [('John Doe', '30'), ('Jane Smith', '25')]
# 使用 finditer 获取 Match 对象,可以更灵活地访问分组
print("\n--- 使用 finditer 访问分组 ---")
for match_obj in re.finditer(pattern_capture, text):
# match_obj.group(0) 或 group() 获取整个匹配
print(f"整个匹配: {match_obj.group(0)}")
# match_obj.group(1) 获取第一个捕获组的内容 (姓名)
print(f" 姓名 (组 1): {match_obj.group(1)}")
# match_obj.group(2) 获取第二个捕获组的内容 (年龄)
print(f" 年龄 (组 2): {match_obj.group(2)}")
# match_obj.groups() 获取所有捕获组组成的元组
print(f" 所有分组: {match_obj.groups()}")
# 非捕获组 (?:...)
# 如果只想分组而不捕获内容,可以使用非捕获组
pattern_non_capture = r"Name: (?:\w+\s+\w+), Age: (\d+)" # 第一个组不捕获
matches_nc = re.findall(pattern_non_capture, text)
print(f"\n--- 使用非捕获组的 findall ---")
print(matches_nc) # 输出: ['30', '25'] (只包含捕获组的内容)
反向引用 (Backreferences): 可以在模式内部或替换字符串中使用 \1
, \2
, … 来引用前面捕获组匹配到的文本。
场景: 查找重复的单词,如 “the the”。
text_repeat = "This is the the test sentence with repeated repeated words."
# \b 确保是完整的单词
# (\w+) 捕获第一个单词
# \s+ 匹配中间的空白
# \1 引用第一个捕获组匹配的内容
pattern_repeat = r"\b(\w+)\s+\1\b"
repeated_words = re.findall(pattern_repeat, text_repeat)
print(f"\n--- 查找重复单词 ---")
print(f"找到的重复单词: {repeated_words}") # 输出: ['the', 'repeated']
# 使用 sub 进行替换
# 将重复的单词替换为单个单词
corrected_text = re.sub(pattern_repeat, r"\1", text_repeat) # 使用 \1 引用捕获组
print(f"修正后的文本: {corrected_text}")
# 输出: This is the test sentence with repeated words.
13.5.5 re
模块核心函数
Python 的 re
模块提供了以下核心函数来执行正则表达式操作:
函数 | 描述 | 返回值 | 主要用途 |
---|---|---|---|
re.match(p, s, flags=0) | 从字符串 s 的 开头 尝试匹配模式 p 。 | 匹配成功返回 Match 对象,失败返回 None 。 | 验证字符串是否以特定模式开始。 |
re.search(p, s, flags=0) | 在 整个 字符串 s 中搜索模式 p 的 第一个 匹配项。 | 匹配成功返回 Match 对象,失败返回 None 。 | 在字符串中查找模式是否存在,并获取第一个匹配项的信息。 |
re.findall(p, s, flags=0) | 在字符串 s 中查找模式 p 的 所有非重叠 匹配项。 | 返回一个 列表。如果模式无捕获组,列表元素是匹配的字符串;如果有捕获组,列表元素是包含各捕获组内容的元组。 | 提取字符串中所有符合模式的子串或捕获组内容。 |
re.finditer(p, s, flags=0) | 与 findall 类似,但返回一个 迭代器 (iterator),迭代器中的每个元素都是一个 Match 对象。 | 返回一个迭代器,每个元素是 Match 对象。 | 处理大量匹配结果时更 内存高效,因为不需要一次性存储所有结果。可以方便地访问每个匹配的详细信息(如位置)。 |
re.sub(p, repl, s, count=0, flags=0) | 在字符串 s 中查找模式 p 的所有匹配项,并用 repl 替换它们。repl 可以是字符串(支持 \g<name> 或 \1 等反向引用)或函数。count 指定最大替换次数。 | 返回替换后的 新字符串。 | 执行查找和替换操作。repl 可以是函数,实现更复杂的替换逻辑。 |
re.split(p, s, maxsplit=0, flags=0) | 使用模式 p 作为分隔符来 分割 字符串 s 。maxsplit 指定最大分割次数。 | 返回一个 列表,包含分割后的子字符串。如果模式中有捕获组,捕获的内容也会包含在列表中。 | 根据复杂的模式分割字符串。 |
re.compile(p, flags=0) | 编译 正则表达式模式 p 为一个 模式对象 (Pattern Object)。 | 返回一个 Pattern 对象。 | 当一个模式需要被 多次 使用时,预先编译可以 提高性能。模式对象拥有与 re 模块函数同名的方法(如 pattern.search(s) )。 |
代码示例:
import re
text = "The quick brown fox jumps over the lazy dog. Phone: 123-456-7890. Email: test@example.com."
# 1. re.match() - 检查开头
pattern_start = r"The"
match_result = re.match(pattern_start, text)
if match_result:
print(f"match(): 字符串以 '{pattern_start}' 开头。匹配内容: '{match_result.group(0)}'")
else:
print(f"match(): 字符串不以 '{pattern_start}' 开头。")
match_fail = re.match(r"quick", text) # 不从开头匹配,所以失败
print(f"match() 失败示例: {match_fail}") # None
# 2. re.search() - 查找第一个匹配
pattern_word = r"fox"
search_result = re.search(pattern_word, text)
if search_result:
print(f"search(): 找到单词 '{pattern_word}'。 起始位置: {search_result.start()}, 结束位置: {search_result.end()}")
else:
print(f"search(): 未找到单词 '{pattern_word}'。")
# 3. re.findall() - 查找所有匹配
pattern_digits = r"\d+" # 查找所有数字序列
all_digits = re.findall(pattern_digits, text)
print(f"findall(): 找到的所有数字序列: {all_digits}") # ['123', '456', '7890']
pattern_email = r"(\w+)@(\w+\.\w+)" # 查找邮箱并捕获用户名和域名
email_parts = re.findall(pattern_email, text)
print(f"findall() 捕获组: {email_parts}") # [('test', 'example.com')]
# 4. re.finditer() - 迭代查找匹配对象
pattern_words_o = r"\b\w*o\w*\b" # 查找所有包含字母'o'的单词
print("finditer(): 查找包含 'o' 的单词:")
for match in re.finditer(pattern_words_o, text, re.IGNORECASE): # 使用 IGNORECASE 标志
print(f" 找到: '{match.group(0)}' at position {match.span()}")
# 5. re.sub() - 替换
pattern_phone = r"\d{3}-\d{3}-\d{4}"
# 将电话号码替换为 [REDACTED]
censored_text = re.sub(pattern_phone, "[REDACTED]", text)
print(f"sub() 替换电话号码: {censored_text}")
# 使用函数进行替换
def mask_email(match_obj):
username = match_obj.group(1)
domain = match_obj.group(2)
return f"{username[0]}***@{domain}" # 用户名只显示第一个字符
censored_email_text = re.sub(pattern_email, mask_email, text)
print(f"sub() 使用函数替换邮箱: {censored_email_text}")
# 6. re.split() - 分割
pattern_punct = r"[.,:;]\s*" # 按标点符号和后面的空格分割
parts = re.split(pattern_punct, text)
print(f"split(): 按标点分割: {parts}")
# 7. re.compile() - 编译模式
compiled_pattern = re.compile(r"l\w*y", re.IGNORECASE) # 编译查找以l开头y结尾的词
# 多次使用编译后的模式
match1 = compiled_pattern.search(text)
if match1:
print(f"compile() & search(): 找到 '{match1.group(0)}'")
match2 = compiled_pattern.findall("Actually, Lily is lovely.")
print(f"compile() & findall(): 找到 {match2}") # ['Lily', 'lovely']
13.5.6 Match 对象详解
当 re.match()
, re.search()
或 re.finditer()
中的一项成功匹配时,它们会返回一个 Match
对象。这个对象包含了关于匹配结果的详细信息。
Match 对象方法/属性 | 描述 | 示例 (假设 m = re.search(r"(\w+) (\d+)", "Order P123 45") ) |
---|---|---|
m.group(0) 或 m.group() | 返回整个匹配的字符串。 | 'P123 45' |
m.group(n) | 返回第 n 个捕获组匹配的字符串 (从 1 开始计数)。 | m.group(1) 返回 'P123' , m.group(2) 返回 '45' |
m.groups() | 返回一个包含所有捕获组匹配内容的 元组。 | ('P123', '45') |
m.groupdict() | 如果模式中使用了 命名捕获组 (?P<name>...) ,返回一个包含组名和匹配内容的字典。 | (需要命名组,如下例) |
m.start([group]) | 返回整个匹配或指定 group 的 起始索引 (包含)。 | m.start() 返回 6, m.start(1) 返回 6, m.start(2) 返回 11 |
m.end([group]) | 返回整个匹配或指定 group 的 结束索引 (不包含)。 | m.end() 返回 13, m.end(1) 返回 10, m.end(2) 返回 13 |
m.span([group]) | 返回一个包含 (start, end) 索引的 元组。 | m.span() 返回 (6, 13) , m.span(1) 返回 (6, 10) |
m.string | 传递给 match() 或 search() 的原始字符串。 | 'Order P123 45' |
m.re | 匹配时使用的已编译的模式对象 (Pattern object)。 |
命名捕获组示例:
import re
text = "Product ID: ABC-987, Quantity: 50"
# 使用 ?P<name> 定义命名捕获组
pattern_named = r"Product ID: (?P<product_id>[A-Z]+-\d+), Quantity: (?P<quantity>\d+)"
match = re.search(pattern_named, text)
if match:
print("\n--- 使用命名捕获组 ---")
# 通过组名访问捕获的内容
print(f"产品 ID: {match.group('product_id')}") # ABC-987
print(f"数量: {match.group('quantity')}") # 50
# groupdict() 返回包含所有命名组的字典
print(f"捕获字典: {match.groupdict()}") # {'product_id': 'ABC-987', 'quantity': '50'}
13.5.7 正则表达式标志 (Flags)
标志可以修改正则表达式的匹配行为。可以在 re
函数的 flags
参数中指定,或在编译时指定。多个标志可以使用 |
(按位或) 组合。
标志 | 简写 | 描述 |
---|---|---|
re.IGNORECASE | re.I | 进行 不区分大小写 的匹配。 |
re.MULTILINE | re.M | 使 ^ 和 $ 匹配 每行的开头和结尾,而不仅仅是整个字符串的开头和结尾。 |
re.DOTALL | re.S | 使元字符 . 能够匹配 包括换行符 \n 在内 的任何字符。 |
re.VERBOSE | re.X | 详细模式。允许在模式字符串中添加 空白和注释 以提高可读性,此时模式中的空白会被忽略,# 后到行尾的内容视为注释。 |
re.ASCII | re.A | 使 \w , \W , \b , \B , \s , \S 只匹配 ASCII 字符,而不是完整的 Unicode 字符集 (Python 3 默认匹配 Unicode)。 |
re.UNICODE (默认) | re.U | 使 \w , \W , \b , \B , \s , \S , \d , \D 匹配完整的 Unicode 字符集。这是 Python 3 的默认行为。 |
示例:
import re
text_multi = """first line
second line
THIRD line"""
# re.I (忽略大小写)
print(f"\n--- Flags 示例 ---")
print(f"re.I: {re.findall(r'line', text_multi, re.IGNORECASE)}") # ['line', 'line', 'line']
# re.M (多行模式)
print(f"re.M (^): {re.findall(r'^s.*', text_multi, re.MULTILINE | re.IGNORECASE)}") # ['second line']
print(f"re.M ($): {re.findall(r'line$', text_multi, re.MULTILINE | re.IGNORECASE)}") # ['line', 'line', 'line']
# re.S (DOTALL)
text_dot = "Hello\nWorld"
print(f"re.S (.): {re.search(r'Hello.World', text_dot, re.DOTALL)}") # 匹配成功
print(f"No re.S (.): {re.search(r'Hello.World', text_dot)}") # 匹配失败 (None)
# re.X (VERBOSE)
# 一个复杂的邮箱模式,使用 VERBOSE 模式添加注释和空格
pattern_verbose = r"""
^ # 匹配字符串开头
[\w\.\-]+ # 用户名部分 (字母、数字、下划线、点、连字符)
@ # @ 符号
([\w\-]+\.)+ # 域名部分 (允许子域名,如 mail.example.)
[a-zA-Z]{2,7} # 顶级域名 (如 .com, .org)
$ # 匹配字符串结尾
"""
email = "test.user-1@sub.example.com"
match_verbose = re.match(pattern_verbose, email, re.VERBOSE)
print(f"re.X (VERBOSE): {'匹配成功' if match_verbose else '匹配失败'}") # 匹配成功
13.5.8 实际应用场景示例
场景 1: 验证中国大陆手机号 (简单示例)
import re
def is_valid_china_mobile(phone_number: str) -> bool:
"""简单验证中国大陆手机号码 (11位数字,常见号段)"""
# 模式解释:
# ^ 匹配字符串开头
# (?:...) 非捕获组
# 1[3-9] 第一位是1,第二位是3到9
# \d{9} 后面跟9位数字
# $ 匹配字符串结尾
pattern = r"^(?:1[3-9])\d{9}$"
if re.match(pattern, phone_number):
return True
else:
return False
print("\n--- 手机号验证 ---")
print(f"13812345678: {is_valid_china_mobile('13812345678')}") # True
print(f"12012345678: {is_valid_china_mobile('12012345678')}") # False (号段不对)
print(f"1381234567: {is_valid_china_mobile('1381234567')}") # False (位数不够)
print(f"138123456789: {is_valid_china_mobile('138123456789')}")# False (位数太多)
注意: 实际手机号验证可能需要更复杂的规则或查询号段数据库。
场景 2: 从 Apache/Nginx 日志中提取 IP 地址和请求路径
import re
log_line = '192.168.1.101 - - [03/May/2025:17:20:01 +0900] "GET /index.html HTTP/1.1" 200 1542 "-" "Mozilla/5.0..."'
# 模式解释:
# ^([\d\.]+) 捕获开头的 IP 地址 (数字和点的组合)
# \s+-\s+-\s+ 匹配中间的 ' - - ' 部分
# \[.*?\] 匹配并忽略方括号内的时间戳 (非贪婪)
# \s+" 匹配时间戳后的空格和双引号
# (GET|POST|PUT|DELETE|HEAD) \s+ 捕获请求方法 (GET, POST 等) 和空格
# ([^\s"]+) 捕获请求路径 (非空格、非双引号的字符)
# \s+HTTP/[\d\.]+" 捕获 HTTP 版本部分
# .* 匹配剩余部分
pattern_log = r'^([\d\.]+) \s+-\s+-\s+ \[.*?\] \s+"(GET|POST|PUT|DELETE|HEAD)\s+([^\s"]+)\s+HTTP/[\d\.]+" .*'
match = re.match(pattern_log, log_line)
if match:
ip_address = match.group(1)
method = match.group(2)
path = match.group(3)
print("\n--- 日志解析 ---")
print(f"IP 地址: {ip_address}") # 192.168.1.101
print(f"请求方法: {method}") # GET
print(f"请求路径: {path}") # /index.html
else:
print("日志格式不匹配")
场景 3: 将 Markdown 样式的链接 [text](url)
转换为 HTML <a>
标签
import re
markdown_text = "这是一个链接 [Google](https://www.google.com) 和另一个 [Python 官网](http://python.org) 的例子。"
# 模式解释:
# \[ 匹配字面量 '['
# ([^\]]+) 捕获链接文本 (不是 ']' 的任意字符一次或多次)
# \] 匹配字面量 ']'
# \( 匹配字面量 '('
# ([^\)]+) 捕获 URL (不是 ')' 的任意字符一次或多次)
# \) 匹配字面量 ')'
pattern_md_link = r'\[([^\]]+)\]\(([^\)]+)\)'
# 使用 re.sub 和反向引用 \1, \2 进行替换
html_text = re.sub(pattern_md_link, r'<a href="\2">\1</a>', markdown_text)
print("\n--- Markdown 转 HTML 链接 ---")
print(f"原始 Markdown: {markdown_text}")
print(f"转换后 HTML: {html_text}")
# 输出: 这是一个链接 <a href="https://www.google.com">Google</a> 和另一个 <a href="http://python.org">Python 官网</a> 的例子。