一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Python单例模式和工厂模式学习笔记

时间:2016-11-08 编辑:简简单单 来源:一聚教程网

一. 单例模式( Singleton )

所谓单例模式,也就是说不管什么时候都要确保只有一个对象实例存在。很多情况下,整个系统中只需要存在一个对象,所有的信息都从这个对象获取,比如系统的配置对象,或者是线程池。这些场景下,就非常适合使用单例模式。

总结起来,就是说不管初始化一个对象多少次,真正干活的对象只会生成一次并且在首次生成。

用Python 实现单例模式的方法有很多,先来看第一种方式。


# !/usr/bin/python3
# -*- coding:utf-8 -*-

class singleton(object):
    """
    单列模式
    """

    class _A(object):
        """
        真正干活的类,对外隐藏。
        """

        def __init__(self):
            pass

        def display(self):
            """ 返回当前实例的ID,是全局唯一的"""
            return id(self)

    # 类变量,用于存储_A 的实例
    _instance = None

    def __init__(self):
        """ 先判断类变量中是否已经保存了_A 的实例,如果没有则创建一个后返回"""
        if singleton._instance is None:
            singleton._instance = singleton._A()

    def __getattr__(self, attr):
        return getattr(self._instance, attr)


if __name__ == "__main__":
    # 创建2个实例
    s1 = singleton()
    s2 = singleton()
    print(id(s1), s1.display())
    print(id(s2), s2.display())

使用装饰器

装饰器维护一个字典对象instances,缓存了所有单例类,只要单例不存在则创建,已经存在直接返回该实例对象。

def singleton(cls):
    instances = {}

    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return wrapper


@singleton
class Foo(object):
    pass

foo1 = Foo()
foo2 = Foo()

print foo1 is foo2   # True

使用基类

__new__ 是真正创建实例对象的方法,所以重写基类的 __new__ 方法,以此来保证创建对象的时候只生成一个实例

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance


class Foo(Singleton):
    pass

foo1 = Foo()
foo2 = Foo()

print foo1 is foo2  # True

使用元类

元类(参考: 深刻理解Python中的元类 )是用于创建类对象的类,类对象创建实例对象时需要一定会调用 __call__ 方法,因此在调用 __call__ 时候保证始终只创建一个实例即可, type 是python中的一个元类。

class Singleton(type):
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instance


class Foo(object):
    __metaclass__ = Singleton


foo1 = Foo()
foo2 = Foo()

print foo1 is foo2  # True

# !/usr/bin/python3 # -*- coding:utf-8 -*- class singleton(object): """ 单列模式 """ class _A(object): """ 真正干活的类,对外隐藏。 """ def __init__ (self): pass def display(self): """ 返回当前实例的ID,是全局唯一的 """ return id(self) # 类变量,用于存储_A 的实例 _instance = None def __init__ (self): """ 先判断类变量中是否已经保存了_A 的实例,如果没有则创建一个后返回 """ if singleton._instance is None: singleton._instance = singleton._A() def __getattr__ (self, attr): return getattr(self._instance, attr) if __name__ == " __main__ " : # 创建2个实例 s1 = singleton() s2 = singleton() print (id(s1), s1.display()) print (id(s2), s2.display())

Output:


1917919737784 1917919737896
1917919738008 1917919737896

View Code
以上的单列模式实现中,借助了使用类变量 Singleton._instance 来存储创建的实例,并且保证只会创建一次实例。由于 Python 是一门动态语言,可以在运行时改变类定义。上面的代码中,在首次初始化 Singleton 时,将首次生成类 _A 的实例,并将其存储到 Singleton._instance 中,以后每次初始化 Singleton 时都从 Singleton._instance 获取真正干活的实例,这样就实现了单例模式。

从输出中可以看到,尽管创建了两个不同的实例(实例 ID 不同),但是访问其属性的时候得到的 ID 是一致的。以上代码虽然很好的实现了单例模式,但是在真正的项目开发中这种方式却不够灵活,因为要将真正干活的类内置在单例类中。 我们知道在 Python 中装饰器很好用,那能否将单例类实现成一个装饰器呢?答案是肯定的,下面实现代码。


# !/usr/bin/python3
# -*- coding:utf-8 -*-
import sqlite3
from flask import current_app
from flask import _app_ctx_stack as stack


class SQLite3(object):
    def __init__(self, app=None):
        self.app = app
        if app is not None:
            self.init_app(app)


def init_app(self, app):
    """典型的 Flask 扩展的初始化方式"""
    app.config.setdefault('SQLITE3_DATABASE', ':memory:')
    app.teardown_appcontext(self.teardown)


def connect(self):
    """连接到 sqlite 数据库"""
    return sqlite3.connect(current_app.config['SQLITE3_DATABASE'])


def teardown(self, exception):
    """关闭 sqlite 链接"""
    ctx = stack.top
    if hasattr(ctx, 'sqlite3_db'):
        ctx.sqlite3_db.close()


@property
def connection(self):
    """
    单例模式在这里:使用 flask._app_ctx_stack 存放 sqlite 链接,
    每次获取数据库链接时都通过 connection 获取
    """
    ctx = stack.top
    if ctx is not None:
        if not hasattr(ctx, 'sqlite3_db'):
            ctx.sqlite3_db = self.connect()
        return ctx.sqlite3_db


在以上的代码中,在每次使用数据库的时候通过 SQLite3.connection 获取数据库连接就可以了。 SQLite3.connection 保证了数据库连接只会发生一次,其原理和之前实现单例模式的方式相同,只不过这里存储实例的地方变成 flask._app_ctx_stack 了。

通过以上几段代码,可以看到单例模式的实现只需要找一个变量存放创建的实例,然后每次获取实例时,先检查变量中是否已保存实例,如果没有则创建一个实例并将其存放到变量中,以后都从这个变量中获取实例就可以了。单例模式中,只会创建一次实例。

热门栏目