python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python 单例

Python中实现单例的几种方式小结

作者:zzwq.

单例模式是一种常用的设计模式,本文就来介绍一下Python中实现单例的几种方式小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要控制资源(如数据库连接、日志记录器、配置管理等)的场景下非常有用。Python 作为一门动态语言,提供了多种灵活的方式来实现单例模式。本文将详细介绍几种常见的实现方法,并分析各自的优缺点。

一、单例模式简介

单例模式的核心要点:
 1. 一个类只能创建一个实例。
 2. 该实例必须由类自身负责创建。
 3. 必须向整个系统提供这个实例。

在 Python 中,由于对象的创建和初始化过程相对透明,实现单例需要一些技巧。下面我们逐一探讨。

二、Python实现单例的几种方式

1. 模块级单例

Python 的模块天然就是单例的:当模块第一次被导入时,会生成一个模块对象,随后再次导入同一模块时,Python 会直接返回缓存的模块对象。利用这个特性,我们只需将需要单例的类或对象定义在模块中,然后导入即可。

示例:

# singleton_module.py
class SingletonClass:
    def __init__(self):
        self.value = 0

    def do_something(self):
        print(f"Doing something, value = {self.value}")

# 创建一个全局实例
instance = SingletonClass()

在其他地方使用时:

from singleton_module import instance

# 无论导入多少次,instance 都是同一个对象
print(instance is instance)  # True

优点:

缺点:

2. 使用类装饰器

通过类装饰器,我们可以包装一个类,使其只能生成一个实例。

示例:

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

# 测试
a = MyClass("Alice")
b = MyClass("Bob")
print(a is b)   # True
print(a.name)   # Alice
print(b.name)   # Alice

优点:

缺点:

3. 使用元类

元类(metaclass)是 Python 中用于创建类的类。通过自定义元类,我们可以在创建类时控制实例化行为。

示例:

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 MyClass(metaclass=SingletonMeta):
    def __init__(self, name):
        self.name = name

# 测试
a = MyClass("Alice")
b = MyClass("Bob")
print(a is b)   # True
print(a.name)   # Alice
print(b.name)   # Alice

优点:

缺点:

线程安全版本:

import threading

class ThreadSafeSingletonMeta(type):
    _instances = {}
    _lock = threading.Lock()

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

4. 重写 __new__ 方法

Python 中对象的创建由 __new__ 方法控制,我们可以通过重写 __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, name):
        # 注意:每次调用 __init__ 都会执行,可能导致属性被覆盖
        self.name = name

# 测试
a = Singleton("Alice")
b = Singleton("Bob")
print(a is b)   # True
print(a.name)   # Bob
print(b.name)   # Bob

优点:

缺点:

改进版:

class Singleton:
    _instance = None
    _initialized = False

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

    def __init__(self, name):
        if not self._initialized:
            self.name = name
            self._initialized = True

5. 使用 @classmethod 的类方法

通过类方法提供实例的获取方式,将构造函数私有化(Python 中无法真正私有,但可以用约定)。

示例:

class Singleton:
    _instance = None

    def __init__(self):
        # 防止外部直接调用 __init__
        raise RuntimeError("Use get_instance() to create instance")

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls.__new__(cls)
            # 可以在此处进行初始化
            cls._instance._init()
        return cls._instance

    def _init(self):
        # 实际初始化代码
        self.value = 0

# 使用
s1 = Singleton.get_instance()
s2 = Singleton.get_instance()
print(s1 is s2)  # True

优点:

缺点:

6. 使用 functools.lru_cache

Python 标准库中的 lru_cache 装饰器可以缓存函数的返回值,利用这一特性也可以实现单例。

示例:

from functools import lru_cache

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

@lru_cache(maxsize=1)
def get_singleton(name):
    return Singleton(name)

# 使用
a = get_singleton("Alice")
b = get_singleton("Bob")
print(a is b)   # True
print(a.name)   # Alice
print(b.name)   # Alice

优点:

缺点:

7. 使用 __init__ 限制实例化次数

另一种思路是直接限制 __init__ 只执行一次,但允许 __new__ 每次都返回同一个实例。

示例:

class Singleton:
    _instance = None
    _initialized = False

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

    def __init__(self, name):
        if not self._initialized:
            self.name = name
            self._initialized = True

这种方式是第 4 种方法的改进,通过标志位避免重复初始化。

三、总结与对比

方法 优点缺点  
模块级单例最简单,天然线程安全  无法控制实例化时机,无延迟加载
类装饰器语法简洁,可传参类型检查破坏,多线程需加锁
元类面向对象,控制力强复杂度高,需理解元类
__new__ 重写最直观,Pythonic__init__ 重复执行,需额外处理
类方法完全控制实例获取,避免重复初始化使用不自然,仍可绕过  
lru_cache简洁,线程安全  只能用于函数,不适合直接作用于类
__init__ 限制解决了 __new__ 重复初始化的问题仍需要 __new__配合,代码稍显冗长

选择建议:

1.如果只需要一个简单的单例,且无需延迟加载,模块级单例是最佳选择。
2.如果需要灵活控制且类型检查无关紧要,类装饰器或 lru_cache 都很方便。
3.对于大型项目或需要清晰继承关系的场景,推荐使用元类或 __new__ 重写。
4.如果希望严格遵循传统单例模式(禁止直接实例化),可以考虑类方法方式。

线程安全注意事项:

在多线程环境下,上述部分实现(如简单的 __new__ 或元类)不是线程安全的,需要添加锁机制。模块级单例和 lru_cache 是线程安全的。其他方式可以通过 threading.Lock 进行保护。

四、总结

单例模式在 Python 中有多种实现方式,每种都有其适用场景。开发者需要根据项目的实际需求,权衡代码简洁性、可维护性以及性能要求,选择最合适的方法。

到此这篇关于Python中实现单例的几种方式小结的文章就介绍到这了,更多相关Python 单例内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文