Python实现Singleton模式

Python实现Singleton模式的几种方式

参考:https://www.cnblogs.com/yssjun/p/9858420.html

使用python实现设计模式中的单例模式。单例模式是一种比较常用的设计模式,其实现和使用场景判定都是相对容易的。本文将简要介绍一下python中实现单例模式的几种常见方式和原理。一方面可以加深对python的理解,另一方面可以更加深入的了解该模式,以便实际工作中能更加灵活的使用单例设计模式。

本文将介绍常见的实现单例模式的几种方式,这里暂不考虑多线程的情况。

为了准备该篇博文,之前写了几篇相关的文章依次完整的介绍了相关的概念,下面会在需要的时候给出链接。

装饰器作为python实现单例模式的一种常用方法,先简单了解一下其概念。

1.装饰器

装饰器(Decorator)可以用作对函数以及类进行二次包裹或者封装,使用方式@wrapper。

 

def f(...):
    ...
f = staticmethod(f)

@staticmethod
def f(...):
    ...

 

上面这两种方式对函数的定义在语法上是等价的。当然对于类也有同样的用法,类可以作为装饰器也可以作为被装饰对象。唯一的区别就是经过包裹的类可能不在是一个类,而是一个类的对象或者一个函数,这取决于装饰器返回的值。

经过Decorator装饰的类或者函数本质上已经不再是原来的类或者函数了。但是,实际上在包裹之后得到的新对象仍然拥有被包裹对象的特性(这句是不是废话:-))。

在python中我们经常只需要实现一个装饰器,然后使用该装饰器作用于只能有唯一一个实例的类。这样只需要实现一个这样的装饰器,便可以作用于任何一个想要唯一实例的类。

2.闭包方式

闭包的应用很多,单例模式则是其应用之一。先看代码:

复制代码

 1 def singleton(cls):
 2     instances = {}
 3     
 4     def getinstance(*args, **kwargs):
 5         if cls not in instances:
 6             instances[cls] = cls(*args, **kwargs)
 7         return instances[cls]
 8     return getinstance
 9 
10 @singleton
11 class my_cls(object):
12     pass

复制代码

 这个实现单例模式的方式将原来类的定义隐藏在闭包函数中,通过闭包函数及其中引用的自由变量来控制类对象的生成。由于唯一的实例存放在自由变量中,而且自由变量是无法直接在脚本层进行访问的。这种方式非常隐蔽的保护实例不被修改,因此很适合用于单例模式。

这种方式简单明了,很容易实现。但是如果不了解闭包实现过程和变量的绑定等概念可能会不明白其实现的过程。建议参考一下我的另一篇博文:理解python闭包概念

这里一个很有趣的地方是为什么要使用instances = {}这样一个变量?可不可以不用字典,使用instance = None?如果singleton作为装饰器被多个不同的类使用,那么instance中会存在几个不同的实例么?

有时间可以思考一下这几个问题,答案也可以在我写的闭包相关的博文中找到。

3.元类方式

所谓单例模式,即我们需要控制类实例的生成过程,并且保证全局只可能存在一个唯一的实例。既然需要在创建类的对象过程中做些什么,应该很容易想到元类。参照介绍元类的文章:python metaclass深入分析

复制代码

 1 class Singleton(type):
 2     def __init__(cls, name, bases, dic):
 3         super(Singleton, cls).__init__(name, bases, dic)
 4         cls._instance = None
 5 
 6     def __call__(cls, *args, **kwargs):
 7         if cls._instance is None:
 8             cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
 9             # cls._instance = cls(*args, **kwargs)    # Error! Lead to call this function recursively
10         return cls._instance
11 
12 class my_cls(object):
13     __metaclass__ = Singleton

复制代码

 这个例子中我们使用元类Singleton替代默认使用type方式创建类my_cls。可以将类my_cls看做是元类Singleton的一个对象,当我们使用my_cls(...)的方式创建类my_cls的对象时,实际上是在调用元类Singleton的对象my_cls。

对象可以以函数的方式被调用,那么要求类中定义__call__函数。不过此处被调用的是类,因此我们在元类中定义函数__call__来控制类my_cls对象创建的唯一性。

这种方式的弊端之一就是类唯一的对象被存放在类的一个静态数据成员中,外部可以通过class_name._instance的方式修改甚至删除这个实例(该例中my_cls._instance = None完全合法)。

4.类作为装饰器之__call__方式

不仅函数可以作为装饰器,类也可以作为装饰器。

