在本文中,我们将介绍一种常用的软件设计模式 —— 单例模式。
通过示例,演示单例创建,并确保该实例在整个应用程序生命周期中保持一致。同时探讨它在 Python 中的目的、益处和实际应用。
关键点:
1、单例模式只有一个实例存在;
2、单例模式必须自己创建自己的唯一实例;
3、单例模式是一种软件设计模式,而不是专属于某种编程语言的语法;
公众号: 滑翔的纸飞机
现在让我们开始吧!
那什么是单例模式?
这是个非常简单的问题,基本上就是当我们有一个类时,我们只能实例化该类的一个实例对象,无论你是要优化资源使用、配置数据,还是要为我们的程序优化某个全局只读数据,该模式都提供了一个清晰有效的解决方案。
因为有不同的方式可以创建。这里我将向你展示几种简单的方法。
__init__
在这里,我将定义一个 Singleton
类并定义一个方法getInstance()
。我们的想法是,无论我在哪里调用,它都会返回 Singleton.getInstance()
的特定实例。
考虑,无论是否已创建类实例,都将返回特定实例对象。因此,在这里使用类变量,跟踪实例是否已创建。
class Singleton:
# 类变量 __instance 将跟踪唯一的对象实例
__instance = None
@staticmethod
def getInstance():
if Singleton.__instance == None:
Singleton()
return Singleton.__instance
现在看起来当然还不是一个单例,接下去通过__init__()
方法(类似于构造函数),在Singleton
对象实例化时被调用。该对象会将类变量设置为对象实例。__init__()
全局只应被调用一次,并且由该类中的方法调用(getInstance()
),保证__instance
类变量赋值之后不允许再此赋值。
def __init__(self):
if Singleton.__instance != None:
raise Exception("Singleton object already created!")
else:
Singleton.__instance = self
现在,让我们创建实例验证:
在s1 情况下会调用__init__()
创建实例;
在s2 情况下返回与s1相同实例;
s1 = Singleton.getInstance()
print(s1)
s2 = Singleton.getInstance()
print(s2)
而且如果我们在 s1 上设置属性,s2 也会有相同的值,因为它们都指向同一个对象。
s1.x = 5
print(s2.x)
它将打印以下输出
<__main__.Singleton object at 0x10e24fdf0>
<__main__.Singleton object at 0x10e24fdf0>
5
完整代码示例:
"""
@Time:2023/9/15 01:18
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""
class Singleton:
# 类变量 __instance 将跟踪唯一的对象实例
__instance = None
def __init__(self):
if Singleton.__instance != None:
raise Exception("Singleton object already created!")
else:
Singleton.__instance = self
@staticmethod
def getInstance():
if Singleton.__instance == None:
Singleton()
return Singleton.__instance
if __name__ == '__main__':
s1 = Singleton.getInstance()
print(s1)
s2 = Singleton.getInstance()
print(s2)
s1.x = 5
print(s2.x)
__call__
& metaclass__call__()
:Python中,只要在创建类时定义了__call__()
方法,这个类就是可调用对象。
针对__call__()
使用参考示例:
class Father:
def __call__(self):
print('My call() function')
fat=Father()
fat()
输出:My call() function
很神奇不是,实例对象也可以像函数一样作为可调用对象来用。
接下去通过__call__
实现单例,完整代码示例如下:
"""
@Time:2023/9/15 01:26
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""
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 Singleton1(metaclass=Singleton):
pass
if __name__ == '__main__':
s1 = Singleton1()
print(s1)
s2 = Singleton1()
print(s2)
s1.x = 5
print(s2.x)
输出:
<__main__.Singleton1 object at 0x1120c8280>
<__main__.Singleton1 object at 0x1120c8280>
5
__new__
简单介绍下__new__()
:只要是面向对象的编程语言,类的实例化都一定包含两个步骤:
(1)在内存中创建对象,即开辟一块内存空间来存放类的实例(Instance);
(2)初始化对象,即给实例的属性赋予初始值,例如全部填空;
在 python 中,第一步由 __new__
函数负责,第二步由 __init__
函数负责。
在这种方法中,我们将使用__new__()
,让它检查类变量以查看实例是否已创建,如果没有,我们就创建它,无论是否需要创建,我们都会返回实例。
class Singleton:
# 类变量 __instance 将跟踪唯一的对象实例
__instance = None
def __new__(cls):
if (cls.__instance is None):
cls.__instance = super(Singleton, cls).__new__(cls)
return cls.__instance
因此,当第一次创建单例对象时,它会运行__new__
,当判断__instance
为 None,则创建对象并赋值至__instance
类变量。下次运行时,发现__instance
不是 None 已被设置,于是将返回 Singleton.__instance
这里通过调用超类的方法来实际返回对象实例本身,然后再返回它的__new__。
s1 = Singleton()
print(s1)
s2 = Singleton()
print(s2)
# 同样,如果在 s1 上设置一个属性,s2将具有相同的值,因为它们都引用同一个对象
s1.x = 5
print(s2.x)
它将打印以下输出
<__main__.Singleton object at 0x10607beb0>
<__main__.Singleton object at 0x10607beb0>
5
完整示例:
"""
@Time:2023/9/16 23:04
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""
class Singleton:
# 类变量 __instance 将跟踪唯一的对象实例
__instance = None
def __new__(cls):
if (cls.__instance is None):
cls.__instance = super(Singleton, cls).__new__(cls)
return cls.__instance
if __name__ == '__main__':
s1 = Singleton()
print(s1)
s2 = Singleton()
print(s2)
# 同样,如果在 s1 上设置一个属性,s2将具有相同的值,因为它们都引用同一个对象
s1.x = 5
print(s2.x)
关于装饰器读者自行查阅其他博文;
本例照旧通过 __instance = {}
来跟踪实例对象。使用类地址作为键,实例作为值,每次创造实例时,检查该类是否存在实例,存在的话直接返回该实例即可,否则新建一个实例并存放在字典中。
函数装饰器示例:
"""
@Time:2023/9/16 23:29
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""
def singleton(cls):
__instance = {}
def inner():
if cls not in __instance:
__instance[cls] = cls()
return __instance[cls]
return inner
@singleton
class Cls(object):
def __init__(self):
print('My name Cls')
if __name__ == "__main__":
cls1 = Cls()
print(cls1)
cls1.x = 5
cls2 = Cls()
print(cls2)
print(cls2.x)
输出:
My name Cls
<__main__.Cls object at 0x10f284160>
<__main__.Cls object at 0x10f284160>
5
类装饰器示例:
"""
@Time:2023/9/16 23:39
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""
class Singleton(object):
def __init__(self, cls):
self._cls = cls
self._instance = {}
def __call__(self):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls()
return self._instance[self._cls]
@Singleton
class Cls(object):
def __init__(self):
print('My name Cls')
if __name__ == "__main__":
# print('=======使用1=======')
cls1 = Cls()
print(cls1)
cls1.x = 5
cls2 = Cls()
print(cls2)
print(cls2.x)
# print('=======使用2=======')
# cls1 = Singleton(Cls)
# cls2 = Singleton(Cls)
# print(cls1)
# print(cls2)
输出:
My name Cls
<__main__.Cls object at 0x10ede0850>
<__main__.Cls object at 0x10ede0850>
5
单例经常与设计模式结合使用,以解决最常见的问题,包括缓存、日志、配置设置和线程池。
案例1:
"""
@Time:2023/9/16 23:09
@Author:'jpzhang.ht@gmail.com'
@Describe:
"""
class Logger:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(Logger, cls).__new__(cls)
cls._instance._initialize()
return cls._instance
def _initialize(self):
self.log_file = open("log.txt", "a")
def log(self, message):
self.log_file.write(message + "\n")
self.log_file.flush()
def close(self):
self.log_file.close()
class LoggerFactory:
def create_logger(self):
return Logger()
# 使用方法
if __name__ == "__main__":
logger_factory = LoggerFactory()
logger1 = logger_factory.create_logger()
logger2 = logger_factory.create_logger()
logger1.log("This is a log message from logger1")
logger2.log("This is a log message from logger2")
logger1.close()
logger2.close()
在本例中,创建了一个采用单例模式的日志类Logger
。它确保只创建一个类实例。LoggerFactory类
是一个工厂类,可创建Logger类的实例。
log.txt:
This is a log message from logger1
This is a log message from logger2
运行代码后,可以看到两个对象(logger1、logger2),实际是Logger类的同一个实例。这样可以确保日志记录只使用一个日志文件。
案例2:
线程安全是实现单例模式时的一个重要考虑因素,因为多个线程可能会尝试同时访问或创建类的实例。如果没有适当的同步,这可能会导致创建多个实例,从而违反单例原则。
import threading
import random
class Singleton:
_instance = None
def __init__(self):
self.value = random.randint(1, 10)
def __new__(cls):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
def create_singleton(index):
s = Singleton()
print(f"Singleton instance created by thread {threading.current_thread().name}: {s} and value: {s.value}\n")
# 模拟多个线程同时创建单例
def problem_case():
threads = []
for i in range(5):
thread = threading.Thread(target=create_singleton, args=(i,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
# 使用锁来确保线程安全
class ThreadSafeSingleton:
__instance = None
__lock = threading.Lock()
def __init__(self):
self.value = random.randint(1, 10)
@classmethod
def get_instance(cls):
if not cls.__instance:
with cls.__lock:
if not cls.__instance:
cls.__instance = cls()
return cls.__instance
def create_thread_safe_singleton(index):
s = ThreadSafeSingleton.get_instance()
print(f"Singleton instance created by thread {threading.current_thread().name}: {s} and value: {s.value}\n")
def thread_safe_case():
threads = []
for i in range(5):
thread = threading.Thread(target=create_thread_safe_singleton, args=(i,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
if __name__ == "__main__":
print("Problem case (without thread safety):")
problem_case()
print("\nThread-safe case:")
thread_safe_case()
在上例中,ThreadSafeSingleton
使用线程锁类创建了一个锁对象,我们可以用它来同步对类变量的访问。然后定义一个类方法,首先检查是否已经创建了该类的实例。如果没有,它会获取锁,并创建该类的新实例。
备注:获取锁后再次校验是否已经创建了该类的实例。
输出:
Problem case (without thread safety):
Singleton instance created by thread Thread-10: <__main__.Singleton object at 0x102881760> and value: 4
Singleton instance created by thread Thread-7: <__main__.Singleton object at 0x102881760> and value: 3
Singleton instance created by thread Thread-6: <__main__.Singleton object at 0x102881760> and value: 10
Singleton instance created by thread Thread-8: <__main__.Singleton object at 0x102881760> and value: 9
Singleton instance created by thread Thread-9: <__main__.Singleton object at 0x102881760> and value: 4
Thread-safe case:
Singleton instance created by thread Thread-11: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
Singleton instance created by thread Thread-12: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
Singleton instance created by thread Thread-13: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
Singleton instance created by thread Thread-14: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
Singleton instance created by thread Thread-15: <__main__.ThreadSafeSingleton object at 0x102874a60> and value: 2
不难看出,上锁后符合单例原则。
实现该模式的每种方法都有自己的优缺点,选择哪种方法可能取决于具体的用例。虽然该模式在某些情况下很有用,但它也可能带来一些缺点,例如使代码更难测试和维护。
总之,单例设计模式是管理应用程序中类的单个实例的强大工具。不过,在使用时应谨慎,考虑系统的具体要求以及对可维护性、可测试性和并发性的潜在影响。