Python实现Singleton模式的方式详解

前言

使用python实现设计模式中的单例模式。单例模式是一种比较常用的设计模式,其实现和使用场景判定都是相对容易的。本文将简要介绍一下python中实现单例模式的几种常见方式和原理。一方面可以加深对python的理解,另一方面可以更加深入的了解该模式,以便实际工作中能更加灵活的使用单例设计模式。

本文将介绍常见的实现单例模式的几种方式,这里暂不考虑多线程的情况。

为了准备该篇博文,之前写了几篇相关的文章依次完整的介绍了相关的概念,下面会在需要的时候给出链接。

装饰器作为python实现单例模式的一种常用方法,先简单了解一下其概念。

1.装饰器

装饰器(Decorator)可以用作对函数以及类进行二次包裹或者封装,使用方式@wrapper。

def f(...):
  ...
f = staticmethod(f)
@staticmethod
def f(...):
  ...

上面这两种方式对函数的定义在语法上是等价的。当然对于类也有同样的用法,类可以作为装饰器也可以作为被装饰对象。唯一的区别就是经过包裹的类可能不在是一个类,而是一个类的对象或者一个函数,这取决于装饰器返回的值。

经过Decorator装饰的类或者函数本质上已经不再是原来的类或者函数了。但是,实际上在包裹之后得到的新对象仍然拥有被包裹对象的特性(这句是不是废话:-))。

在python中我们经常只需要实现一个装饰器,然后使用该装饰器作用于只能有唯一一个实例的类。这样只需要实现一个这样的装饰器,便可以作用于任何一个想要唯一实例的类。

2.闭包方式

闭包的应用很多,单例模式则是其应用之一。先看代码:

def singleton(cls):
  instances = {}
  def getinstance(*args, **kwargs):
    if cls not in instances:
      instances[cls] = cls(*args, **kwargs)
    return instances[cls]
  return getinstance
@singleton
class my_cls(object):
  pass

这个实现单例模式的方式将原来类的定义隐藏在闭包函数中,通过闭包函数及其中引用的自由变量来控制类对象的生成。由于唯一的实例存放在自由变量中,而且自由变量是无法直接在脚本层进行访问的。这种方式非常隐蔽的保护实例不被修改,因此很适合用于单例模式。

这种方式简单明了,很容易实现。但是如果不了解闭包实现过程和变量的绑定等概念可能会不明白其实现的过程。建议参考一下我的另一篇博文:理解python闭包概念。

这里一个很有趣的地方是为什么要使用instances = {}这样一个变量?可不可以不用字典,使用instance = None?如果singleton作为装饰器被多个不同的类使用,那么instance中会存在几个不同的实例么?

有时间可以思考一下这几个问题,答案也可以在我写的闭包相关的博文中找到。

3.元类方式

所谓单例模式,即我们需要控制类实例的生成过程,并且保证全局只可能存在一个唯一的实例。既然需要在创建类的对象过程中做些什么,应该很容易想到元类。

class Singleton(type):
  def __init__(cls, name, bases, dic):
    super(Singleton, cls).__init__(name, bases, dic)
    cls._instance = None
  def __call__(cls, *args, **kwargs):
    if cls._instance is None:
      cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
      # cls._instance = cls(*args, **kwargs)  # Error! Lead to call this function recursively
    return cls._instance

class my_cls(object):
  __metaclass__ = Singleton

这个例子中我们使用元类Singleton替代默认使用type方式创建类my_cls。可以将类my_cls看做是元类Singleton的一个对象,当我们使用my_cls(...)的方式创建类my_cls的对象时,实际上是在调用元类Singleton的对象my_cls。

对象可以以函数的方式被调用,那么要求类中定义__call__函数。不过此处被调用的是类,因此我们在元类中定义函数__call__来控制类my_cls对象创建的唯一性。

这种方式的弊端之一就是类唯一的对象被存放在类的一个静态数据成员中,外部可以通过class_name._instance的方式修改甚至删除这个实例(该例中my_cls._instance = None完全合法)。

4.类作为装饰器之__call__方式

不仅函数可以作为装饰器,类也可以作为装饰器。

下面简单的介绍一下使用类作为装饰器实现单例模式的另一种方式。

class Singleton(object):
  _INSTANCE = {}
  def __init__(self, cls):
    self.cls = cls
  def __call__(self, *args, **kwargs):
    instance = self._INSTANCE.get(self.cls, None)
    if not instance:
      instance = self.cls(*args, **kwargs)
      self._INSTANCE[self.cls] = instance
    return instance
  def __getattr__(self, key):
    return getattr(self.cls, key, None)
