郑文峰的博客 郑文峰的博客
首页
  • python之路
  • go之路
  • 其他
  • redis
  • mysql
  • docker
  • k8s
读书破万卷
周刊
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)

zhengwenfeng

穷则变,变则通,通则久
首页
  • python之路
  • go之路
  • 其他
  • redis
  • mysql
  • docker
  • k8s
读书破万卷
周刊
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)
  • python

    • 基础

      • python迭代器与生成器
      • python元编程
      • python垃圾回收机制
      • python上下文管理器
      • python装饰器的使用方法
      • 使用python实现单例模式的三种方式
        • 0 . 前言
        • 1. 在类中_new_方法中实现
        • 2. 通过元类实现
        • 3. 通过装饰器实现单例
      • python中import原理
    • 第三方库

    • django

    • flask

    • tornado

    • 其他

  • go

  • 其他

  • 编程
  • python
  • 基础
zhengwenfeng
2022-12-10
目录

使用python实现单例模式的三种方式

# 0 . 前言

在整个进程中,有且只有一个对象存在,在任何地点使用都是同一个对象,可以解决多线程资源竞争问题,也常用于配置信息。

本文主要介绍使用python的三种实现单例模式的方式。

# 1. 在类中__new__方法中实现

在需要实现单例的 class 中添加__new__方法,在创建该 class 对象时会调用该方法,使用类变量 _instance 来保存当前对象,每次创建之前都会判断是否有该对象,没有则创建,有则直接返回。

from typing import Any

class A:
    def __new__(cls, *args, **kwargs) -> Any:
        if not hasattr(cls, "_instance"):
            cls._instance = super().__new__(cls, *args, **kwargs)
    return cls._instance
1
2
3
4
5
6
7

我们创建两个 class A 对象,然后分别打印他们的内存 ID,会发现两者 ID 是一致的,也就是是同一个对象。

a1 = A()  
a2 = A()  
print(id(a1))  
print(id(a2))

Output: 
2659742107728
2659742107728
1
2
3
4
5
6
7
8

# 2. 通过元类实现

上面的方式需要在每一个单例类中都要添加一个__new__方法,有大量的重复代码。接下来我们介绍通过元类来实现单例。

  • 第一版

首先创建 class Singleton 来继承 type,该类为我们自定义的元类。然后创建我们需要单例的 class A 和 B,它们都需要通过 metaclass=Singleton 来选择 Singleton 作为它们的元类。

在元类中,创建 __call__ 方法,该方法会在 class A 和 B 创建对象时调用,在该方法中会调用 __new__ 和 __init__ 方法,创建完对象后,再将该对象放在类变量 _instance 中,和 1. 在__new__中实现单例 的方法一样。

from typing import Any

class Singleton(type):
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        if not hasattr(self, "_instance"):
            self._instance = super(Singleton, self).__call__(*args, **kwds)

        return self._instance

class A(metaclass=Singleton):
    pass

class B(metaclass=Singleton):
    pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14

我们再通过测试,可以看到 class A 和 B 都实现了单例。

a1 = A()
a2 = A()
print(id(a1))
print(id(a2))

b1 = B()
b2 = B()
print(id(b1))
print(id(b2))

Output:
>>> 2802632572784
>>> 2802632572784
>>> 2802632572400
>>> 2802632572400
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

但这个单例设计只适用于单线程,在多线程中,如果两个线程都停留 hasattr 下面,可能还是会创建两个对象,达不到单例的效果。

  • 第二版

通过加锁解决上面并发的问题。

import threading

class Singleton(type):
    lock = threading.Lock()
    
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        with Singleton.lock:
            if not hasattr(self, "_instance"):
                self._instance = super(Singleton, self).__call__(*args, **kwds)

        return self._instance
1
2
3
4
5
6
7
8
9
10
11

但是其实只有第一次创建对象时,需要通过锁同步获取单例对象,在已有对象时,不需要再用锁了,在这种情况下,每次获取对象都经过锁,会影响性能。

  • 最终版

所以再加上一重判断,减少每次锁判断带来的性能消耗。

import threading
class Singleton(type):
    lock = threading.Lock()
    
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        if not hasattr(self, "_instance"):
            with Singleton.lock:
                if not hasattr(self, "_instance"):
                    self._instance = super(Singleton, self).__call__(*args, **kwds)

        return self._instance
1
2
3
4
5
6
7
8
9
10
11

# 3. 通过装饰器实现单例

该方法是通过实现一个装饰器,在需要实现类上添加该装饰器即可完成,使用简单。

通过将所有的单例对象保存在装饰器的 _instance 字典中,以类为 key,对象为 value 进行存储。

def Singleton(cls):
    _instance = {}
    def _singleton(*args, **kargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kargs)
        return _instance[cls]
    return _singleton
@Singleton
class A:
    pass
  
@Singleton
class B:
    pass
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#python
上次更新: 2023/01/15, 15:47:48
python装饰器的使用方法
python中import原理

← python装饰器的使用方法 python中import原理→

最近更新
01
django rest_framework 分页
03-20
02
学习周刊-第03期-第09周
03-03
03
学习周刊-第02期-第08周
02-24
更多文章>
Theme by Vdoing | Copyright © 2022-2023 zhengwenfeng | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式