Python设计模式-单例模式

article/2025/8/21 16:11:20

img

设计思想

通过上面的介绍,我们可以知道单例模式最重要的就是要保证一个类只有一个实例并且这个类易于被访问,那么要怎么做才能保证一个类具有一个实例呢?一个全局变量使得一个对象可以被访问,但是这样做却不能防止你实例化多个对象。 一个更好的办法就是,让该类自身负责保存它的唯一实例instance。并且这个类保证没有其他的实例可以被创建。 怎样保证一个对象的唯一性总结如下:

1.构造器私有化:为了避免其它程序过多的建立该类的对象,先禁止其它程序建立该类对象实例
2.单例对象公开化:为了方便其它程序访问该类的对象,只好在本类中自定义一个对象,由1可知该对象是static的,并对外提供访问方式。

单例定义

  • 单例设计模式保证一个类只有一个实例。
  • 要提供一个访问该类对象实例的全局访问点。

分类:

1.饿汉式这种方式加载类对象,我们称作:预先加载方式。 多线程没有问题。

2.懒汉式这种方式加载类对象,我们称作:延迟加载方式。多线程会出现线程安全问题。

多线程模式下懒汉式与饿汉式区别:https://cloud.tencent.com/developer/article/1414936

使用场景

  1. 配置文件
    服务器中的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象,再通过这个单例对象获取这些配置信息。从而简化了在比较复杂的环境下配置管理。
  2. 日志记录
  3. 数据库操作
  4. 打印机后台处理程序
  5. Django源码
    https://zhuanlan.zhihu.com/p/389035970
    1. admin,Django的admin模块
    2. site,conf.settings

实现方式

除了模块导入外,其他几种单例模式的实现方式本质都是通过设置中间变量,来判断类是否已经拥有实例对象。区别就是中间变量或设置在元类中,或封装在函数中,或设置在类中作为静态变量。中间变量的访问和更改存在线程安全的问题:在开启多线程模式的时候需要加锁处理。Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。中间变量的访问和更改存在线程安全的问题:在开启多线程模式的时候需要加锁处理。

模块导入单例模式

single.pyclass Singleton: passsingleton = Singleton()other.pyfrom single import singleton

其他场景单例模式

Python实现单例模式的方式:本质上就三种实现方式:

  • new() 创建类对象的实例,init()为其个例,即懒汉式
  • __call__元类方式实现,继承元类,
  • decorator装饰器方式实现,来构建实例化对象是否为空检查,用字典存储其值;

以下详细介绍:

new()实现

new() 是一种负责创建类实例的静态方法(实际上最根本的由元类type控制着对象的实例化),它无需使用 staticmethod 装饰器修饰,且该方法会优先 init() 初始化方法被调用。init()是负责创建实例属性的静态方法。一般情况下,覆写 new() 的实现将会使用合适的参数调用其超类的 super().new(),并在返回之前修改实例。例如:

class demoClass:instances_created = 0def __new__(cls,*args,**kwargs):print("__new__():",cls,args,kwargs)instance = super().__new__(cls)instance.number = cls.instances_createdcls.instances_created += 1return instancedef __init__(self,attribute):print("__init__():",self,attribute)self.attribute = attributetest1 = demoClass("abc")
test2 = demoClass("xyz")
print(test1.number,test1.instances_created)
print(test2.number,test2.instances_created)
>>>
# __new__(): <class '__main__.demoClass'> ('abc',) {}
# __init__(): <__main__.demoClass object at 0x0000026FC0DF8080> abc
# __new__(): <class '__main__.demoClass'> ('xyz',) {}
# __init__(): <__main__.demoClass object at 0x0000026FC0DED358> xyz
# 0 2
# 1 2

继承的方式实现,其方法实现单例模式的基本原理如下所示:

class Singleton(object):def __new__(cls, *args, **kwargs):if not hasattr(cls, '_instance'):cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)return cls._instanceclass Foo(Singleton):  # 单例类a = 1foo = Foo()
print('test1 obj is: ', foo)foo = Foo()
print('test2 att is: ', foo)
# >>>
# test1 obj is:  <__main__.Foo object at 0x7f81ea8e1ee0>
# test2 att is:  <__main__.Foo object at 0x7f81ea8e1ee0>

或者不使用继承得方式实现:

注意:new__方法无法避免触发__init(),初始的成员变量会进行覆盖。

class Singleton:def __new__(cls, *args, **kwargs):if not hasattr(cls, '_instance'):cls._instance = super(Singleton, cls).__new__(cls)return cls._instancedef __init__(self, name=None):self.name = namedef print_name(self):print(self.name)s = Singleton('10')
print(s)
print(s.print_name())s1 = Singleton('20')
print(s1)
print(s1.print_name())
print(s.print_name())
# >>>
# <__main__.Singleton object at 0x7f9dc6966ee0>
# 10
# <__main__.Singleton object at 0x7f9dc6966ee0>
# 20
# 20		# s的成员变量10被s1的20覆盖了

new() 通常会返回该类的一个实例,但有时也可能会返回其他类的实例,如果发生了这种情况,则会跳过对__init__()方法的调用。而在某些情况下(比如需要修改不可变类实例(Python 的某些内置类型)的创建行为),利用这一点会事半功倍。比如:

class NonZero(int):def __new__(cls, value):return super(NonZero, cls).__new__(cls, value) if value != 0 else Nonedef __init__(self, skipped_value):# 此例中会跳过此方法print("__init__()")super().__init__()print(type(NonZero(-12)))
print(type(NonZero(0)))
# >>>
# __init__()
# <class '__main__.NonZero'>
# <class 'NoneType'>		# 这里没有再次运行init方法,而是直接跳过

那么,什么情况下使用 new()呢?答案很简单,在__init__()不够用的时候。

例如,前面例子中对 Python 不可变的内置类型(如 int、str、float 等)进行了子类化,这是因为一旦创建了这样不可变的对象实例,就无法在__init__()方法中对其进行修改。

有些读者可能会认为,new() 对执行重要的对象初始化很有用,如果用户忘记使用 super(),可能会漏掉这一初始化。虽然这听上去很合理,但有一个主要的缺点,即如果使用这样的方法,那么即便初始化过程已经是预期的行为,程序员明确跳过初始化步骤也会变得更加困难。不仅如此,它还破坏了__init__()中执行所有初始化工作”的潜规则。

注意,由于__new__()不限于返回同一个类的实例,所以很容易被滥用,不负责任地使用这种方法可能会对代码有害,所以要谨慎使用。一般来说,对于特定问题,最好搜索其他可用的解决方案,最好不要影响对象的创建过程,使其违背程序员的预期。比如说,前面提到的覆写不可变类型初始化的例子,完全可以用工厂方法(一种设计模式)来替代。

Python中大量使用 new() 方法且合理的,就是 MetaClass 元类。有关元类的介绍,可阅读《Python MetaClass元类》一节。

init()实现

为__new__的个例,即懒汉模式。

懒汉式的实例化,例如在导入模块的时候,我们可能会无意中创建一个对象,但当时根本用不到它。懒汉式实例化能够确保实际需要时才创建实例化对象,所以,懒汉式实例化是一种节约资源并仅在需要时才创建它们的方式。

在下面的示例代码中,执行s = Singleton()时候并未创建,只有在Singleton.get_instance()才实际创建实例化对象,调用__new__方法构建。这里本质上还是调用上一个的__new__方法来构建对象的,即Singleton()是调用__new__()方法构建对象。

class Singleton:_instance = Nonedef __init__(self):if not self._instance:print('__init__ method called')else:print('Instance already created: ', self.get_instance())@classmethoddef get_instance(cls):if not cls._instance:# Singleton()应该是调用__new__()方法构建对象cls._instance = Singleton()return cls._instances = Singleton()
print(Singleton.get_instance())
s1 = Singleton()
# >>>
# __init__ method called
# __init__ method called
# <__main__.Singleton object at 0x7f23fdf0ce80>
# Instance already created:  <__main__.Singleton object at 0x7f23fdf0ce80>