@Singleton
class my_cls(object):
  pass

函数作为装饰器返回的是一个函数,函数被调用过程中实际上是间接地调用其内部包裹的被装饰的对象。

类作为装饰器要想达到相同的效果只需要将类的对象返回,并且其对象是可以调用的。这是上面这个例子表达的一个核心思想。

这种方式写法很多,也很灵活,其思想基本上就是对被包裹对象的调用实际上调用的是类对象的__call__函数,该函数实际上是对被装饰对象的一次封装。

5.类本身实现方式

上面的例子中我们都是使用的装饰器或者元类的方式间接的通过控制类对象生成的方式来保证对象的唯一性,那么有没有办法直接在类中通过某种方式保证类对象的唯一性?

答案是肯定的。参考我之前写的一篇介绍元类的文章,可知生成对象前会调用函数__new__,如果__new__函数返回被创建的对象,那么会自动调用类中定义的__init__函数进行对象的初始化操作。

相信读了上面这句话,应该知道我们接下来要干什么了?没错,我们的目标就是__new__。

class MSC(object):
  _INSTANCE = None
  def __new__(cls, *args, **kwargs):
    if not cls._INSTANCE:
      cls._INSTANCE = super(MSC, cls).__new__(cls, *args, **kwargs)
      # cls._INSTANCE.args = args
      # cls._INSTANCE.kwargs = kwargs
    return cls._INSTANCE
  def __init__(self, *args, **kwargs):
    pass

在这个例子中,我们完全可以理解为什么只会有一个类的对象会被创建。这种方式的定义决定了类本身只能被创建一个对象。

但是这里有一点需要注意,那就是不管创建多少MSC的对象,至始至终只会有一个对象,但是如果每次创建的时候传入的参数都不同,也就是__init__函数中参数不同,会导致同一个对象被多次初始化。

这种方式的弊端显然很明显,那就是该方法只能作用于单个类的定义。不能像上面的装饰器和元类,一次实现,可以到处使用。

那能不能将这个控制类生成过程的结构单独抽象出来呢?而且有没有什么方法能防止同一个对象多次被__init__初始化。下面我们看一种能被不同的类使用的更加抽象的结构。

6.替换__new__方式

我们定义的类作为一个对象,通过替换其部分属性可以达到控制类对象生成的目的。

def Singleton(cls):
  _instance = {}
  cls._origin_new = cls.__new__
  cls._origin_init = cls.__init__
  @functools.wraps(cls.__new__)
  def _singleton_new(cls, *args, **kwargs):
    if cls not in _instance:
      sin_instance = cls._origin_new(cls, *args, **kwargs)
      sin_instance._origin_init(*args, **kwargs)
      _instance[cls] = sin_instance
    return _instance[cls]
  # As a special case,__new__ is a staticmethod, need convert function to staticmethod by self
  cls.__new__ = staticmethod(_singleton_new)
  # setattr(cls, '__new__', staticmethod(_singleton_new))
  cls.__init__ = lambda self, *args, **kwargs: None
  # setattr(cls, '__init__', lambda self, *args, **kwargs: None)
  return cls
@Singleton
class my_cls(object):
  pass

上面我们通过替换类的__new__函数和__init__函数的方式,保证被Singleton装饰的类只有一个对象会被原来的__new__和__init__生成和初始化。

这里必须要替换类的__init__函数,而且该函数应该什么都不做。原因在于替换之后的__new__返回唯一的对象后,会自动调用现在的__init__函数。

原来的__init__函数已经在创建唯一一个对象时被调用过。而且只能被调用一次。

这里返回的并不是闭包结构,只是使用装饰器修改了类的部分属性,返回的仍是传入的类。但是类的__new__函数引用了Singleton中的local variable _instance。

my_cls.__new__.func_closure[0].cell_contents
==
{<class '__main__.my_cls'>: <__main__.my_cls object at 0x02954810>}
==
_instance

Cell 对象my_cls.__new__.func_closure[0]中存放的便是类my_cls唯一的实例。

当然我们可以将my_cls唯一的对象作为类的一个静态数据成员放入cls.__dict__中来替代_instance = {},但是显然闭包结构更适合。

7.注意事项

