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


Posted in Python onNovember 14, 2018

在Python中如何实现单例模式?这可以说是一个经典的Python面试题了。这回我们讲讲实现Python中实现单例模式的n种方式,和它的原理。

什么是单例模式

维基百科 中说:

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

在日常编程中,最常用的地方就在于配置类了。举个例子:

from config import config
print(config.SQLALCHEMY_DB_URI)

我们当然是希望 config 在全局中都是唯一的,那么最简单的实现单例的方式就出来了:使用一个全局变量。

实现单例的方式

全局变量

我们在一个模块中实现配置类:

# config.py
class Config:
  def __init__(self, SQLALCHEMY_DB_URI):
    self.SQLALCHEMY_DB_URI = SQLALCHEMY_DB_URI
config = Config("mysql://xxx")

当然这只是一个例子。真正实现的时候我们肯定不会这样做,因为 __init__ 太难写了。也许我们可以考虑 Python 3.7 中引入的 dataclass :

# config.py
from dataclasses import dataclass
@dataclass
class Config:
    SQLALCHEMY_DB_URI = SQLALCHEMY_DB_URI
config = Config(SQLALCHEMY_DB_URI ="mysql://")

通过使用全局变量,我们在所有需要引用配置的地方,都使用 from config import config 来导入,这样就达到了全局唯一的目的。

使用metaclass

class Singleton(type):
  _instances = {}
  def __call__(cls, *args, **kwargs):
    if cls not in cls._instances:
      cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
    return cls._instances[cls]
class Config(metaclass=Singleton):
  def __init__(self, SQLALCHEMY_DB_URI):
    self.SQLALCHEMY_DB_URI = SQLALCHEMY_DB_URI

metaclass 是类的类,在Python中,instance是实例,class是类,metaclass是类的类。instance是class实例化的结果,而class是metaclass实例化的结果。因此, Config 在被实例化的时候,就会调用 Singleton.__call__ , 所以所有 Config() 的地方,最后都会返回同一个对象。

重写 __new__

class Singleton(object):
  _instance = None
  def __new__(class_, *args, **kwargs):
    if not isinstance(class_._instance, class_):
      class_._instance = object.__new__(class_, *args, **kwargs)
    return class_._instance
class Config(Singleton, BaseClass):
  pass

Python中,类实例化的过程是先执行 Config.__new__ 生成实例,然后执行 实例.__init__ 进行初始化的,所以通过重写 __new__ 也可以达到所有调用 Config() 的地方都返回同一个对象。

使用装饰器

def singleton(class_):
  class class_w(class_):
    _instance = None
    def __new__(class_, *args, **kwargs):
      if class_w._instance is None:
        class_w._instance = super(class_w, class_).__new__(class_, *args, **kwargs)
        class_w._instance._sealed = False
      return class_w._instance
    def __init__(self, *args, **kwargs):
      if self._sealed:
        return
      super(class_w, self).__init__(*args, **kwargs)
      self._sealed = True
  class_w.__name__ = class_.__name__
  return class_w
@singleton
class Config(BaseClass):
  pass

使用装饰器也能达到这样的目的,即:有闭包存储了实例,在每次调用 Config() 之前,检查该实例,如果已经初始化过,那么就直接返回,否则则调用 Config() 进行初始化,然后存储。

总结

看完了这四种实现单例的方式,不知道你有没有发现他们都有一个共同点,即:在真正调用 Config() 之前进行一些拦截操作,来保证返回的对象都是同一个:

  • 全局变量:不直接调用 Config() ,而使用同一个全局变量
  • 使用metaclass:metaclass重写 __call__ 来保证每次调用 Config() 都会返回同一个对象
  • 重写 __new__ :重写 __new__ 来保证每次调用 Config() 都会返回同一个对象
  • 使用装饰器:使用装饰器来保证每次调用 Config() 都会返回同一个对象

