惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
WordPress大学
WordPress大学
G
Google Developers Blog
博客园 - 三生石上(FineUI控件)
Last Week in AI
Last Week in AI
IT之家
IT之家
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
爱范儿
爱范儿
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
U
Unit 42
Security Archives - TechRepublic
Security Archives - TechRepublic
C
Cisco Blogs
S
Schneier on Security
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
Microsoft Security Blog
Microsoft Security Blog
The Cloudflare Blog
博客园 - 聂微东
C
Cybersecurity and Infrastructure Security Agency CISA
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Blog — PlanetScale
Blog — PlanetScale
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Engineering at Meta
Engineering at Meta
M
MIT News - Artificial intelligence
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
Cisco Talos Blog
Cisco Talos Blog
P
Palo Alto Networks Blog
MongoDB | Blog
MongoDB | Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
V2EX
L
Lohrmann on Cybersecurity
Latest news
Latest news
小众软件
小众软件
有赞技术团队
有赞技术团队
Know Your Adversary
Know Your Adversary
Simon Willison's Weblog
Simon Willison's Weblog
B
Blog
雷峰网
雷峰网
K
Kaspersky official blog
美团技术团队
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Check Point Blog
Recorded Future
Recorded Future
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security

郑文峰的博客

使用dify对接飞书多维表格 使用n8n对接飞书多维表格 服务启动时出现 OOM 一次服务升级时pg表DDL执行超时失败 Go语言高效IO缓冲技术详解 Go语言延迟初始化(Lazy Initialization)最佳实践 Go语言字符串拼接性能对比与优化指南 Go语言结构体内存对齐完全指南 Go语言空结构体:零内存消耗的高效编程 Go语言堆栈分配与逃逸分析深度解析 Go语言原子操作完全指南 Go语言内存预分配完全指南 Go语言不可变数据共享:无锁并发编程实践 Go语言零拷贝技术完全指南 Go语言遍历性能深度解析:从原理到优化实践 Go语言Interface Boxing原理与性能优化指南 Go协程池深度解析:原理、实现与最佳实践 使用etcd分布式锁导致的协程泄露与死锁问题 基于pre-commit的Python代码规范落地实践 初识 MCP Server pulsar阻塞导致logstash无法接入日志 django-prometheus使用及源码分析 kube-proxy源码分析 kubernetes service如何通过iptables转发 tcp缓存引起的日志丢失 django-apschedule定时任务异常停止 理解calico容器网络通信方案原理 理解flannel的三种容器网络方案原理 理解Linux IPIP隧道 理解VXLAN网络 理解Linux TunTap设备 快速了解iptables kafka中listener和advertised.listeners的作用 django rest_framework 分页 django后端服务、logstash和flink接入VictoriaMetrics指标监控 python中import原理 docker容器单机网络 手动实现docker容器bridge网络模型 mysql之MVCC原理 mysql之日志 使用java开发logstash的filter插件 redis之缓存 redis之分片集群 redis之哨兵机制 redis之主从库同步 redis之持久化 redis之五种基本数据类型 go中如何处理error pod中将代码与运行环境分离 ddt源码分析 python装饰器的使用方法 读书笔记:如何阅读一本书 使用ddt实现unittest的参数化测试 使用kubeadm安装k8s 优化gin表单的错误提示信息 gin中validator模块的源码分析 go简单使用grpc python简单使用grpc k8s之PV、PVC和StorageClass k8s之StatefulSet k8s之DaemonSet k8s之Job和CronJob k8s之ConfigMap和Secret k8s之Service k8s之Pod k8s之Deployment 容器的本质 docker容器 python迭代器与生成器 python元编程 python垃圾回收机制 python上下文管理器 django rest_framework使用jwt django rest_framework异常处理 django rest_framework 自定义文档 django压缩文件下载 django rest_framework使用pytest单元测试 django restframework choice 自定义输出数据 django Filtering 使用 django viewset 和 Router 配合使用时报的错 django model的序列化 django中使用AbStractUser django.core.exceptions.ImproperlyConfigured Application labels aren't unique, duplicates users django 中 media配置 django 外键引用自身和on_delete参数 django 警告 while time zone support is active Flask使用flask_socketio实现websocket flask结合mongo tornado 文件上传 tornado 使用jwt完成用户异步认证 tornado 用户密码 bcrypt加密 tornado 结合wtforms使用表单操作 tornado finish和write区别 tornado 使用peewee-async 完成异步orm数据库操作 pyspark streaming简介 和 消费 kafka示例 使用hue创建ozzie的pyspark action workflow count的性能优化 django rest_framework Authentication django celery 结合使用 网站
使用python实现单例模式的三种方式
zhengwenfeng · 2022-12-11 · via 郑文峰的博客

# 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