1 前言
单例模式(Singleton Pattern) 是一种创建型设计模式,旨在确保一个类在程序运行期间只有一个实例,并且提供一个全局访问点。
单例模式的主要作用是控制实例的数量,防止类被实例化多次,以节省资源并确保全局状态的唯一性。
1.1 场景
- 需要控制资源访问(数据库连接、文件句柄等)
- 想要确保全局状态唯一性(日志记录、配置文件管理等)
1.2 单利模式关键点
- 在程序的整个生命周期中只会创建一次对象实例
- 必须提供一个全局访问点,以便其他代码可以访问唯一实例
1.3 常见方案有点对比
- __new__方法适用于较为简单的单例实现。
- 装饰器方式使得代码更加优雅且易于复用。
- 模块方式利用了 Python 模块的特性,非常简洁。
- 元类方式适合需要更高控制权的情况。
2  __new__方法
Python的 __new__方法是在实例化之前调用的,用于控制实例的常见,我们可以通过 __new__方法控制实例的创建,确保只创建一个实例。
2.1 案例(唯一实例)
class SingleTest:
    _instance = None  
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)  
        return cls._instance
    def __init__(self, name):
        self.name = name
single_test1 = SingleTest("有勇气的牛排")
single_test2 = SingleTest("个人博客")
print(single_test1 is single_test2)
print(single_test2.name)

2.2 场景
适合在需要严格控制类的实例化过程的场景中使用。比如创建一些资源密集的对象时。如:
数据库连接
class DBConnectionPool:
    _instance = None
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._initialize_pool(cls._instance)
        return cls._instance
    @staticmethod
    def _initialize_pool(instance):
        
        instance.pool = "连接池初始化"
db1 = DBConnectionPool()
db2 = DBConnectionPool()
print(db1 is db2)  
print(db1.pool)  
3 装饰器
可以使用一个装饰器来将任何类转换为单例。
装饰器通过一个闭包来保存类的唯一实例。
无论调用多少次,只要该类已经被实例化,都会返回第一次创建的实例。
3.1 案例
def singletest(cls):
    instance = {}
    def get_instance(*args, **kwargs):
        if cls not in instance:
            
            instance[cls] = cls(*args, **kwargs)
        return instance[cls]
    return get_instance
@singletest
class SingleTest:
    def __init__(self, name):
        self.name = name
singletest1 = SingleTest("有勇气的牛排")
singletest2 = SingleTest("个人博客")
print(singletest1 is singletest2)
print(singletest1.name)
print(singletest2.name)

3.2 场景
这种方法适合那些希望不修改现有类的情况下,将单例模式添加到类中的场景。例如,你可以为多个类应用相同的单例装饰器,而无需改变它们的定义。这种方式更灵活,可以应用到多种类中。
3.2.1 日志记录
日志记录器在应用程序中始终只有一个实例,但又不希望改变类的定义。
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 Logger:
    def __init__(self, log_file):
        self.log_file = log_file
    def log(self, message):
        print(f"Logging message: {message} to {self.log_file}")
logger1 = Logger("app.log")
logger2 = Logger("error.log")
print(logger1 is logger2)  
logger1.log("输出:有勇气的牛排")
3.3 升级多线程安全守护
线程锁:使用 threading.Lock() 来确保实例创建时的互斥性,避免多线程下同时创建多个实例。
通用性:这个改进后的版本可以安全用于数据库、多线程和日志等场景,尤其是在需要单例的多线程程序中。
场景:可以安全用于数据库、多线程和日志等场景,尤其是在需要单例的多线程程序中。
import threading
def singleton(cls):
    instances = {}
    lock = threading.Lock()
    def get_instance(*args, **kwargs):
        with lock:
            if cls not in instances:
                instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance
4 模块化实现
Python 模块本质上是单例的,因为模块在第一次被导入时会执行其代码,后续的导入会直接引用第一次导入的结果。
4.1 案例
single_test.py
class SingleTest:
    def __init__(self):
        self.name = None
single_test = SingleTest()
main.py
from single_test import single_test
from single_test import single_test as single_test2
single_test.name = "有勇气的牛排"
print(single_test.name)
print(single_test2.name)

4.2 场景
4.2.1 配置管理或全局状态管理
模块级别的单例模式非常适合处理全局状态管理,尤其是当某个模块负责配置管理或者其他全局资源时。这种方式无需显式创建单例对象,模块本身天然是单例的。
在应用程序的各个部分,可能需要访问同一个全局配置文件,模块级单例是非常合适的。
config.py
class AppConfig:
    def __init__(self):
        self.name = "有勇气的牛排"
