Python单例设计模式

单例模式的使用场景

  • Python的logger就是一个单例模式,用以日志记录
  • Windows的资源管理器是一个单例模式
  • 线程池,数据库连接池等资源池一般也用单例模式
  • 网站计数器

从这些使用场景我们可以总结下什么情况下需要单例模式:

  1. 当每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次;
  2. 当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题。

当然所有使用单例模式的前提是我们的确用一个实例就可以搞定要解决的问题,而不需要多个实例,如果每个实例都需要维护自己的状态,这种情况下单例模式肯定是不适用的。

Python来实现一个单例模式

class Singleton(object):
    __instance = None
    def __new__(cls, *args, **kwargs):  # 这里不能使用__init__,因为__init__是在instance已经生成以后才去调用的
        if cls.__instance is None:
            cls.__instance = super(Singleton, cls).__new__(cls)
        return cls.__instance

a = Singleton()
b = Singleton()

print(a)
print(b)
print(id(a))
print(id(b))

打印结果如下:

<__main__.singleton object at 0x017EEFF0>
<__main__.singleton object at 0x017EEFF0>
25096176
25096176

可以看出两次创建对象,结果返回的是同一个对象实例,我们再让我们的例子更接近真实的使用场景来看看

class Singleton(object):
    __instance = None

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

    def __init__(self, status_number):
        self.status_number = status_number


s1 = Singleton(2)
s2 = Singleton(5)
print s1
print s2

print s1.status_number
print s2.status_number

这里我们使用了init方法,下面是打印结果,可以看出确实是只有一个实例,共享了实例的变量

<__main__.Singleton object at 0x7f5116865490>
<__main__.Singleton object at 0x7f5116865490>
5
5

这个例子中有一个问题我们没有解决,那就是多线程的问题,当有多个线程同时去初始化对象时,就很可能同时判断__instance is None,从而进入初始化instance的代码中。所以为了解决这个问题,我们必须通过同步锁来解决这个问题。以下例子来自xiaorui

import threading
try:
    from synchronize import make_synchronized
except ImportError:
    def make_synchronized(func):
        import threading
        func.__lock__ = threading.Lock()

        def synced_func(*args, **kws):
            with func.__lock__:
                return func(*args, **kws)

        return synced_func


class Singleton(object):
    instance = None

    @make_synchronized
    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = object.__new__(cls, *args, **kwargs)
        return cls.instance

    def __init__(self):
        self.blog = "xiaorui.cc"

    def go(self):
        pass


def worker():
    e = Singleton()
    print(id(e))
    e.go()


def test():
    e1 = Singleton()
    e2 = Singleton()
    e1.blog = 123
    print(e1.blog)
    print(e2.blog)
    print(id(e1))
    print(id(e2))

if __name__ == "__main__":
    test()
    task = []
    for one in range(30):
        t = threading.Thread(target=worker)
        task.append(t)

    for one in task:
        one.start()

    for one in task:
        one.join()

更简单地使用单例模式

在Python的官方网站给了两个例子是用装饰符来修饰类,从而使得类变成了单例模式,使得我们可以通过更加简单的方式去实现单例模式
官网Singleton

第一种装饰器写法(简单)
def singleton (cls, *args, **kwargs):

    instances = {}

    def get_instance (*args, **kwargs):

        if cls not in instances:

            instances[cls] = cls(*args, **kwargs)

        return instances[cls]

    return get_instance

代码分析:
第1行,创建外层函数singleton,可以传入类

第3行,创建一个instances字典用来保存单例

第5行,创建一个内层函数来获得单例

第7,9,11行, 判断instances字典中是否含有单例,如果没有就创建单例并保存到instances字典中,然后返回该单例

第13行, 返回内层函数get_instance

@singleton
class Student(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def run(self):
        print(self.name)
 
if __name__ == '__main__':
    student = Student('wang', 18)
    student.run()
    hshs = Student('kai', 21)
    hshs.run()
 
 
执行结果:
wang
wang

student = Student('wang', 18) 即相当于直接把 Student('wang', 18) 作用 singleton的第一个参数传入方法中,此时instance 为空,当这个类第一次实例化之后 instalce = {<class 'main.Student'>:<main.Student object at 0x0000026CFBA5CF60>}

第二种装饰器写法(复杂)
import functools

def singleton(cls):
    """
    将一个类作为单例
    """

    cls.__new_original__ = cls.__new__

    @functools.wraps(cls.__new__)
    def singleton_new(cls, *args, **kw):
        it = cls.__dict__.get('__it__')
        if it is not None:
            return it

        cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
        it.__init_original__(*args, **kw)
        return it

    cls.__new__ = singleton_new
    cls.__init_original__ = cls.__init__
    cls.__init__ = object.__init__

    return cls

@ singleton
class Foo:
    def __new__(cls):
        cls.x = 10
        return object.__new__(cls)

    def __init__(self):
        assert self.x == 10
        self.x = 15

assert Foo().x == 15

Foo().x = 20

assert Foo().x == 20
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容