Python 实现单例模式的五种写法!

单例模式(Singleton Pattern) 是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。微信搜索公众号:架构师指南,回复:架构师 领取资料 。

事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。

在 Python 中,我们可以用多种方法来实现单例模式:

  1. 使用模块

  2. 使用装饰器

  3. 使用类

  4. 基于 __new__ 方法实现

  5. 基于 metaclass 方式实现

下面来详细介绍:

使用模块

其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。

因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。

如果我们真的想要一个单例类,可以考虑这样做:

class Singleton(object):def foo(self):pass
singleton = Singleton()

将上面的代码保存在文件 mysingleton.py 中,要使用时,直接在其他文件中导入此文件中的对象,这个对象即是单例模式的对象 

from mysingleton import singleton

使用装饰器 

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(object):a = 1def __init__(self, x=0):self.x = xa1 = A(2)
a2 = A(3)

使用类 

class Singleton(object):def __init__(self):pass@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instance

一般情况,大家以为这样就完成了单例模式,但是当使用多线程时会存在问题:

class Singleton(object):def __init__(self):pass@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instanceimport threadingdef task(arg):obj = Singleton.instance()print(obj)for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()

程序执行后,打印结果如下:

<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>

看起来也没有问题,那是因为执行速度过快,如果在 __init__ 方法中有一些 IO 操作,就会发现问题了。

下面我们通过 time.sleep 模拟,我们在上面 __init__ 方法中加入以下代码:

def __init__(self):import timetime.sleep(1)

重新执行程序后,结果如下:

<__main__.Singleton object at 0x034A3410>
<__main__.Singleton object at 0x034BB990>
<__main__.Singleton object at 0x034BB910>
<__main__.Singleton object at 0x034ADED0>
<__main__.Singleton object at 0x034E6BD0>
<__main__.Singleton object at 0x034E6C10>
<__main__.Singleton object at 0x034E6B90>
<__main__.Singleton object at 0x034BBA30>
<__main__.Singleton object at 0x034F6B90>
<__main__.Singleton object at 0x034E6A90>

问题出现了!按照以上方式创建的单例,无法支持多线程。

解决办法:加锁!未加锁部分并发执行,加锁部分串行执行,速度降低,但是保证了数据安全。

import time
import threadingclass Singleton(object):_instance_lock = threading.Lock()def __init__(self):time.sleep(1)@classmethoddef instance(cls, *args, **kwargs):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instancedef task(arg):obj = Singleton.instance()print(obj)for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()time.sleep(20)
obj = Singleton.instance()
print(obj)

打印结果如下:

<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>

这样就差不多了,但是还是有一点小问题,就是当程序执行时,执行了 time.sleep(20) 后,下面实例化对象时,此时已经是单例模式了。

但我们还是加了锁,这样不太好,再进行一些优化,把 intance 方法,改成下面这样就行:

@classmethod
def instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instance

这样,一个可以支持多线程的单例模式就完成了。+ 

import time
import threadingclass Singleton(object):_instance_lock = threading.Lock()def __init__(self):time.sleep(1)@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instancedef task(arg):obj = Singleton.instance()print(obj)for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()time.sleep(20)
obj = Singleton.instance()
print(obj)

这种方式实现的单例模式,使用时会有限制,以后实例化必须通过 obj = Singleton.instance()

如果用 obj = Singleton(),这种方式得到的不是单例。

基于 __new__ 方法实现

通过上面例子,我们可以知道,当我们实现单例时,为了保证线程安全需要在内部加入锁。

我们知道,当我们实例化一个对象时,是先执行了类的 __new__ 方法(我们没写时,默认调用 object.__new__),实例化对象;然后再执行类的 __init__ 方法,对这个对象进行初始化,所有我们可以基于这个,实现单例模式。