以上所述是小编给大家介绍的Python中实现单例模式的n种方式和原理,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Python 相关文章推荐
Python 元组(Tuple)操作详解
Mar 11 Python
Python中模拟enum枚举类型的5种方法分享
Nov 22 Python
在Python中操作字典之setdefault()方法的使用
May 21 Python
让python在hadoop上跑起来
Jan 27 Python
python中数组和矩阵乘法及使用总结(推荐)
May 18 Python
Django生成PDF文档显示在网页上以及解决PDF中文显示乱码的问题
Jul 04 Python
基于python的列表list和集合set操作
Nov 24 Python
基于Python获取城市近7天天气预报
Nov 26 Python
python生成13位或16位时间戳以及反向解析时间戳的实例
Mar 03 Python
浅谈python 调用open()打开文件时路径出错的原因
Jun 05 Python
Python docutils文档编译过程方法解析
Jun 23 Python
Django使用echarts进行可视化展示的实践
Jun 10 Python
解决Python print输出不换行没空格的问题
Nov 14 #Python
python3 实现一行输入,空格隔开的示例
Nov 14 #Python
python抓取京东小米8手机配置信息
Nov 13 #Python
python输入整条数据分割存入数组的方法
Nov 13 #Python
在Python中输入一个以空格为间隔的数组方法
Nov 13 #Python
python 输入一个数n,求n个数求乘或求和的实例
Nov 13 #Python
python判断完全平方数的方法
Nov 13 #Python
You might like
PHP 文件系统详解
2012/09/13 PHP
20个2014年最优秀的PHP框架回顾
2014/10/22 PHP
分享自定义的几个PHP功能函数
2015/04/15 PHP
php线性表的入栈与出栈实例分析
2015/06/12 PHP
最新制作ThinkPHP3.2.3完全开发手册
2015/11/23 PHP
phpStudy2016 配置多个域名期间遇到的问题小结
2017/10/19 PHP
NodeJS的url截取模块url-extract的使用实例
2013/11/18 NodeJs
JS简单实现登陆验证附效果图
2013/11/19 Javascript
node.js中的console.error方法使用说明
2014/12/10 Javascript
Ionic如何实现下拉刷新与上拉加载功能
2016/06/03 Javascript
深入理解JavaScript中Ajax
2016/08/02 Javascript
javascript 动态脚本添加的简单方法
2016/10/11 Javascript
jQuery中hover方法搭配css的hover选择器,实现选中元素突出显示方法
2017/05/08 jQuery
基于vue+ bootstrap实现图片上传图片展示功能
2017/05/17 Javascript
浅谈vue自定义全局组件并通过全局方法 Vue.use() 使用该组件
2017/12/07 Javascript
bootstrap treeview 树形菜单带复选框及级联选择功能
2018/06/08 Javascript
jQuery实现获取选中复选框的值实例详解
2018/06/28 jQuery
angular 实时监听input框value值的变化触发函数方法
2018/08/31 Javascript
JavaScript基于用户照片姓名生成海报
2020/05/29 Javascript
vue动态设置页面title的方法实例
2020/08/23 Javascript
[57:28]2018DOTA2亚洲邀请赛 4.6 淘汰赛 TNC vs Liquid 第一场
2018/04/10 DOTA
python网络编程学习笔记(九):数据库客户端 DB-API
2014/06/09 Python
python生成器表达式和列表解析
2016/03/10 Python
python实现一个简单的ping工具方法
2019/01/31 Python
对python中不同模块(函数、类、变量)的调用详解
2019/07/16 Python
Django 权限管理(permissions)与用户组(group)详解
2020/11/30 Python
使用HTML5做个画图板的方法介绍
2013/05/03 HTML / CSS
iframe在移动端的缩放的示例代码
2018/10/12 HTML / CSS
匡威帆布鞋美国官网:Converse美国
2016/08/22 全球购物
澳大利亚领先的在线美容商城:Adore Beauty
2017/04/14 全球购物
英国婚礼商城:Wedding Mall
2019/11/02 全球购物
美国主要的特色咖啡和茶公司:Peet’s Coffee
2020/02/14 全球购物
工厂门卫岗位职责
2013/11/25 职场文书
3.15国际消费者权益日主题活动活动总结
2014/03/16 职场文书
学校趣味运动会开幕词
2016/03/04 职场文书
Python+Tkinter打造签名设计工具
2022/04/01 Python