下面简单的介绍一下使用类作为装饰器实现单例模式的另一种方式。

复制代码

 1 class Singleton(object):
 2     _INSTANCE = {}
 3     def __init__(self, cls):
 4         self.cls = cls
 5         
 6     def __call__(self, *args, **kwargs):
 7         instance = self._INSTANCE.get(self.cls, None)
 8         if not instance:
 9             instance = self.cls(*args, **kwargs)
10             self._INSTANCE[self.cls] = instance
11         return instance
12     
13     def __getattr__(self, key):
14         return getattr(self.cls, key, None)
15 
16 @Singleton
17 class my_cls(object):
18     pass

复制代码

  函数作为装饰器返回的是一个函数,函数被调用过程中实际上是间接地调用其内部包裹的被装饰的对象。

类作为装饰器要想达到相同的效果只需要将类的对象返回,并且其对象是可以调用的。这是上面这个例子表达的一个核心思想。

这种方式写法很多,也很灵活,其思想基本上就是对被包裹对象的调用实际上调用的是类对象的__call__函数,该函数实际上是对被装饰对象的一次封装。

5.类本身实现方式

上面的例子中我们都是使用的装饰器或者元类的方式间接的通过控制类对象生成的方式来保证对象的唯一性,那么有没有办法直接在类中通过某种方式保证类对象的唯一性?

答案是肯定的。参考我之前写的一篇介绍元类的文章,可知生成对象前会调用函数__new__,如果__new__函数返回被创建的对象,那么会自动调用类中定义的__init__函数进行对象的初始化操作。

相信读了上面这句话,应该知道我们接下来要干什么了?没错,我们的目标就是__new__。

复制代码

 1 class MSC(object):
 2     _INSTANCE = None
 3 
 4     def __new__(cls, *args, **kwargs):
 5         if not cls._INSTANCE:
 6             cls._INSTANCE = super(MSC, cls).__new__(cls, *args, **kwargs)
 7             # cls._INSTANCE.args = args
 8             # cls._INSTANCE.kwargs = kwargs
 9         return cls._INSTANCE
10 
11     def __init__(self, *args, **kwargs):
12         pass

复制代码

在这个例子中,我们完全可以理解为什么只会有一个类的对象会被创建。这种方式的定义决定了类本身只能被创建一个对象。

但是这里有一点需要注意,那就是不管创建多少MSC的对象,至始至终只会有一个对象,但是如果每次创建的时候传入的参数都不同,也就是__init__函数中参数不同,会导致同一个对象被多次初始化。

这种方式的弊端显然很明显,那就是该方法只能作用于单个类的定义。不能像上面的装饰器和元类,一次实现,可以到处使用。

那能不能将这个控制类生成过程的结构单独抽象出来呢?而且有没有什么方法能防止同一个对象多次被__init__初始化。下面我们看一种能被不同的类使用的更加抽象的结构。

6.替换__new__方式

我们定义的类作为一个对象,通过替换其部分属性可以达到控制类对象生成的目的。

复制代码

 1 def Singleton(cls):
 2     _instance = {}
 3     cls._origin_new = cls.__new__
 4     cls._origin_init = cls.__init__
 5     @functools.wraps(cls.__new__)
 6     def _singleton_new(cls, *args, **kwargs):
 7         if cls not in _instance:
 8             sin_instance = cls._origin_new(cls, *args, **kwargs)
 9             sin_instance._origin_init(*args, **kwargs)
10             _instance[cls] = sin_instance
11         return _instance[cls]
12     # As a special case,__new__ is a staticmethod, need convert function to staticmethod by self 
13     cls.__new__ = staticmethod(_singleton_new)
14     # setattr(cls, '__new__', staticmethod(_singleton_new))
15     cls.__init__ = lambda self, *args, **kwargs: None
16     # setattr(cls, '__init__', lambda self, *args, **kwargs: None)
17     return cls
18 
19 @Singleton
20 class my_cls(object):
21     pass

复制代码

 上面我们通过替换类的__new__函数和__init__函数的方式,保证被Singleton装饰的类只有一个对象会被原来的__new__和__init__生成和初始化。

这里必须要替换类的__init__函数,而且该函数应该什么都不做。原因在于替换之后的__new__返回唯一的对象后,会自动调用现在的__init__函数。

原来的__init__函数已经在创建唯一一个对象时被调用过。而且只能被调用一次。

