OOP元类与单例模式

元类是 Python 面向对象编程的高级特性,核心作用是 “控制类的创建和实例化过程”;单例模式则是基于元类(或其他方式)实现的经典设计模式,确保类仅创建一个实例。

本章学习知识点

  • 元类:type(默认元类)、元类的__init__(控制类创建)、元类的__call__(控制实例化)

  • 单例模式:类方法实现、装饰器实现、元类实现

  • 辅助工具:exec函数(模拟类的名称空间构建)

  • 知识点核心速查表

    知识点模块 核心功能 关键方法 / 工具 适用场景
    元类 控制类的创建和实例化过程 type(默认)、自定义元类(继承 type)、__init____call__exec 强制类遵循创建规则、统一实例化逻辑
    单例模式 确保类仅一个实例 类方法、装饰器、元类 数据库连接池、配置对象等资源共享场景

一、元类

Python 中 “一切皆对象”:用class定义的类本身也是对象,而元类就是负责创建 “类对象” 的类(即 “类的类”)。

  • 默认元类:所有类的默认元类是typetype创建了所有类,包括intstr、自定义类);
  • 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

  1. 元类的init:控制类的创建

    • 触发时机:类对象被创建时(class关键字定义类的瞬间);
    • 核心作用:初始化类对象,可添加类的创建规则(如类名规范、必须包含特定属性 / 方法);
    • 示例见「自定义元类」,核心是通过__init__校验类的创建规则。
  2. 元类的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__ 数据库连接池、配置对象等资源共享场景
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