Skip to main content

09-单例模式

在 Python 中,单例模式是一种常用的设计模式,确保一个类只有一个实例,并提供一个全局访问点。这在需要全局共享状态、配置管理、资源管理等场景中非常有用。

核心概念

  • 唯一性:类在应用程序的生命周期中只会创建一个实例。
  • 全局访问:通过类提供的一个访问方法,所有使用者都能得到同一个实例。
  • 延迟实例化(可选):在需要时才创建实例,减少不必要的资源占用。

单例模式的实现方式

1.使用类变量

class Singleton:
_instance = None

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


# 测试
a = Singleton()
b = Singleton()
print(a is b) # True

原理:

  • _instance 类变量保存实例。
  • __new__ 方法控制实例创建,只有在首次调用时创建实例,该方法在创建实例时自动被调用,该方法被调用后会自动调用 __init__ 初始化方法。

2.使用装饰器

def singleton(cls):
instances = {} # 使用字典来承接类和类实例对象的映射关系

def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls] # 返回该cls类的实例对象

return get_instance


@singleton
class Singleton:
pass


a = Singleton()
b = Singleton()
print(a is b) # True

原理:

  • instances 字典保存类与实例的映射。
  • 装饰器确保只创建一个类实例。

3.使用模块(Python独有)

Python 的模块天然是单例,因为模块在第一次导入时会被缓存。

# singleton_module.py
class Singleton:
def __init__(self):
self.value = 42

singleton = Singleton()

使用时:

from singleton_module import singleton
print(singleton.value) # 42

原理:

  • 模块只会被导入和执行一次,其内容被缓存。
  • 无论在哪引入该模块,都会引用同一个实例。

4.使用 metaclass 元类

class SingletonMeta(type):
_instances = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]


class Singleton(metaclass=SingletonMeta):
pass


# 测试
a = Singleton()
b = Singleton()
print(a == b) # True

原理:

  • metaclass 控制类的实例化过程。
  • __call__ 拦截实例创建逻辑,确保只创建一个实例。

补充: hasattr(obj, str)函数

在 Python 中,hasattr() 是一个内置函数,用于判断对象是否有某个属性。它的语法是:

hasattr(object, name)
  • object:要检查的对象。
  • name:字符串,表示属性名。

如果对象中存在这个属性,hasattr() 返回 True,否则返回 False

例子:

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

person = Person("Alice")

print(hasattr(person, "name")) # True,因为 person 对象有 name 属性
print(hasattr(person, "age")) # False,因为没有 age 属性
print(hasattr(person, "__init__")) # True,__init__ 是类的方法,也是一个属性

注意事项:

  • hasattr() 实际上会尝试访问这个属性,所以如果属性的访问器中有副作用(比如抛出异常或修改状态),调用 hasattr() 可能会触发这些副作用。
  • 如果属性存在,但访问时抛出异常,hasattr() 会返回 False

**小技巧:**你可以结合 getattr() 使用,在确认属性存在后再安全地获取其值:

if hasattr(person, "name"):
name = getattr(person, "name")
print(name)

5.使用 threading.Lock 保证线程安全

在多线程环境下,使用锁来保证单例安全。

import threading


class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
if cls._instance is None: # 双重检查
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance


# 测试
a = Singleton()
b = Singleton()
print(id(a)) # 1734385437776
print(id(b)) # 1734385437776

原理:

  • threading.Lock() 控制对类实例的访问。
  • 双重检查确保指在需要时加锁,提供性能。

6.使用 @classmethod

class Singleton:
_instance = None

@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance

# 测试
a = Singleton.get_instance()
b = Singleton.get_instance()
print(a is b) # True

原理

  • get_instance 是一个类方法,用于管理类的唯一实例。
  • _instance 类变量保存唯一实例,确保只在第一次调用时创建。

优缺点

  • 优点:实现简单,逻辑清晰。
  • 缺点:实例获取方式稍显繁琐,需要通过类方法调用。
  • 线程安全:不安全,需要加锁处理多线程场景。

7.使用 __new__(基础实现)

这种方式其实和我们前面提到的类变量实现类似,但更直接。

class Singleton:
_instance = None

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

def __init__(self):
# 这里的 __init__ 只会在首次实例化时调用
self.value = 42

# 测试
a = Singleton()
b = Singleton()
print(a is b) # True
print(a.value) # 42
b.value = 99
print(a.value) # 99 (说明是同一个实例)

原理

  • __new__ 是 Python 中用于控制实例创建的特殊方法,它在 __init__ 执行前执行。
  • 通过类变量 _instance 控制只创建一个实例。

优缺点

  • 优点:简单直接,兼容普通类的初始化方式。
  • 缺点:没有线程安全,需要在多线程中加锁。

扩展:加锁保证线程安全(结合 __new__

如果需要在多线程环境中确保单例安全,我们可以在 __new__ 中加锁实现双重检查。

import threading

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock: # 加锁保证线程安全
if cls._instance is None: # 双重检查
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance

# 测试
a = Singleton()
b = Singleton()
print(a is b) # True

总结这两种方法:

实现方式简洁性灵活性线程安全使用场景
@classmethod中等较灵活明确通过类方法获取实例
__new__(基础)简单常规类初始化场景
__new__(加锁)中等多线程环境

这两种方法确实也是很常用的实现方式,尤其是 __new__,可以说是 Python 中实现单例模式的经典方法。

如果你更喜欢通过类方法显式管理实例,用 @classmethod; 如果你希望更像普通类的用法,但仍保持单例特性,用 __new__

你觉得哪种方式更适合你的需求?或者需要帮你完善某个实现吗? 😄

总结

实现方式简洁性灵活性线程安全Python特性
类变量简单
装饰器简洁中等
模块最简
元类 metaclass稍复杂
带锁的类变量中等

如果你追求简单性,直接用模块类变量实现; 如果需要强灵活性,metaclass 实现非常合适; 如果是多线程场景,建议用带锁实现

需要我帮你分析哪种实现更适合你的项目吗? 😊