Python 中 Singleton 的写法及其拓展

为什么要有 Singleton ?

重要性无需多言,咱们在项目中常常有「要一个进程全局的变量(内存块)」的需求,并且单例模式是几种设计模式中最容易的。编程

偷懒且有用的作法:模块级别常量

我常用这种方式,由于简单且不易出错。设计模式

众所周知,Python 的 module 概念,是一个自然的 Singleton。并且 Python 是多范式语言,能够没必要像 Java 那样使用 class 去处理这件事情,在 module 级别定义一个常量,是一种很天然的想法。bash

代码以下:框架

  • 定义方
singleton.py

class _MySingleton(object):
    """ 咱们使用下划线开头, 告诫调用者, 不要直接 new 也不要来访问这个class """
    def __init__(self, name, age):
        self._name = name
        self._age = age

    def print_name(self):
        print(self._name)

# 能够定制多个全局实例
S1 = _MySingleton('s1', 22)
S2 = _MySingleton('s2', 11)
复制代码
  • 调用方
caller.py

from singleton import S1

# 尽情使用S1( 在任意点import 均可以 ), 它是全局惟一的!
复制代码

正规作法:元编程

有些人不喜欢上面那种作法,他们认为「破坏了代码的纯粹性」,这个时候咱们可使用元编程的方式,让咱们更近一步。函数

这是从 Python Cookbook 摘取出来的代码。ui

  • 元类基类
class SingletonMetaclass(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(SingletonMetaclass, self).__call__(*args, **kwargs)
            return self.__instance
        else:
            return self.__instance
复制代码
  • 继承了元类基类的类
class SpamSingleton(metaclass=SingletonMetaclass):
    """注意: 根据Singleton的定义, 构造函数通常须要使用默认的构造函数"""
    def get_addr(self):
        return id(self)
复制代码
  • 调用方
from singleton import SpamSingleton

s1 = SpamSingleton()
s2 = SpamSingleton()

pritn(id(s1), id(s2))   # 内存地址是同样的
复制代码

老实说,理解 SingletonMetaclass 的做用过程仍是有点困难的,我花了很久才搞清楚上面各个方法的调用流程。搜索引擎

不过 SingletonMetaclass 的做用也是巨大的,咱们定义将其放入一个 base.py 的文件中,任什么时候刻咱们想要定义某个 Singleton class,直接从其继承便可,简单且方便。spa

比较 Hack 的作法:Borg 模式

很少说,搜索引擎搜出来的结果,全都是推荐这种作法,让人觉得这是「主流作法」(其实并非)。设计

这种作法,修改了 Singleton 的定义,即:全部变量共享一个内存块,可是这个内存块的内容是可变化的。而后经过将实例的 __dict__ 方法重定向 class 的 __dict__ 方法,以达到其目的。code

可是这种作法不是很符合我对 Singleton的感知,即:全局惟一,且其内容最好也不要变化。因此在实际开发中,并不喜欢这种作法。

更进一步

一般来讲,更好的方法则是:在应用代码以外引入一个Manager,让这个Manager来为咱们建立和管理单例。这种作法也很广泛,就是咱们一般所说的「依赖注入框架」。

若是使用 Spring,那么一切都是很美妙的;若是没有使用 Spring,我常用 Guice 来作个人依赖注入框架。

不过 Python 社区貌似对「依赖注入框架」、「IoC容器」等等都不怎么感冒(实际上是好东西),在此先不提。

总结

从我的的倾向来看,比较喜欢「掌握一种或两种方式,而后使用最熟练,并且不出错」的观点,因此我只推荐第一二种方式。

相关文章
相关标签/搜索