文中借助python语言的类创建对象过程的相关原理,介绍了几种不同的单例模式实现方式。

为了保留被装饰对象的一些属性,可以使用@functools.wraps的方式对返回的闭包进行装饰。

平时建议使用前两种实现方式,也就是闭包方式和元类方式。其他情况多少有点玩弄python语法技巧的一些嫌疑,当然了,作为学习python来说还是比较有意义的。

建议多关注语言特性的应用以及如何的解决实际的问题,不要沉迷于语言实现的一些细枝末节。本末倒置总会有些得不偿失嘛。尤其是python作为一种非常实用的语言。

本文介绍中如果有什么不当之处欢迎指正,如果有其他的更好的实现方式也请不吝赐教。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Python下singleton模式的实现方法

    很多开发人员在刚开始学Python 时,都考虑过像 c++ 那样来实现 singleton 模式,但后来会发现 c++ 是 c++,Python 是 Python,不能简单的进行模仿. Python 中常见的方法是借助 global 变量,或者 class 变量来实现单件.本文就介绍以decorator来实现 singleton 模式的方法.示例代码如下: ##----------------------- code begin ----------------------- # -*- cod

  • Python中实现单例模式的n种方式和原理

    在Python中如何实现单例模式?这可以说是一个经典的Python面试题了.这回我们讲讲实现Python中实现单例模式的n种方式,和它的原理. 什么是单例模式 维基百科 中说: 单例模式,也叫单子模式,是一种常用的软件设计模式.在应用这个模式时,单例对象的类必须保证只有一个实例存在.许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为.比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获

  • 详解python实现小波变换的一个简单例子

    最近工作需要,看了一下小波变换方面的东西,用python实现了一个简单的小波变换类,将来可以用在工作中. 简单说几句原理,小波变换类似于傅里叶变换,都是把函数用一组正交基函数展开,选取不同的基函数给出不同的变换.例如傅里叶变换,选择的是sin和cos,或者exp(ikx)这种复指数函数:而小波变换,选取基函数的方式更加灵活,可以根据要处理的数据的特点(比如某一段上信息量比较多),在不同尺度上采用不同的频宽来对已知信号进行分解,从而尽可能保留多一点信息,同时又避免了原始傅里叶变换的大计算量.以下计

  • Python中单例模式总结

    一.单例模式 a.单例模式分为四种:文件,类,基于__new__方法实现单例模式,基于metaclass方式实现 b.类实现如下: class Sigletion(objects): import time def __init__(self): time.sleep(1) @classmethod def instance(cls,*args,**kwargs) if not hasattr(Sigletion,'_instance'): Sigletion._instance=Sigleti

  • Python使用metaclass实现Singleton模式的方法

    本文实例讲述了Python使用metaclass实现Singleton模式的方法.分享给大家供大家参考.具体实现方法如下: class Singleton(type): def __call__(cls, *args, **kwargs): print "Singleton call" if not hasattr(cls, 'instance'): cls.instance = super(Singleton, cls).__call__(*args, **kwargs) retur

  • python中单例常用的几种实现方法总结

    前言 最近这两天在看自己之前写的代码,所以正好把用过的东西整理一下,单例模式,在日常的代码工作中也是经常被用到, 所以这里把之前用过的不同方式实现的单例方式整理一下 什么是单例? 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式. 那么单例模式有什么用途呢?举个常见的单例模式例子,我们平时使用的电脑上都有一个回收站,在整个操作系统中,回收站只能有一个实例,整个系统都使用这个唯一的实例,而且回收站自行提供自己的实例,因此回收站是单例模

  • Python实现Singleton模式的方式详解

    前言 使用python实现设计模式中的单例模式.单例模式是一种比较常用的设计模式,其实现和使用场景判定都是相对容易的.本文将简要介绍一下python中实现单例模式的几种常见方式和原理.一方面可以加深对python的理解,另一方面可以更加深入的了解该模式,以便实际工作中能更加灵活的使用单例设计模式. 本文将介绍常见的实现单例模式的几种方式,这里暂不考虑多线程的情况. 为了准备该篇博文,之前写了几篇相关的文章依次完整的介绍了相关的概念,下面会在需要的时候给出链接. 装饰器作为python实现单例模式

  • Python selenium 三种等待方式详解(必会)

    很多人在群里问,这个下拉框定位不到.那个弹出框定位不到-各种定位不到,其实大多数情况下就是两种问题:1 有frame,2 没有加等待.殊不知,你的代码运行速度是什么量级的,而浏览器加载渲染速度又是什么量级的,就好比闪电侠和凹凸曼约好去打怪兽,然后闪电侠打完回来之后问凹凸曼你为啥还在穿鞋没出门?凹凸曼分分中内心一万只羊驼飞过,欺负哥速度慢,哥不跟你玩了,抛个异常撂挑子了. 那么怎么才能照顾到凹凸曼缓慢的加载速度呢?只有一个办法,那就是等喽.说到等,又有三种等法,且听博主一一道来: 1. 强制等待

  • Python中捕获键盘的方式详解

    python中捕获键盘操作一共有两种方法 第一种方法: 使用pygame中event方法 使用方式如下:使用键盘右键为例 if event.type = pygame.KEYDOWN  and event.key =pygame.K_RIGHT:        print('向右移动') 第二种方法: 使用pygame中的key模块 1,使用pygame.key.get_pressed()返回一个包含键盘中所有按键的元组,元组用一个变量接收.如: keys_pressed = pygame.ke

  • Python标准库time使用方式详解

    目录 1.time库 1.1.获取格林威治西部的夏令时地区的偏移秒数 1.2.时间函数 1.3.格式化时间.日期 1.4.单调时钟 1.time库 时间戳(timestamp)的方式:通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量 结构化时间(struct_time)方式:struct_time元组共有9个元素 格式化的时间字符串(format_string),时间格式的字符串 1.1.获取格林威治西部的夏令时地区的偏移秒数 如果该地区在格林威治东部会返回负值(

  • Python实现最常见加密方式详解

    前言 我们所说的加密方式,都是对二进制编码的格式进行加密的,对应到Python中,则是我们的Bytes. 所以当我们在Python中进行加密操作的时候,要确保我们操作的是Bytes,否则就会报错. 将字符串和Bytes互相转换可以使用encode()和decode()方法.如下所示: # 方法中不传参数则是以默认的utf-8编码进行转换In [1]: '南北'.encode()Out[1]: b'\xe5\x8d\x97\xe5\x8c\x97'In [2]: b'\xe5\x8d\x97\xe

  • Python批量修改文件名的方式详解

    目录 批量修改文件名(保留后缀) 批量修改文件名(全改) 读取文件下的所有文件名 总结 批量修改文件名(保留后缀) 这种方法,保留了文件原本的后缀.这里主要演示的是对图片名的修改. # 批量修改文件名 # 批量修改图片文件名 import os import re import sys path = 'F:\Temp\ZZ' fileList = os.listdir(path) # 待修改文件夹 print("修改前:"+str(fileList)) # 输出文件夹中包含的文件 cu

  • 单利模式及python实现方式详解

    单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保 某一个类只有一个实例存在 .当希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. 比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息.如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重

  • Python中断多重循环的几种方式详解

    这篇文章主要介绍了Python中断多重循环的几种方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 I. 跳出单循环 不管是什么编程语言,都有可能会有跳出循环的需求,比如枚举时,找到一个满足条件的数就终止.跳出单循环是很简单的,比如 for i in range(10): if i > 5: print i break 然而,我们有时候会需要跳出多重循环,而break只能够跳出一层循环,比如 for i in range(10): for

  • Python实现单例模式的四种方式详解

    简介:单例模式可以保证一个类仅有一个实例,并提供一个访问它的全局访问点.适用性于当类只能有一个实例而且客户可以从一个众所周知的访问点访问它,例如访问数据库.MQ等. 实现方式: 1.通过导入模块实现 2.通过装饰器实现 3.通过使用类实现 4.通过__new__ 方法实现 单例模块方式被导入的源码:singleton.py # -*- coding: utf-8 -*- # time: 2022/5/17 10:31 # file: singleton.py # author: tom # 公众

  • Python selenium 父子、兄弟、相邻节点定位方式详解

    今天跟大家分享下selenium中根据父子.兄弟.相邻节点定位的方法,很多人在实际应用中会遇到想定位的节点无法直接定位,需要通过附近节点来相对定位的问题,但从父节点定位子节点容易,从子节点定位父节点.定位一个节点的哥哥节点就一筹莫展了,别急,且看博主一步步讲解. 1. 由父节点定位子节点 最简单的肯定就是由父节点定位子节点了,我们有很多方法可以定位,下面上个例子: 对以下代码: <html> <body> <div id="A"> <!--父节

随机推荐