import threadingclass Singleton(object):_instance_lock = threading.Lock()def __init__(self):passdef __new__(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = object.__new__(cls)  return Singleton._instanceobj1 = Singleton()
obj2 = Singleton()
print(obj1,obj2)def task(arg):obj = Singleton()print(obj)for i in range(10):t = threading.Thread(target=task,args=[i,])t.start()

打印结果如下:

<__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>

采用这种方式的单例模式,以后实例化对象时,和平时实例化对象的方法一样 obj = Singleton() 。另外,搜索公众号GitHub猿后台回复“赚钱”,获取一份惊喜礼包。

基于 metaclass 方式实现

相关知识:

  1. 类由 type 创建,创建类时,type 的 __init__ 方法自动执行,类() 执行 type 的 __call__ 方法(类的 __new__ 方法,类的 __init__ 方法)

  2. 对象由类创建,创建对象时,类的 __init__ 方法自动执行,对象()执行类的 __call__ 方法

例子:

class Foo:def __init__(self):passdef __call__(self, *args, **kwargs):passobj = Foo()
# 执行type的 __call__ 方法,调用 Foo类(是type的对象)的 __new__方法,用于创建对象,然后调用 Foo类(是type的对象)的 __init__方法,用于对对象初始化。obj()    # 执行Foo的 __call__ 方法

元类的使用:

class SingletonType(type):def __init__(self,*args,**kwargs):super(SingletonType,self).__init__(*args,**kwargs)def __call__(cls, *args, **kwargs): # 这里的cls,即Foo类print('cls',cls)obj = cls.__new__(cls,*args, **kwargs)cls.__init__(obj,*args, **kwargs) # Foo.__init__(obj)return objclass Foo(metaclass=SingletonType): # 指定创建Foo的type为SingletonTypedef __init__(self,name):self.name = namedef __new__(cls, *args, **kwargs):return object.__new__(cls)obj = Foo('xx')

实现单例模式:

import threadingclass SingletonType(type):_instance_lock = threading.Lock()def __call__(cls, *args, **kwargs):if not hasattr(cls, "_instance"):with SingletonType._instance_lock:if not hasattr(cls, "_instance"):cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)return cls._instanceclass Foo(metaclass=SingletonType):def __init__(self,name):self.name = nameobj1 = Foo('name')
obj2 = Foo('name')
print(obj1,obj2)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/69483.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ccf-csp题解】第1次csp认证-第三题-命令行选项-题解

题目描述 思路讲解 本题是一个简单的字符串模拟题&#xff0c;这种题目是csp认证第三题的常客 大致思路是用两个bool数组记录某一个选项&#xff08;0--25下标对应小写字母a--z&#xff09;&#xff0c;第一个数组中无参选项为true&#xff0c;第二个数组中有参选项为true&a…

K8S的CKA考试环境和题目

CKA考试这几年来虽然版本在升级&#xff0c;但题目一直没有大的变化&#xff0c;通过K8S考试的方法就是在模拟环境上反复练习&#xff0c;通过练习熟悉考试环境和考试过程中可能遇到的坑。这里姚远老师详细向大家介绍一下考试的环境和题目&#xff0c;需要详细资料的同学请在文…

Tomcat多实例和负载均衡动静分离

一、Tomcat多实例部署 安装jdk 设置jdk环境变量 安装tomcat 配置Tomcat环境变量 修改端口号 修改tomcat中startup.sh和shutdown.sh文件添加tomcat环境变量 启动Tomcat中的startup.sh 浏览器测试 http://192.168.30.100:8080 http://192.168.30.100:8081 二、负载均衡动静分离…

linux并发服务器 —— IO多路复用(八)

半关闭、端口复用 半关闭只能实现数据单方向的传输&#xff1b;当TCP 接中A向 B 发送 FIN 请求关闭&#xff0c;另一端 B 回应ACK 之后 (A 端进入 FIN_WAIT_2 状态)&#xff0c;并没有立即发送 FIN 给 A&#xff0c;A 方处于半连接状态 (半开关)&#xff0c;此时 A 可以接收 B…

你知道用Woof创建的Linux吗?

Quirky 8.2 已发布&#xff0c;它是 Puppy Linux 的姊妹项目&#xff0c;是用一份叫 Woof 的定制工具创建的 Linux 发行。 新版本 Quirky 8.2 运行在 64 位的 x86 计算机上&#xff0c;主要提供了针对以前的 8.x 版本的增量改进。 Quirky Linux 8.2 x86_64 的代号是Xerus&…

Python爬虫:下载小红书无水印图片、视频

该代码只提供学习使用&#xff0c;该项目是基于https://github.com/JoeanAmier/XHS_Downloader的小改动 1.下载项目 git clone https://github.com/zhouayi/XHS_Downloader.git2.找到需要下载的文章的ID 写入main.py中 3.下载 python main.py最近很火的莲花楼为例<嘿嘿…

LeetCode 周赛上分之旅 #44 同余前缀和问题与经典倍增 LCA 算法

⭐️ 本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。 学习数据结构与算法的关键在于掌握问题背后的算法思维框架&#xff0c;你的思考越抽象&#xff0c;它能覆盖的问题域就越广&#xff0c;理解难度…

成都瀚网科技有限公司:抖店怎么开通直播?

