首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

你真的了解Python的单例模式吗?

你真的了解Python的单例模式吗?

最近在用Python单例模式的时候遇到一些问题, 还是自己太年轻了, 在这里总结一下我在使用这个设计模式的时候的坑.

前言(单例模式简介)

单例模式提供了这样一个机制,即确保类有且只有一个特定类型的对象,并提供全局访问点。因此,单例模式通常用于下列情形,例如日志记录或数据库操作、打印机后台处理程序,以及其他程序——该程序运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求。例如,我们可能希望使用一个数据库对象对数据库进行操作,以维护数据的一致性;或者希望使用一个日志类的对象,将多项服务的日志信息按照顺序转储到一个特定的日志文件中。

简单来说, 单例模式可以总结为下面三个要点:

•确保类有且只有一个对象被创建。•为对象提供一个访问点,以使程序可以全局访问该对象。•控制共享资源的并行访问。

下文中, 我们将会用Python来实现单例模式, 不做特殊说明, 下文中所有的代码均基于Python 3.6.8实现.

经典的单例模式

代码如下所示:

class Singleton:

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

def test(): s1 = Singleton() s2 = Singleton() print(f"s1: {s1}\ns2: {s2}")

我们来运行代码查看一下运行结果.

可以发现上述代码满足如下两个要求:

•只允许Singleton类生成一个实例。•如果已经有一个实例了,会重复提供同一个对象。

懒汉实例化版单例模式

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

class LazySingleton: __instance = None

def __init__(self): if not LazySingleton.__instance: print('Called __init__ method...') else: print("Instance already created @: ", self.get_instance())

@classmethod def get_instance(cls): if not cls.__instance: cls.__instance = LazySingleton() return cls.__instance

def test():

s1 = LazySingleton() print("Created Object", LazySingleton.get_instance()) s2 = LazySingleton()

同样来看一下代码的运行结果,

利用元类实现单例模式

为什么会考虑到这么写, 因为如果我想对某个类实现单例模式, 我要复制上面的一堆代码, 这显现不够Pythonic, 因此考虑一下使用元类来实现. 有人可能发问了, 这为什么不用继承来实现, 写一个基类, 其他子类继承他不就好了吗? 悄悄的说一句, 如果用继承的话是有坑的, 下面首先来看一下继承方法为什么不可以.

来看一个简单的例子, 还是使用前面所说的简单单例.

class Singleton:

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

class FirstChildSingleton(Singleton): pass

class SecondChildSingleton(Singleton): pass

def test(): s1 = Singleton() s2 = Singleton() print(f"s1: {s1}\ns2: {s2}")

first_child = FirstChildSingleton() second_child = SecondChildSingleton() print(f"first child: {first_child}\nsecond child: {second_child}")

打印一下结果, 可以发现,

这里所有的子类也是同一个对象, 这显然是不行的.

什么是元类

让我们先来了解一下元类。元类是一个类的类,这意味着该类是它的元类的实例。使用元类,程序员有机会从预定义的Python类创建自己类型的类。

在Python中,一切皆对象。如果我们说a=1,则type(a)返回,这意味着a是int类型。但是,type(int)返回,这表明存在一个元类,因为int是type类型的类。

具体有关元类的知识, 在本文中不多介绍了, 不是这一篇文章的重点.

单例实现

首先还是老套路, 先来看一下代码:

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

class FirstChildMetaSingleton(metaclass=MetaSingleton): pass

class SecondChildMetaSingleton(metaclass=MetaSingleton): pass

def test():

s1 = FirstChildMetaSingleton() s2 = SecondChildMetaSingleton() print(f"s1: {s1}\ns2: {s2}")

我们可以发现, 这样的话, 实例化出来的单例就不是同一个了.

线程安全的单例

你以为写个元类, 这样就可以解决一切问题吗? 哈哈, 太天真了, 上述的写法实际上是非线程安全的, 我们来看一个例子.

先来看一下代码,

class SimpleSingleton(metaclass=MetaSingleton): def __init__(self): time.sleep(1)

def create_in_thread(name): s = SimpleSingleton() print(f'{name}: {s}')

def test(): t1 = threading.Thread(target=create_in_thread, args=("first thread",)) t2 = threading.Thread(target=create_in_thread, args=("second thread",))

t1.start() t2.start() t1.join() t2.join()

从上图中可以看到, 这样会创建两个单例, 也就是说这样代码是非线程安全的, 我太难了, 下面来看一个线程安全的单例吧.

class MetaThreadSecuritySingleton(type): _instance_lock = threading.Lock()

def __call__(cls, *args, **kwargs): if not hasattr(cls, "_instance"): with MetaThreadSecuritySingleton._instance_lock: if not hasattr(cls, "_instance"): cls._instance = super(MetaThreadSecuritySingleton, cls).__call__(*args, **kwargs) return cls._instance

class SimpleThreadSecuritySingleton(metaclass=MetaThreadSecuritySingleton): def __init__(self): time.sleep(1)

def create_in_thread(name): s = SimpleThreadSecuritySingleton() print(f'{name}: {s}')

def test(): t1 = threading.Thread(target=create_in_thread, args=("first thread",)) t2 = threading.Thread(target=create_in_thread, args=("second thread",))

t1.start() t2.start() t1.join() t2.join()

最终这个单例终于变得线程安全了, 生活中处处充满着惊喜, 微笑着面对它, 奥利给.

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200506A0S58T00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券