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://static.couragesteak.com/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://static.couragesteak.com/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://static.couragesteak.com/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://static.couragesteak.com/article/07df7e2e64d1f1026345cbb7cacee998.png" alt="image.png" /></p>
留言