随着互联网和移动支付的快速发展&#xff0c;越来越多的人选择开设自己的抖音商店。抖音作为国内最受欢迎的短视频平台之一&#xff0c;拥有庞大的用户基础&#xff0c;成为众多创业者青睐的平台。那么&#xff0c;如何经营自己的抖音店铺呢&#xff1f;下面将从几个方面为您介…

Si24R2F+畜牧 耳标测体温开发资料

Si24R2F是针对IOT应用领域推出的新款超低功耗2.4G内置NVM单发射芯片。广泛应用于2.4G有源活体动物耳标&#xff0c;带实时测温计步功能。相较于Si24R2E&#xff0c;Si24R2F增加了温度监控、自动唤醒间隔功能&#xff1b;发射功率由7dBm增加到12dBm&#xff0c;距离更远&#xf…

k8s 搭建基于session模式的flink集群

1.flink集群搭建 不废话直接上代码&#xff0c;都是基于官网的&#xff0c;在此记录一下 Kubernetes | Apache Flink flink-configuration-configmap.yaml apiVersion: v1 kind: ConfigMap metadata:name: flink-configlabels:app: flink data:flink-conf.yaml: |jobmanager…

postgresql-条件表达式

postgresql-条件表达式 简单Case表达式搜索Case表达式缩写函数总结 简单Case表达式 select e.first_name , e.last_name , e.department_id , case e.department_id when 90 then 管理when 60 then 开发else 其他end as "部门" from cps.public.employees e ;-- 统…

Vue笔记

第一章&#xff1a;Vue环境搭建 1.搭建Vue环境 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><!-- 1.引入Vue.js--><script src"1.vue.js"></scr…

软件生命周期及流程

软件生命周期&#xff1a; 软件生命周期(SDLC&#xff0c;Systems Development Life Cycle)是软件开始研制到最终被废弃不用所经历的各个阶段. 需求分析阶段--输出需求规格说明书&#xff08;原型图&#xff09; 测试介入的晚--回溯成本高 敏捷开发模型&#xff1a; 从1990年…

STM32CUBEMX_创建时间片轮询架构的软件框架

STM32CUBEMX_创建时间片轮询架构的软件框架 说明&#xff1a; 1、这种架构避免在更新STM32CUBEMX配置后把用户代码清除掉 2、利用这种时间片的架构可以使得代码架构清晰易于维护 创建步骤&#xff1a; 1、使用STM32CUBEMX创建基础工程 2、新建用户代码目录 3、构建基础的代码框…

OpenLdap +PhpLdapAdmin + Grafana docker-compose部署安装

目录 一、OpenLdap介绍 二、PhpLdapAdmin介绍 三、使用docker-compose进行安装 1. docker-compose.yml 2. grafana配置文件 3. provisioning 四、安装openldap、phpldapadmin、grafana 五、配置OpenLDAP 1. 登陆PhpLdapAdmin web管理 2. 需要注意的细节 内容介绍参考…

Java作业3

1.下面代码的运行结果是&#xff08;C&#xff09; public static void main(String[] args){String s;System.out.println("s"s);}A.代码编程成功&#xff0c;并输出”s” B.代码编译成功&#xff0c;并输出”snull” C.由于String s没有初始化&#xff0c;代码不…

python基础运用例子

python基础运用例子 1、⼀⾏代码交换 a , b &#xff1a;a, b b, a2、⼀⾏代码反转列表 l[::-1]3、合并两个字典 res {**dict1, **dict2}**操作符合并两个字典for循环合并dict(a, **b) 的方式dict(a.items() b.items()) 的方式dict.update(other_dict) 的方式 4、⼀⾏代码列…

【多尺度双域引导网络:Pan-sharpening】

Multi-Scale Dual-Domain Guidance Network for Pan-sharpening &#xff08;用于泛锐化的多尺度双域引导网络&#xff09; 全色锐化的目标是在纹理丰富的全色图像的指导下&#xff0c;通过超分辨低空间分辨率多光谱图像&#xff08;LRMS&#xff09;的对应物产生高空间分辨率…

语音特征提取与预处理

导入相关包 import librosa import librosa.display import soundfile as sf import numpy as np import matplotlib.pyplot as plt from playsound import playsound 语音读取与显示 file_path test1.wav data, fs librosa.load(file_path, srNone, monoTrue) librosa.d…

数学建模--整数规划匈牙利算法的Python实现

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 #整数规划模型--匈牙利算法求解 """ 整数规划模型及概念&#xff1a;规划问题的数学模型一般由三个因素构成 决策变量 目标函数 约束条件&#xff1b;线性规划即以线性函数为目标函数&a…