首页 > 数据库 > 数据库开发技术 > 你知道单例模式吗?Python如何实现?
如果你准备从事Python后端开发,那么在面试中可能会被问到如何实现单例模式(Singleton Pattern)。
单例模式(Singleton Pattern)是一种软件设计模式,顾名思义,旨在确保一个类只有一个实例,并提供一个全局访问点来访问这个示例。单例模式通过控制实例化过程,确保一个类在整个程序生命周期中只有一个对象实例存在。
简单来说,一个单例类有且仅有一个对象实例。如果我们尝试再次实例化,实际上最终得到的却是同一个实例。
单例模式在以下场景比较有用:
单例模式可以通过多种方式实现,以下是一些常见的实现方式:
这种单例表示实例在第一次使用时才创建:
class LazySingleton:
__instance = None
def __new__(cls, *args, **kwargs):
# if cls.__instance does not exist, instantiate it.
if cls.__instance is None:
print('Creating LazySingleton instance')
cls.__instance = super(LazySingleton, cls).__new__(cls, *args, **kwargs)
# else, simply return the existing instance
return cls.__instance
if __name__ == '__main__':
print('Before using LazySingleton')
lazy1 = LazySingleton() # It should print "Creating LazySingleton instance" at here.
lazy2 = LazySingleton() # It does not create instance again.
print(f'layzy1 is lazy2: {lazy1 is lazy2}')
从输出中可以看到虽然我们创建两个实例,但是“Creating LazySingleton instance”只打印了一次,说明只创建了一个实例。
这里,我们重写了 __new__ 魔术方法,它定义了类实例化的方式。
cls.__instance 不存在,那么我们就创建它,并且对其进行实例化。cls.__instance 已存在,那么我们只需要简单地返回现有示例即可。cls.__instance 为 None 时才会创建该实例。比如,在下面的示例中,我们使用懒汉式单例模式实现一个配置管理类,确保配置在整个程序中只有一个实例。
class ConfigurationManager:
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super(ConfigurationManager, cls).__new__(cls, *args, **kwargs)
# initiate configuration
cls.__instance.config = {}
return cls.__instance
def get(self, key):
return self.config.get(key)
def set(self, key, value):
self.config[key] = value
if __name__ == '__main__':
config1 = ConfigurationManager()
config2 = ConfigurationManager()
config1.set('database', 'MySQL')
print(f"config2: {config2.get('database')}")
print(f'config1 is config2: {config1 is config2}')
print(f'config1: {config1}')
print(f'config2: {config2}')
在这个示例中,我们尝试创建 ConfigurationManager 的两个实例 config1 和 config2,并且用 config1 设置实例信息,但是我们用 config2 获取到实例信息与 config1 设置的一样。此外,当我们输出这两个实例时,他们都有相同的哈希地址 0x00000151832FFE10。
这就意味着 config1 和 config2 也许是两个不同的变量,但它们实际上指向同一个对象,即 ConfigurationManager 的单实例。
这种单例表示实例在类加载时就创建:
class EagerSingleton:
__instance = None
def __new__(cls, *args, **kwargs):
raise RuntimeError('Call get_instance() instead!')
@classmethod
def get_instance(cls):
return cls.__instance
@classmethod
def _create_instance(cls):
print('Creating EagerSingleton instance')
cls.__instance = super(EagerSingleton, cls).__new__(cls)
if __name__ == '__main__':
# instance created intermediately at class load time.
EagerSingleton._create_instance()
print('Before using EagerSingleton')
eager1 = EagerSingleton.get_instance() # does not create instance again.
eager2 = EagerSingleton.get_instance()
print(f'eager1 is eager2: {eager1 is eager2}')
print(f'eager1: {eager1}')
print(f'eager2: {eager2}')
_create_instance 方法用于在类加载时创建实例。从输出中可以看到,在类加载的时候就创建了实例,后面再次实例化实际上是调用 get_instance 方法获取现有实例。
在下面这个示例中,我们使用饿汉式单例创建了一个日志记录类,实现日志的记录和管理。
class Logger:
__instance = None
def __new__(cls, *args, **kwargs):
raise RuntimeError('Call get_instance() instead!')
@classmethod
def get_instance(cls):
return cls.__instance
@classmethod
def _create_instance(cls):
cls.__instance = super(Logger, cls).__new__(cls)
cls.__instance.logs = []
def log(self, message):
self.logs.append(message)
def get_logs(self):
return self.logs
if __name__ == '__main__':
Logger._create_instance()
logger1 = Logger.get_instance()
logger2 = Logger.get_instance()
logger1.log('Log message 1')
logger1.log('Log message 2')
print(f'Log info: {logger1.get_logs()}')
print(f'logger1 is logger2: {logger1 is logger2}')
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class DecratorSingleton:
pass
这里,我们通过装饰器将 DecratorSingleton 变成了一个单例类。
下面我们通过一个数据库连接池的示例来展现装饰器实现单例模式的用法:
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
print(f'Creating a new instance of {cls.__name__}')
instances[cls] = cls(*args, **kwargs)
else:
print(f'Using existing instance of {cls.__name__}')
return instances[cls]
return get_instance
@singleton
class DatabaseConnectionPool:
def __init__(self):
self.connections = []
def get_connection(self):
if self.connections:
return self.connections.pop()
else:
# create new connection
return 'New Connection'
def return_connection(self, connection):
self.connections.append(connection)
if __name__ == '__main__':
pool1 = DatabaseConnectionPool()
pool2 = DatabaseConnectionPool()
conn1 = pool1.get_connection()
pool2.return_connection(conn1)
print(pool1.get_connection())
print(f'pool1 is pool2: {pool1 is pool2}')
在我们的代码中使用了 DatabaseConnectionPool() 的方式来创建实例,但实际上是调用了装饰器返回的 get_instance 方法,而不是直接实例化 DatabaseConnectionPool 类。这样,DatabaseConnectionPool() 就变成了一个单例类。
通过元类控制类的实例化过程,从而实现单例模式:
class SingletonMeta(type):
__instance = {}
def __call__(cls, *args, **kwargs):
if cls not in cls.__instance:
cls.__instance[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
return cls.__instance[cls]
class Singleton(metaclass=SingletonMeta):
pass
下面是一个设备驱动管理器的示例,我们通过元类实现单例模式,确保设备驱动管理器只有一个实例。
class SingletonMeta(type):
__instance = {}
def __call__(cls, *args, **kwargs):
if cls not in cls.__instance:
cls.__instance[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
return cls.__instance[cls]
class DeviceDriverManager(metaclass=SingletonMeta):
def __init__(self):
# Initiate device driver
self.devices = {}
def register_device(self, device_id, device):
self.devices[device_id] = device
def get_device(self, device_id):
return self.devices.get(device_id)
if __name__ == '__main__':
manager1 = DeviceDriverManager()
manager2 = DeviceDriverManager()
manager1.register_device('d001', 'Printer Device')
print(manager2.get_device('d001')) # Output: Pinter Device
print(f'manger1 is manager2: {manager1 is manager2}')
这里,当我们通过 DeviceDriverManager() 的方式实例化时,实际上是调用了元类的 __call__ 方法,元类控制了类的实例只会被创建一个。
优点:
缺点:
单例模式也有个不可避免的潜在漏洞,那就是如果用户真的想打破你的单例类的话,他们可以重写单例类的 __new__ 方法。
class Singleton:
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__new__(cls)
return cls
if __name__ == '__main__':
Singleton.__new__ = object.__new__ # break singleton pattern
s1 = Singleton()
s2 = Singleton()
s3 = Singleton()
print(s1)
print(s2)
print(s3)
从输出中可以看出三个实例对象的哈希地址都不相同,此时,在我们的有意破坏之后,Singleton 类就再也不满足单例模式了。
目前并没有合适的方法阻止这种情况的发生。Python 似乎赋予了用户强大的实用功能,同时也给予了人们破坏代码的能力。
所以,我们只能寄希望于我们的用户不知道如何搞破坏吧!😄😄😄
推荐阅读👇👇👇
机器学习基础:今天的主角—逻辑回归(Logistics Regression)!献给正在入门机器学习的你!
机器学习基础:一文搞懂“二分类之王”—支持向量机(SVM)背后的数学原理
Python自动化:适合新手练习的5个有趣又实用的Python脚本,帮你快速掌握编程技能!拿走不谢!
万字长文:数据科学中 25 个最重要的数学概念(收藏!!!)
- END -
🌟 喜欢这篇文章吗?给个赞支持一下吧!🌟
如果你发现今天的内容对你有帮助,不妨点个赞并关注我,第一时间接收更多关于Python、数据分析和机器学习的深入分析和最新趋势。我们一起探索数据的力量,解锁AI的潜能。你的每一次互动都是对我的极大鼓励,也是我们共同成长的动力!

👇👇👇 关注并开启星标提醒,让知识与技能的火花不断在你的职业道路上燃烧吧!
原文作者:数据派探险家;如有侵权,请联系管理员删除。