__call__实现

简单点说,就是type元类的__call__ 方法在类的 new 方法和 init 方法之前执行。

class SingletonType(type):_instances = {}def __call__(cls, *args, **kwargs):# _instances这里区别与装饰器的对象方法,为类方法,需要cls.if cls not in cls._instances:cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)return cls._instances[cls]# 或者# def __call__(cls, *args, **kwargs):#     return type.__call__(cls, *args, **kwargs)class A(metaclass=SingletonType):passprint(A() is A())
# >>>
# True

问题:

# *args, **kwargs与cls, *args, **kwargs的区别,为什么?
# 自己call不需要传入自己,即cls,其他人如type来call需要传入自己cls
super(SingletonType, cls).__call__(*args, **kwargs)def __call__(cls, *args, **kwargs):return type.__call__(cls, *args, **kwargs)Traceback (most recent call last):File "/home/kali/oadps/common/test_logger.py", line 217, in <module>print(A() is A())File "/home/kali/oadps/common/test_logger.py", line 208, in __call__cls._instances[cls] = super(SingletonType, cls).__call__(cls, *args, **kwargs)
TypeError: A() takes no argumentsclass type(object):"""type(object) -> the object's typetype(name, bases, dict, **kwds) -> a new type"""def mro(self, *args, **kwargs): # real signature unknown""" Return a type's method resolution order. """passdef __call__(self, *args, **kwargs): # real signature unknown""" Call self as a function. """pas

或者这样实现:(这里看不太懂元类的逻辑)

class Singleton(type):def __init__(self, *args, **kwargs):self.__instance = Nonesuper().__init__(*args, **kwargs)def __call__(self, *args, **kwargs):if self.__instance is None:self.__instance = super().__call__(*args, **kwargs)return self.__instanceclass A(metaclass=Singleton):passprint(A() is A())
# >>>
# True

decorator实现

用装饰器singleton方法去装饰MyFun类,装饰器实现基本逻辑如下:

def singleton(cls, *args, **kwargs):instances = {}def _singleton():if cls not in instances:instances[cls] = cls(*args, **kwargs)	# 用装饰器来控制调用__call__方法return instances[cls]return _singleton@singleton
class MyFun:a = 1def __init__(self, x=0):self.x = xprint(MyFun() is MyFun())   # True

单例模式日志

采用装饰器singleton实现日志实例对象唯一性,全部模块代码如下:

# -*- coding:utf-8 -*-
import os
import time
import loggingdef singleton(cls, *args, **kwargs):"""Singleton pattern decorator used by logger function."""instances = {}def _singleton():if cls not in instances:instances[cls] = cls(*args, **kwargs)return instances[cls]return _singletonclass PathFileHandler(logging.FileHandler):"""Handler for file and path."""def __init__(self, path, filename, mode='a', encoding=None, delay=False, errors=None):if not os.path.exists(path):os.mkdir(path)self.baseFilename = os.path.join(path, filename)self.mode = modeself.encoding = encodingself.delay = delayself.errors = errorsif delay:logging.Handler.__init__(self)self.stream = Noneelse:logging.StreamHandler.__init__(self)logging.StreamHandler.__init__(self, self._open())@singleton
class MyLogger:"""Custom my own logger class."""level_relations = {'debug': logging.DEBUG,'info': logging.INFO,'warning': logging.WARNING,'error': logging.ERROR,'critical': logging.CRITICAL}def __init__(self, filename='{}.logs'.format(time.strftime("%Y-%m-%d_%H%M%S", time.localtime())), level='debug',log_dir='logs', fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'):# create Logger、set level、set Formatterself.logger = logging.getLogger(filename)self.logger.setLevel(self.level_relations.get(level))format_str = logging.Formatter(fmt)self.directory = os.path.join(os.getcwd(), log_dir)if not self.logger.handlers:# output to terminalstream_handler = logging.StreamHandler()stream_handler.setLevel(logging.INFO)stream_handler.setFormatter(format_str)# output to filefile_handler = PathFileHandler(path=self.directory, filename=filename, mode='a')file_handler.setFormatter(format_str)# add the Handlerself.logger.addHandler(stream_handler)self.logger.addHandler(file_handler)# Import this logger attribute in your model, then you can use logger.info or
# logger.warnning ... to output logs in your terminal and log files.
logger = MyLogger().logger

http://chatgpt.dhexx.cn/article/8JQwcLnI.shtml

相关文章

redis常用命令

redis常用命令 服务端客户端 服务端 1.使用help查看帮助文档 redis-server --help2.启动redis服务 sudo service redis start3.停止redis服务 sudo service redis stop4.重启redis服务 sudo service redis restart如果上面命令不行&#xff0c;用下面的命令&#xff1a; …

运维维护服务器redis常用命令

查看服务器是否有redis ps -ef | grep redis知道有redis存在了&#xff0c;查看下redis状态 systemctl status redis知道redis状态是运行态和只能本机访问后&#xff0c;想查看redis配置文件位置 cat /etc/systemd/system/redis.service知道redis配置在哪里&#xff0c;修改…

redis启动,停止,重启

1、查看redis是否在运行&#xff1a; ps aux | grep redis 2、启动redis&#xff1a; /etc/init.d/redis start 3、关闭redis&#xff1a;redis-cli shutdown 4、当设置密码后&#xff0c; 上面的关闭命令无效&#xff1a;带密码输入&#xff1a;redis-cli -a [password] 回…

centos中常用与redis相关的命令

该篇指令只针对完全不懂Linux&#xff0c;但是却不得不配置redis的同学&#xff08;没错就是我&#xff09; 安装过程就不阐述了&#xff0c;默认都安好了嗷。 启动redis 首先进入到redis安装位置的bin目录下&#xff0c;路径因人而异&#xff0c;这里用本人的路径作为示例 …

Linux重启 Redis自动启动

今天又要维护新的服务器了&#xff0c;翻了一下网上Redis自动启动&#xff0c;结果配置一大堆&#xff0c;其实很多没什么用&#xff0c;也不好理解&#xff0c;所以干脆把我自己的记录给写这里给大家参考。 第一步&#xff1a;创建Redis启动服务 在系统开机启动项目录 /lib/…

windows 重启redis的方法

目录 打开本地的文件夹 选中文件的路径输入cmd 在cmd命令窗口中输入以下的指令 指令的内容以及对应的命令含义 前言&#xff1a; 最近在本地搭建redis环境&#xff0c;突然发现本地的redis启动失败了&#xff0c;使用reids的启动命令也不行&#xff0c;于是找了不少的方法&…

redis linux 重启命令行,redis 如何重启? linux下请输入命令不要用kill 的方式

噜噜哒 1、如果是用apt-get或者yum install安装的redis&#xff0c;可以直接通过下面的命令停止/启动/重启redis&#xff1a;/etc/init.d/redis-server stop/etc/init.d/redis-server start/etc/init.d/redis-server restart2、如果是通过源码安装的redis&#xff0c;则可以通过…

redis重启命令Linux,Redis启动、配置 及 常用命令-Go语言中文社区

启动 Redis 服务 src/redis-server或者src/redis-server redis.conf 注:src/redis-server 会不加载redis.conf配置文件,提示: Warning: no config file specified, using the default config. 标题 而:src/redis-server redis.conf 为载入redis.conf配置的启动方式 标题 sr…

Windows环境下redis重启

在redis安装的目录下打开cmd窗口 输入以下命令打开启动redis redis-server redis.windows.conf 如果提示 Creating Server TCP listening socket *:6379: bind: No error&#xff0c;需要重启redis 一、重启步骤&#xff1a; 1、依次输入以下指令 redis-cli -h 127.0.0.1…

如何停止/重启/启动Redis服务

如何停止/重启/启动Redis服务 一、命令行直接启动/停止/重启redis1、启动redis服务及验证2、停止redis服务及验证3、重启redis服务及验证 二、通过redis的相关配置信息关闭/启动redis1.关闭 redis服务及验证2.启动redis服务及验证 叮嘟&#xff01;最近做项目学习用到了Redis&a…

如何停止重启启动Redis服务

如何停止/重启/启动Redis服务 一、命令行直接启动/停止/重启redis 1、启动redis服务及验证2、停止redis服务及验证3、重启redis服务及验证 二、通过redis的相关配置信息关闭/启动redis 1.关闭 redis服务及验证2.启动redis服务及验证 叮嘟&#xff01;最近做项目学习用到了Re…

语音识别,标注数据

切割音频文件 from pydub import AudioSegment from pydub.silence import split_on_silence import ossound AudioSegment.from_mp3("E:/data/AcsData/zfBX/hw202003301111246_23401.wav") loudness sound.dBFS outputPath "E:/data/AcsData/zfBX/output/&…

音频标注工具

ASR音频标注工具 前言一、音频标注工具二、使用环境总结 前言 为了方便查看音频标注质量&#xff0c;使用图形化音频标注工具可以更方便的对音频文本内容进行编辑。 下载链接&#xff1a; https://download.csdn.net/download/weixin_54971024/85426511 一、音频标注工具 主页…

数据标注工具、平台、类型

华为云ModelArts标注工具&#xff0c;支持的标注类型有&#xff1a; 图片&#xff1a; imgClassify 图像分类&#xff1a;识别一张图片中是否包含某种物体 objDetect 物体检测&#xff1a;识别出图片中每个物体的位置及类别 imgSevering 图像分割&#xff1a;根据图片中的物体…

Python-实现语音识别并批处理标注(重命名)文件

由于我的测试经常需要对音频文件内容标注&#xff08;根据语音的内容用拼音标注&#xff09;&#xff0c;一个一个人工标注很费时费力&#xff01;那有没有简单点的方法&#xff0c;答案是有&#xff01;&#xff01;&#xff01;一起学习吧~ 1.在百度AI开发平台获取AppID、AP…

景联文科技提供语音数据采集标注服务

什么是语音标注&#xff1f; 语音标注是数据标注行业中一种常见的标注类型&#xff0c;是由标注员对语音信息进行不断标注转写&#xff0c;让人工系统进行进一步学习&#xff0c;标注后的数据主要用于人工智能机器学习&#xff0c;相当于给计算机系统装上了“耳朵”&#xff0…

语音数据标注工具与平台/公司

最近在做语音深度学习的项目&#xff0c;整理了一下语音数据标注工具和语音标注平台&#xff0c;供大家参考 语音标注工具--Praat Praat是目前已经成为比较流行也比较专业的语音处理的软件&#xff0c;可以进行语音数据标注、语音录制、语音合成、语音分析等等&#xff0c;具有…

语音标注必须了解的基础知识点

昨天发了一遍关于语音数据标注工具Praat的基础使用&#xff0c;后台收到了很多小伙伴的留言&#xff0c;想了解一下更多的语音相关的知识&#xff0c;也想深入的学习一下&#xff0c;所以今天继续写一篇关于语音相关的基础知识&#xff0c;不管是数据标注还是刚了解语音相关AI技…

景联文科技|浅谈常见的语音标注方法

语音标注是数据标注行业中一种比较常见的标注类型。 语音标注的主要工作内容是将语音中包含的文字信息、各种声音“提取”出来&#xff0c;再进行转写或合成&#xff0c;标注后的数据主要用于人工智能&#xff0c;应用在机器学习、语音识别、对话机器人等领域。相当于给计算机…