这里返回的并不是闭包结构,只是使用装饰器修改了类的部分属性,返回的仍是传入的类。但是类的__new__函数引用了Singleton中的local variable _instance。

my_cls.__new__.func_closure[0].cell_contents 
== 
{<class '__main__.my_cls'>: <__main__.my_cls object at 0x02954810>}
==
_instance

Cell 对象my_cls.__new__.func_closure[0]中存放的便是类my_cls唯一的实例。

当然我们可以将my_cls唯一的对象作为类的一个静态数据成员放入cls.__dict__中来替代_instance = {},但是显然闭包结构更适合。

7.注意事项

文中借助python语言的类创建对象过程的相关原理,介绍了几种不同的单例模式实现方式。

为了保留被装饰对象的一些属性,可以使用@functools.wraps的方式对返回的闭包进行装饰。

平时建议使用前两种实现方式,也就是闭包方式和元类方式。其他情况多少有点玩弄python语法技巧的一些嫌疑,当然了,作为学习python来说还是比较有意义的。

建议多关注语言特性的应用以及如何的解决实际的问题,不要沉迷于语言实现的一些细枝末节。本末倒置总会有些得不偿失嘛。尤其是python作为一种非常实用的语言。

本文介绍中如果有什么不当之处欢迎指正,如果有其他的更好的实现方式也请不吝赐教。

欢迎转载博客文章,转载请标明出处!

 

 


 

Python实现Singleton模式,可以用于搭建数据库连接池

参考:https://blog.csdn.net/lqxqust/article/details/51910007

 

Singleton模式即单例对象必须必须保证只有一个实例存在。可以说Singleton是设计模式中最基本一种设计模式,在学习设计模式时一般都会先了解该模式。在Python中实现单例模式有很多方法,下面简单总结以下,可以根据情况选择适合自己业务场景的一种进行实现。 
1. 如果在C++中实现过单例模式,在Python中比较常想到的实现方法是借助__new__方法实现:

class Singleton(object):
    instance = None
    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super().__new__(cls, *args, **kwargs)
        return cls.instance

t1 = Singleton()
t2 = Singleton()
assert id(t1) == id(t2)

该方法基本可以保证只有一个实例,但存在并发的问题。 
2. 在C++中实现Singleton模式的时候也会考虑多线程的问题,在Pyhton中同样存在该问题,可以通过加锁进行解决。

import threading 

class Singleton(object):
    vars = {}
    single_lock = threading.Lock()
    def __new__(cls, *args, **kwargs):
        if cls in cls.vars:
            return cls.vars[cls]
        cls.single_lock.acquire()
        try:
            if cls in cls.vars:
                return cls.vars[cls]
            cls.vars[cls] = super().__new__(cls, *args, **kwargs)
            return cls.vars[cls]
        finally:
            cls.single_lock.release()

以上两种都是在类似C++的语言中实现的常用方法,但是在Python中实现不那么Pytonic,并且被子类重载可能还存在问题。 
3. 在《改善Python程序的91个建议》中介绍了一种方法,Python的import机制天然支持单例的实现。 
1. 所有变量都会绑定到模块 
2. 模式只初始化一次 
3. import机制是线程安全的 
该放很多情况下都可以满足单例的使用,实现有时觉得有点隐晦。 
4. 有没有更Pythnic得方法,并且有比较易于使用的呢?大Python当时是无所不能了O(∩_∩)O~,通过使用元类来实现。元类就是用来控制创建类的类。

class SingletonMeta(type):
    def __init__(cls, name, bases, namespaces):
        super().__init__(cls, name, bases, namespaces)
        cls.instance = None
    def __call__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super().__call__(*args, **kwargs)
        else:
            print("instance already existed!")
        return cls.instance

class Singleton(meta=Singleton):
    pass

t1 = Singleton()
t2 = Singleton()

实现了__call__方法这个类就成为可调用的,类似于重载了括号操作符,因为在元类中实现__call__,则使用Singleton()实例化类时就会调用__call__,对其进行了单例创建的控制。 
4. 还有一种Pythonic的方法时使用decorator实现

def singleton(cls, *args, **kwargs):
    instances = {}
    def wrapper():
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return _wrapper

@singleton
class Singleton(object):
    pass

t1 = Singleton()
t2 = Singleton()

以上是Python中常见的实现单例模式的方法,在实际开发中,可以根据应用场景选择自己喜欢的方式实现,个人觉得通过元类或装饰器方式实现还是非常的方便和Pythonic。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值