我找到了一种优雅的方法来装饰Python类,使其成为singleton
。该类只能生成一个对象。每个Instance()
调用都返回相同的对象:
class Singleton:
"""
A non-thread-safe helper class to ease implementing singletons.
This should be used as a decorator -- not a metaclass -- to the
class that should be a singleton.
The decorated class can define one `__init__` function that
takes only the `self` argument. Also, the decorated class cannot be
inherited from. Other than that, there are no restrictions that apply
to the decorated class.
To get the singleton instance, use the `Instance` method. Trying
to use `__call__` will result in a `TypeError` being raised.
"""
def __init__(self, decorated):
self._decorated = decorated
def Instance(self):
"""
Returns the singleton instance. Upon its first call, it creates a
new instance of the decorated class and calls its `__init__` method.
On all subsequent calls, the already created instance is returned.
"""
try:
return self._instance
except AttributeError:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError('Singletons must be accessed through `Instance()`.')
def __instancecheck__(self, inst):
return isinstance(inst, self._decorated)
我在这里找到了代码: Is there a simple, elegant way to define singletons?
顶部的评论说:
[这是]一个非线程安全的助手类,可以轻松实现单例。
不幸的是,我没有足够的多线程经验来自己看到'线程不安全'。
我在多线程Python应用程序中使用此@Singleton
装饰器。我担心潜在的稳定性问题。因此:
有没有办法让这段代码完全是线程安全的?
如果上一个问题没有解决方案(或者解决方案过于繁琐),我应采取哪些预防措施以保证安全?
@Aran-Fey指出装饰器编码错误。当然,非常感谢任何改进。
特此提供我当前的系统设置:
> Python 3.6.3
> Windows 10,64位
子>
答案 0 :(得分:7)
我建议你选择一个更好的单例实现。 metaclass-based implementation是最常用的。
至于线程安全,你的方法或上面链接中建议的任何方法都不是线程安全的:线程总是可以读取没有现有实例并开始创建一个,但是{{3在存储第一个实例之前。
您可以使用another thread does the same来保护基于元类的单例类的__call__
方法。
import functools
import threading
lock = threading.Lock()
def synchronized(lock):
""" Synchronization decorator """
def wrapper(f):
@functools.wraps(f)
def inner_wrapper(*args, **kw):
with lock:
return f(*args, **kw)
return inner_wrapper
return wrapper
class Singleton(type):
_instances = {}
@synchronized(lock)
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 SingletonClass(metaclass=Singleton):
pass
答案 1 :(得分:0)
如果您担心性能,可以使用check-lock-check pattern来最大程度地减少锁定获取,从而改善已接受答案的解决方案:
class SingletonOptmized(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._locked_call(*args, **kwargs)
return cls._instances[cls]
@synchronized(lock)
def _locked_call(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonOptmized, cls).__call__(*args, **kwargs)
class SingletonClassOptmized(metaclass=SingletonOptmized):
pass
区别在于:
In [9]: %timeit SingletonClass()
488 ns ± 4.67 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [10]: %timeit SingletonClassOptmized()
204 ns ± 4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
答案 2 :(得分:0)
我发布此信息只是为了简化@OlivierMelançon和@ se7entyse7en建议的解决方案:import functools
和包装不会产生开销。
import threading
lock = threading.Lock()
class SingletonOptmizedOptmized(type):
_instances = {}
def call(cls, *args, **kwargs):
if cls not in cls._instances:
with lock:
if cls not in cls._instances:
cls._instances[cls] = super(SingletonOptmizedOptmized, cls).call(*args, **kwargs)
return cls._instances[cls]
class SingletonClassOptmizedOptmized(metaclass=SingletonOptmizedOptmized):
pass
差异:
>>> timeit('SingletonClass()', globals=globals(), number=1000000) 0.4635776 >>> timeit('SingletonClassOptmizedOptmized()', globals=globals(), number=1000000) 0.192263300000036