config = AppConfig()
main.py
from config import config
print(config.name)  
config.name = "个人博客"
from config import config
print(config.name)  
5 元类Metaclass
元类控制类的创建,我们可以通过自定义元类来控制类的实例化过程,实现单例模式。
5.1 案例
原文:https://www.couragesteak.com/article/480
class SingleTestMeta(type):
    _instance = {}  
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instance:
            
            cls._instance[cls] = super().__call__(*args, **kwargs)
        return cls._instance[cls]
class SingleTest(metaclass=SingleTestMeta):
    def __init__(self, name):
        self.name = name
single_test1 = SingleTest("有勇气的牛排")
single_test2 = SingleTest("个人博客")
print(single_test1 is single_test2)  
print(single_test1.name)
print(single_test2.name)

                <h2><a id="1__0"></a>1 前言</h2>
<p><strong>单例模式(Singleton Pattern)</strong> 是一种创建型设计模式,旨在确保一个类在程序运行期间只有一个实例,并且提供一个全局访问点。</p>
<p>单例模式的主要作用是控制实例的数量,防止类被实例化多次,以节省资源并确保全局状态的唯一性。</p>
<h3><a id="11__6"></a>1.1 场景</h3>
<ul>
<li>需要控制资源访问(数据库连接、文件句柄等)</li>
<li>想要确保全局状态唯一性(日志记录、配置文件管理等)</li>
</ul>
<h3><a id="12__11"></a>1.2 单利模式关键点</h3>
<ol>
<li>在程序的整个生命周期中只会创建一次对象实例</li>
<li>必须提供一个全局访问点,以便其他代码可以访问唯一实例</li>
</ol>
<h3><a id="13__18"></a>1.3 常见方案有点对比</h3>
<ul>
<li><code>__new__</code> 方法适用于较为简单的单例实现。</li>
<li>装饰器方式使得代码更加优雅且易于复用。</li>
<li>模块方式利用了 Python 模块的特性,非常简洁。</li>
<li>元类方式适合需要更高控制权的情况。</li>
</ul>
<h2><a id="2____new___25"></a>2 <code> __new__</code>方法</h2>
<p>Python的 <code>__new__</code>方法是在实例化之前调用的,用于控制实例的常见,我们可以通过 <code>__new__</code>方法控制实例的创建,确保<strong>只创建一个实例</strong>。</p>
<h3><a id="21__29"></a>2.1 案例(唯一实例)</h3>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">SingleTest</span>:
    _instance = <span class="hljs-literal">None</span>  <span class="hljs-comment"># 类属性,确保唯一的实例</span>
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__new__</span>(<span class="hljs-params">cls, *args, **kwargs</span>):
        <span class="hljs-keyword">if</span> cls._instance <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            cls._instance = <span class="hljs-built_in">super</span>().__new__(cls)  <span class="hljs-comment"># 调用父类__new__方法创建实例</span>
        <span class="hljs-keyword">return</span> cls._instance
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, name</span>):
        self.name = name
<span class="hljs-comment"># 实例化</span>
single_test1 = SingleTest(<span class="hljs-string">"有勇气的牛排"</span>)
single_test2 = SingleTest(<span class="hljs-string">"个人博客"</span>)
<span class="hljs-built_in">print</span>(single_test1 <span class="hljs-keyword">is</span> single_test2)
<span class="hljs-comment"># 输出:True,说明2个实例为同一个</span>
<span class="hljs-built_in">print</span>(single_test2.name)
<span class="hljs-comment"># 输出:个人博客,说明第二次初始化覆盖了第一次</span>
</code></div></pre>
<p><img src="https://www.couragesteak.com/tcos/article/eec512a5a1133693f7018e5cdd171765.png" alt="Python使用__new__实现单例模式" /></p>
<h3><a id="22__55"></a>2.2 场景</h3>
<p>适合在需要严格控制类的实例化过程的场景中使用。比如创建一些资源密集的对象时。如:</p>
<ul>
<li>数据库连接</li>
<li>缓存管理</li>
<li>线程池等</li>
</ul>
<h4><a id="_63"></a>数据库连接</h4>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">DBConnectionPool</span>:
    _instance = <span class="hljs-literal">None</span>
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__new__</span>(<span class="hljs-params">cls, *args, **kwargs</span>):
        <span class="hljs-keyword">if</span> cls._instance <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            cls._instance = <span class="hljs-built_in">super</span>().__new__(cls)
            cls._initialize_pool(cls._instance)
        <span class="hljs-keyword">return</span> cls._instance
