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 只会点到为止,有感兴趣的同学可以在网上搜索相关内容,几篇参考文章:



使用 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 实例。


小结


本文虽然是讲单例模式,但在实现单例模式的过程中,涉及到了蛮多高级 Python 语法,包括装饰器、元类、new、type 甚至 super 等等。对于新手同学可能难以理解,其实在工程项目中并不需要你掌握的面面俱到,掌握其中一种,剩下的作为了解即可。


关于更多的设计模式,给初学者推荐《Head First 设计模式》(Head First Design Patterns),此书浅显易懂,在 Head First 系列书籍里面也算是很好的一本。


  • 如需了解视频课程及答疑群等更多服务,请号内回复 码上行动
  • 代码相关问题可以在论坛上发帖提问 bbs.crossincode.com

推荐阅读:

朋友圈 |电影票 | 百万关注 | 技术宅 | 火车票 | 单词表 | 押韵工具 | 新手建议 | 就业 | 知乎 | 排序

相关文章
|
7月前
|
Python
如何在Python中实现单例模式?
【1月更文挑战第15天】
53 2
|
2月前
|
设计模式 存储 数据库连接
Python编程中的设计模式之美:单例模式的妙用与实现###
本文将深入浅出地探讨Python编程中的一种重要设计模式——单例模式。通过生动的比喻、清晰的逻辑和实用的代码示例,让读者轻松理解单例模式的核心概念、应用场景及如何在Python中高效实现。无论是初学者还是有经验的开发者,都能从中获得启发,提升对设计模式的理解和应用能力。 ###
|
3月前
|
设计模式 缓存 数据库
Python中的单例模式
单例模式确保一个类只有一个实例,并提供全局访问点。此模式常用于共享资源或限制资源访问,可避免资源浪费并简化全局访问。其实现方法包括使用模块级变量、装饰器或元类。尽管单例模式能简化访问和初始化,但也可能引入全局状态,影响代码的可维护性。适用于配置管理、数据库连接池等场景。
|
4月前
|
设计模式 存储 数据库连接
Python设计模式:巧用元类创建单例模式!
Python设计模式:巧用元类创建单例模式!
53 0
|
6月前
|
设计模式 测试技术 数据库
Python单例模式详解与实际应用
单例模式是一种特殊的设计模式,它通过限制一个类的实例数量为一个来保证只有一个实例被创建,并且提供一个全局访问点以供其他对象使用。
90 3
|
5月前
|
设计模式 缓存 数据库
Python中的单例模式是什么?
**摘要:** 单例模式是一种设计模式,确保类只有一个实例并提供全局访问点。常用于共享资源、配置管理、数据库连接池、日志记录和缓存。Python中可使用模块级变量、装饰器或元类实现单例。需谨慎使用,以避免全局状态导致的复杂性。
|
7月前
|
存储 安全 Python
如何在Python中实现一个单例模式,确保在多线程环境中也是安全的?
【2月更文挑战第5天】【2月更文挑战第11篇】如何在Python中实现一个单例模式,确保在多线程环境中也是安全的?
215 1
|
7月前
|
存储 安全 Python
|
Python
62 python - 单例模式
62 python - 单例模式
34 0
|
缓存 安全 开发者
Python 单例模式讲解和代码示例
Python 单例模式讲解和代码示例
53 0