14、Python面向对象编程-元类与单例模式
自定义元类需继承type,通过metaclass参数指定给目标类,可重写元类方法控制类的创建规则。if not class_name.istitle(): raise TypeError('类名必须首字母大写')if not cls.__doc__ or len(cls.__doc__.strip()) == 0: raise TypeError('类文档必须填写')print("类名:", cl
OOP元类与单例模式
元类是 Python 面向对象编程的高级特性,核心作用是 “控制类的创建和实例化过程”;单例模式则是基于元类(或其他方式)实现的经典设计模式,确保类仅创建一个实例。
本章学习知识点
-
元类:
type(默认元类)、元类的__init__(控制类创建)、元类的__call__(控制实例化) -
单例模式:类方法实现、装饰器实现、元类实现
-
辅助工具:
exec函数(模拟类的名称空间构建) -
知识点核心速查表
知识点模块 核心功能 关键方法 / 工具 适用场景 元类 控制类的创建和实例化过程 type(默认)、自定义元类(继承 type)、 __init__、__call__、exec强制类遵循创建规则、统一实例化逻辑 单例模式 确保类仅一个实例 类方法、装饰器、元类 数据库连接池、配置对象等资源共享场景
一、元类
Python 中 “一切皆对象”:用class定义的类本身也是对象,而元类就是负责创建 “类对象” 的类(即 “类的类”)。
- 默认元类:所有类的默认元类是
type(type创建了所有类,包括int、str、自定义类); exec函数:执行字符串形式的 Python 代码,可模拟类的名称空间构建(类定义的本质是执行类体代码并收集属性到名称空间)。
1.1、exec 函数
-
exec是理解元类创建类的前置工具,用于模拟类的名称空间构建过程。 -
示例
# 用exec模拟类的名称空间构建 # 1. 定义类体代码(字符串形式) class_body_code = ''' # 类属性 version = "1.0" # 类方法 def say_hello(self): print(f"Hello, {self.name}!") # 初始化方法 def __init__(self, name): self.name = name ''' # 2. 空字典存储执行后的属性(模拟类的名称空间) class_namespace = {} # 用于存储执行后的变量和函数(模拟名称空间) # 3. 执行字符串代码,结果存入名称空间 exec(class_body_code, {}, class_namespace) # 打印名称空间(包含类属性、方法) print("类的名称空间:", class_namespace.keys()) # 输出:dict_keys(['version', 'say_hello', '__init__']) # 对比:正常类定义的__dict__就是名称空间 class Test: version = "1.0" def say_hello(self): print(f"Hello, {self.name}!") def __init__(self, name): self.name = name print("正常类的__dict__:", [k for k in Test.__dict__.keys() if not k.startswith('__')]) # 输出:['version', 'say_hello', '__init__']
1.2、创建类的两种方式
类的创建本质是调用元类的过程,需具备三个要素:类名、基类(父类)、类的名称空间。
1.2.1、使用默认元类 type
直接调用type(类名, 基类元组, 名称空间字典)即可创建类,等同于class关键字定义。
# 步骤1:定义创建类的三要素
class_name = "Person" # 类名(字符串)
class_bases = (object,) # 基类(元组,多继承时放多个父类)
class_namespace = {} # 空字典存储类的名称空间
# 步骤2:定义类体代码(字符串)
class_body_code = '''
country = "China"
def __init__(self, name, age):
self.name = name
self.age = age
def get_info(self):
return f"{self.name}({self.age}岁),来自{self.country}"
'''
# 步骤3:执行代码填充名称空间
exec(class_body_code, {}, class_namespace)
# 步骤4:调用type创建类(核心:元类type创建类对象)
Person = type(class_name, class_bases, class_namespace)
# 测试创建的类
p = Person("张三", 25)
print(p.get_info()) # 输出:张三(25岁),来自China
print(type(Person)) # 输出:<class 'type'>(验证Person是type的实例)
1.2.2、自定义元类(继承 type)
自定义元类需继承type,通过metaclass参数指定给目标类,可重写元类方法控制类的创建规则。
class MyMeta(type):
def __init__(cls, class_name, class_metaclass, class_namespace):
if not class_name.istitle(): raise TypeError('类名必须首字母大写')
if not cls.__doc__ or len(cls.__doc__.strip()) == 0: raise TypeError('类文档必须填写')
print("类名:", class_name)
print("基类:", class_metaclass)
print("名称空间:", class_namespace.keys())
# 调用父类初始化(必须保留)
super().__init__(class_name, class_metaclass, class_namespace)
# 使用自定义元类创建类(通过metaclass参数指定)
class Person(object, metaclass=MyMeta):
'''这是类的文档'''
school = '吉林大学'
def __init__(self, name):
self.name = name
s1 = Person('张三')
print(s1.name)
print(type(Person))
# 执行结果(类创建时触发元类的__init__):
# 类名: Person
# 基类: (<class 'object'>,)
# 名称空间: dict_keys(['__module__', '__qualname__', '__firstlineno__', '__doc__', 'school', '__init__', '__static_attributes__'])
1.3、元类核心方法:call
-
元类的init:控制类的创建
- 触发时机:类对象被创建时(
class关键字定义类的瞬间); - 核心作用:初始化类对象,可添加类的创建规则(如类名规范、必须包含特定属性 / 方法);
- 示例见「自定义元类」,核心是通过
__init__校验类的创建规则。
- 触发时机:类对象被创建时(
-
元类的call:控制类的实例化
-
触发时机:类被实例化时(
类名()); -
核心作用:自定义实例化流程(如属性校验、单例控制、修改初始化参数);
-
底层逻辑:
类名(*args, **kwargs)→ 调用元类的__call__→__call__内部调用类的__new__创建空对象 → 调用类的__init__初始化对象。class MyMeta(type): """自定义元类:控制实例化过程""" def __call__(cls, *args, **kwargs): # 步骤1:创建空对象(调用类的__new__) obj = cls.__new__(cls) # 步骤2:初始化对象(调用类的__init__) cls.__init__(obj, *args, **kwargs) # 自定义逻辑:校验属性 if hasattr(obj, "age") and not isinstance(obj.age, int): raise TypeError("age必须是整数!") # 步骤3:返回初始化后的对象 return obj # 使用元类 class User(object, metaclass=MyMeta): def __init__(self, name, age): self.name = name self.age = age # 测试:正常实例化 u1 = User("王五", 30) print(u1.age) # 输出:30 # 不符合规则的实例化会报错 # u2 = User("赵六", "30") # age是字符串,触发TypeError
-
二、单例模式
- 定义与适用场景
- 定义:确保一个类只能创建一个实例对象,多次实例化返回同一个内存地址的对象;
- 适用场景:数据库连接池、配置对象、日志对象等(避免重复创建对象浪费资源)。
2.1、单例模式优缺点
- 单例模式的优点
- 由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
- 全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;
- 单例可长驻内存,减少系统开销。
- 单例模式的应用举例
- 生成全局惟一的序列号;
- 访问全局复用的惟一资源,如磁盘、总线等;
- 单个对象占用的资源过多,如数据库等;
- 系统全局统一管理,如Windows下的Task Manager;
- 网站计数器。
- 单例模式的缺点
- 单例模式的扩展是比较困难的;
- 赋于了单例以太多的职责,某种程度上违反单一职责原则;
- 单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试;
- 单例模式在某种情况下会导致“资源瓶颈”。
2.2、单例实现方式
-
类方法实现
通过类属性存储唯一实例,提供类方法用于获取实例,判断类属性是否为空来决定创建新实例还是返回已有实例。
class Mysql: """数据库连接类:类方法实现单例""" # 私有类属性:存储唯一实例 __instance = None def __init__(self, host="127.0.0.1", port=3306): self.host = host self.port = port @classmethod def get_instance(cls): """获取唯一实例的类方法""" if cls.__instance is None: cls.__instance = cls() # 首次调用创建实例 return cls.__instance # 测试:多次调用返回同一个实例 sql1 = Mysql.get_instance() sql2 = Mysql.get_instance() print(f"sql1与sql2内存地址是否相同:{id(sql1) == id(sql2)}") # 输出:True # 直接实例化仍会创建新对象(缺点) sql3 = Mysql() print(f"sql1与sql3内存地址是否相同:{id(sql1) == id(sql3)}") # 输出:False # 连接数据库或者其它连接方式时,定义好一次,以后直接调用它,重复利用节省资源浪费 -
装饰器实现
核心思路:装饰器内部存储唯一实例,拦截类的实例化调用,无参数时返回唯一实例,有参数时创建新实例。
from functools import wraps def singleton_decorator(cls): """单例装饰器""" # 存储唯一实例 _instance = cls() @wraps(cls) # 保留原类的元信息(如类名、文档注释) def wrapper(*args, **kwargs): # 无参数时返回唯一实例,有参数时创建新实例 if not args and not kwargs: return _instance return cls(*args, **kwargs) return wrapper # 使用装饰器实现单例 @singleton_decorator class Redis: """Redis连接类:装饰器实现单例""" def __init__(self, host="127.0.0.1", port=6379): self.host = host self.port = port # 测试 r1 = Redis() # 无参数,返回唯一实例 r2 = Redis() # 无参数,返回同一实例 print(f"r1与r2内存地址是否相同:{id(r1) == id(r2)}") # 输出:True r3 = Redis("192.168.1.1", 6379) # 有参数,创建新实例 print(f"r1与r3内存地址是否相同:{id(r1) == id(r3)}") # 输出:False -
元类实现(推荐)
核心思路:通过元类的
__call__方法控制实例化过程,存储唯一实例,确保无参数时返回同一实例。class SingletonMeta(type): """单例元类""" def __init__(cls, class_name, class_bases, class_namespace): # 初始化时创建唯一实例(默认参数) cls._instance = cls.__new__(cls) cls.__init__(cls._instance) # 无参数初始化 super().__init__(class_name, class_bases, class_namespace) def __call__(cls, *args, **kwargs): # 无参数时返回唯一实例 if not args and not kwargs: return cls._instance # 有参数时创建新实例 obj = cls.__new__(cls) cls.__init__(obj, *args, **kwargs) return obj # 使用元类实现单例 class Config(metaclass=SingletonMeta): """配置类:元类实现单例""" def __init__(self, env="dev"): self.env = env # 环境:dev/test/prod self.debug = True if env == "dev" else False # 测试 c1 = Config() # 无参数,返回唯一实例(env=dev) c2 = Config() # 无参数,返回同一实例 print(f"c1与c2内存地址是否相同:{id(c1) == id(c2)}") # 输出:True print(f"c1的环境:{c1.env},调试模式:{c1.debug}") # 输出:dev,True c3 = Config("prod") # 有参数,创建新实例 print(f"c1与c3内存地址是否相同:{id(c1) == id(c3)}") # 输出:False print(f"c3的环境:{c3.env},调试模式:{c3.debug}") # 输出:prod,False
三、总结
-
元类核心要点
- 元类是 “类的类”,默认元类是
type,自定义元类需继承type; exec函数可模拟类的名称空间构建,是理解type创建类的关键;- 元类的
__init__控制类的创建规则,__call__控制类的实例化过程; - 使用元类需通过
metaclass参数指定,适用于需要强制类遵循统一规则的场景。
- 元类是 “类的类”,默认元类是
-
单例模心式核心要点
- 核心目标:确保类仅一个实例,节省资源;
- 三种实现方式对比:
- 类方法:简单易理解,但无法阻止直接实例化创建新对象;
- 装饰器:灵活,可复用,但需注意保留原类元信息(
@wraps); - 元类:最彻底,可控制所有实例化行为,推荐用于核心类的单例实现;
- 适用场景:数据库连接、配置对象、日志对象等无需多实例的场景。
-
最终知识点汇总表
知识点模块 核心功能 关键方法 / 工具 适用场景 元类 控制类的创建和实例化过程 type(默认)、自定义元类(继承 type)、 __init__(类创建)、__call__(实例化)、exec强制类遵循创建规则、统一实例化逻辑 单例模式 确保类仅一个实例 类方法( @classmethod)、装饰器(@wraps)、元类(__call__)数据库连接池、配置对象等资源共享场景
更多推荐


所有评论(0)