<span class="hljs-meta">    @staticmethod</span>
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">_initialize_pool</span>(<span class="hljs-params">instance</span>):
        <span class="hljs-comment"># 假设初始化数据库连接池</span>
        instance.pool = <span class="hljs-string">"连接池初始化"</span>
<span class="hljs-comment"># 测试</span>
db1 = DBConnectionPool()
db2 = DBConnectionPool()
<span class="hljs-built_in">print</span>(db1 <span class="hljs-keyword">is</span> db2)  <span class="hljs-comment"># True</span>
<span class="hljs-built_in">print</span>(db1.pool)  <span class="hljs-comment"># "连接池初始化"</span>
</code></div></pre>
<h2><a id="3__88"></a>3 装饰器</h2>
<p>可以使用一个装饰器来将任何类转换为单例。</p>
<p>装饰器通过一个闭包来保存类的唯一实例。</p>
<p>无论调用多少次,只要该类已经被实例化,都会返回<strong>第一次创建的实例</strong>。</p>
<h3><a id="31__96"></a>3.1 案例</h3>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">singletest</span>(<span class="hljs-params">cls</span>):
    instance = {}
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_instance</span>(<span class="hljs-params">*args, **kwargs</span>):
        <span class="hljs-keyword">if</span> cls <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> instance:
            <span class="hljs-comment"># 首次调用 创建实例</span>
            instance[cls] = cls(*args, **kwargs)
        <span class="hljs-keyword">return</span> instance[cls]
    <span class="hljs-keyword">return</span> get_instance
<span class="hljs-meta">@singletest</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">SingleTest</span>:
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, name</span>):
        self.name = name
singletest1 = SingleTest(<span class="hljs-string">"有勇气的牛排"</span>)
singletest2 = SingleTest(<span class="hljs-string">"个人博客"</span>)
<span class="hljs-built_in">print</span>(singletest1 <span class="hljs-keyword">is</span> singletest2)
<span class="hljs-comment"># 输出True:说明2个为同一个实例</span>
<span class="hljs-built_in">print</span>(singletest1.name)
<span class="hljs-built_in">print</span>(singletest2.name)
<span class="hljs-comment"># 输出:有勇气的牛排,说明第一次实例化的值不会被第二次改变</span>
</code></div></pre>
<p><img src="https://www.couragesteak.com/tcos/article/8c6c97c3831c9dceaa1972ecd19a5ad4.png" alt="image.png" /></p>
<h3><a id="32__130"></a>3.2 场景</h3>
<p>这种方法适合那些希望不修改现有类的情况下,将单例模式添加到类中的场景。例如,你可以为多个类应用相同的单例装饰器,而无需改变它们的定义。这种方式更灵活,可以应用到多种类中。</p>
<h4><a id="321__134"></a>3.2.1 日志记录</h4>
<p>日志记录器在应用程序中始终只有一个实例,但又不希望改变类的定义。</p>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">singleton</span>(<span class="hljs-params">cls</span>):
    instances = {}
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_instance</span>(<span class="hljs-params">*args, **kwargs</span>):
        <span class="hljs-keyword">if</span> cls <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> instances:
            instances[cls] = cls(*args, **kwargs)
        <span class="hljs-keyword">return</span> instances[cls]
    <span class="hljs-keyword">return</span> get_instance
<span class="hljs-meta">@singleton</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Logger</span>:
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, log_file</span>):
        self.log_file = log_file
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">log</span>(<span class="hljs-params">self, message</span>):
        <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Logging message: <span class="hljs-subst">{message}</span> to <span class="hljs-subst">{self.log_file}</span>"</span>)
<span class="hljs-comment"># 测试</span>
logger1 = Logger(<span class="hljs-string">"app.log"</span>)
logger2 = Logger(<span class="hljs-string">"error.log"</span>)
<span class="hljs-built_in">print</span>(logger1 <span class="hljs-keyword">is</span> logger2)  <span class="hljs-comment"># True</span>
logger1.log(<span class="hljs-string">"输出:有勇气的牛排"</span>)
</code></div></pre>
<h3><a id="33__167"></a>3.3 升级多线程安全守护</h3>
<p>线程锁:使用 threading.Lock() 来确保实例创建时的互斥性,避免多线程下同时创建多个实例。<br />
通用性:这个改进后的版本可以安全用于数据库、多线程和日志等场景,尤其是在需要单例的多线程程序中。</p>
<p>场景:可以安全用于数据库、多线程和日志等场景,尤其是在需要单例的多线程程序中。</p>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">import</span> threading
<span class="hljs-keyword">def</span> <span class="hljs-title function_">singleton</span>(<span class="hljs-params">cls</span>):
    instances = {}
    lock = threading.Lock()
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">get_instance</span>(<span class="hljs-params">*args, **kwargs</span>):
        <span class="hljs-keyword">with</span> lock:
            <span class="hljs-keyword">if</span> cls <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> instances:
                instances[cls] = cls(*args, **kwargs)
        <span class="hljs-keyword">return</span> instances[cls]
    <span class="hljs-keyword">return</span> get_instance
</code></div></pre>
<h2><a id="4__192"></a>4 模块化实现</h2>
<p>Python 模块本质上是单例的,因为模块在第一次被导入时会执行其代码,后续的导入会直接引用第一次导入的结果。</p>
<h3><a id="41__196"></a>4.1 案例</h3>
<p><code>single_test.py</code></p>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">SingleTest</span>:
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):
        self.name = <span class="hljs-literal">None</span>
single_test = SingleTest()
</code></div></pre>
<p><code>main.py</code></p>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">from</span> single_test <span class="hljs-keyword">import</span> single_test
<span class="hljs-keyword">from</span> single_test <span class="hljs-keyword">import</span> single_test <span class="hljs-keyword">as</span> single_test2
single_test.name = <span class="hljs-string">"有勇气的牛排"</span>
<span class="hljs-built_in">print</span>(single_test.name)
<span class="hljs-built_in">print</span>(single_test2.name)
</code></div></pre>
<p><img src="https://www.couragesteak.com/tcos/article/e22e8cf3e4316ee4ae7c2d1cec089cee.png" alt="image.png" /></p>
<h3><a id="42__223"></a>4.2 场景</h3>
<h4><a id="421__225"></a>4.2.1 配置管理或全局状态管理</h4>
<p>模块级别的单例模式非常适合处理全局状态管理,尤其是当某个模块负责配置管理或者其他全局资源时。这种方式无需显式创建单例对象,模块本身天然是单例的。</p>
<p>在应用程序的各个部分,可能需要访问同一个全局配置文件,模块级单例是非常合适的。</p>
<p>config.py</p>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">AppConfig</span>:
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):
        self.name = <span class="hljs-string">"有勇气的牛排"</span>
config = AppConfig()
</code></div></pre>
<p>main.py</p>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">from</span> config <span class="hljs-keyword">import</span> config
<span class="hljs-built_in">print</span>(config.name)  <span class="hljs-comment"># 输出:有勇气的牛排</span>
config.name = <span class="hljs-string">"个人博客"</span>
<span class="hljs-comment"># 其他模块中继续使用同一配置</span>
<span class="hljs-keyword">from</span> config <span class="hljs-keyword">import</span> config
<span class="hljs-built_in">print</span>(config.name)  <span class="hljs-comment"># 输出:个人博客</span>
</code></div></pre>
<h2><a id="5_Metaclass_254"></a>5 元类Metaclass</h2>
<p>元类控制类的创建,我们可以通过自定义元类来控制类的实例化过程,实现单例模式。</p>
<h3><a id="51__258"></a>5.1 案例</h3>
<p>原文:<a href="https://www.couragesteak.com/article/480" target="_blank">https://www.couragesteak.com/article/480</a></p>
<pre><div class="hljs"><code class="lang-python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">SingleTestMeta</span>(<span class="hljs-title class_ inherited__">type</span>):
    _instance = {}  <span class="hljs-comment"># 存放单例实例</span>
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__call__</span>(<span class="hljs-params">cls, *args, **kwargs</span>):
        <span class="hljs-keyword">if</span> cls <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> cls._instance:
            <span class="hljs-comment"># 调用 父类 __call__ 创建实例</span>
            cls._instance[cls] = <span class="hljs-built_in">super</span>().__call__(*args, **kwargs)
        <span class="hljs-keyword">return</span> cls._instance[cls]
<span class="hljs-keyword">class</span> <span class="hljs-title class_">SingleTest</span>(metaclass=SingleTestMeta):
    <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, name</span>):
        self.name = name
single_test1 = SingleTest(<span class="hljs-string">"有勇气的牛排"</span>)
single_test2 = SingleTest(<span class="hljs-string">"个人博客"</span>)
<span class="hljs-built_in">print</span>(single_test1 <span class="hljs-keyword">is</span> single_test2)  <span class="hljs-comment"># True: 为同一个实例</span>
<span class="hljs-built_in">print</span>(single_test1.name)
<span class="hljs-built_in">print</span>(single_test2.name)
</code></div></pre>
<p><img src="https://www.couragesteak.com/tcos/article/07df7e2e64d1f1026345cbb7cacee998.png" alt="image.png" /></p>
            
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
评论区