这个问题不是为了讨论 singleton design pattern 是否可取、是否是反模式或任何宗教战争,而是讨论如何在 Python 中以如下方式最好地实现该模式:最蟒蛇。在这种情况下,我将“最 Pythonic”定义为它遵循“最小惊讶原则”。
我有多个将成为单例的类(我的用例是记录器,但这并不重要)。当我可以简单地继承或装饰时,我不希望用添加的口香糖来弄乱几个类。
最佳方法:
方法一:装饰器
def singleton(class_):
instances = {}
def getinstance(*args, **kwargs):
if class_ not in instances:
instances[class_] = class_(*args, **kwargs)
return instances[class_]
return getinstance
@singleton
class MyClass(BaseClass):
pass
优点
装饰器以一种比多重继承更直观的方式添加。
缺点
虽然使用 MyClass() 创建的对象是真正的单例对象,但 MyClass 本身是一个函数,而不是类,因此您不能从中调用类方法。也适用于 x = MyClass(); y = MyClass(); t = 类型(n)();
然后 x == y
但 x != t && y != t
方法二:基类
class Singleton(object):
_instance = None
def __new__(class_, *args, **kwargs):
if not isinstance(class_._instance, class_):
class_._instance = object.__new__(class_, *args, **kwargs)
return class_._instance
class MyClass(Singleton, BaseClass):
pass
优点
这是一堂真正的课
缺点
多重继承——哎呀! __new__ 可以在从第二个基类继承期间被覆盖?一个人必须考虑比必要的更多。
方法 3:元类
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]
#Python2
class MyClass(BaseClass):
__metaclass__ = Singleton
#Python3
class MyClass(BaseClass, metaclass=Singleton):
pass
优点
这是一堂真正的课
自动神奇地覆盖继承
将 __metaclass__ 用于正确目的(并让我意识到)
缺点
有吗?
方法四:装饰器返回同名类
def singleton(class_):
class class_w(class_):
_instance = None
def __new__(class_, *args, **kwargs):
if class_w._instance is None:
class_w._instance = super(class_w,
class_).__new__(class_,
*args,
**kwargs)
class_w._instance._sealed = False
return class_w._instance
def __init__(self, *args, **kwargs):
if self._sealed:
return
super(class_w, self).__init__(*args, **kwargs)
self._sealed = True
class_w.__name__ = class_.__name__
return class_w
@singleton
class MyClass(BaseClass):
pass
优点
这是一堂真正的课
自动神奇地覆盖继承
缺点
创建每个新类没有开销吗?在这里,我们为每个希望创建单例的类创建两个类。虽然在我的情况下这很好,但我担心这可能无法扩展。当然,关于扩展这种模式是否太容易存在争议……
_sealed 属性的意义何在
不能使用 super() 在基类上调用同名方法,因为它们会递归。这意味着您不能自定义 __new__ 并且不能子类化需要您调用 __init__ 的类。
方法5:一个模块
模块文件 singleton.py
优点
简单胜于复杂
缺点
没有延迟实例化
foo.x
或者如果您坚持使用 Foo.x
而不是 Foo().x
);使用类属性和静态/类方法 (Foo.x
)。
使用元类
我会推荐方法#2,但你最好使用元类而不是基类。这是一个示例实现:
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 Logger(object):
__metaclass__ = Singleton
或者在 Python3 中
class Logger(metaclass=Singleton):
pass
如果您想在每次调用类时运行 __init__
,请添加
else:
cls._instances[cls].__init__(*args, **kwargs)
Singleton.__call__
中的 if
语句。
关于元类的几句话。元类是类的类;也就是说,类是其元类的实例。您可以使用 type(obj)
在 Python 中找到对象的元类。普通的新式类是 type
类型。上面代码中的 Logger
将是 class 'your_module.Singleton'
类型,就像 Logger
的(唯一)实例将是 class 'your_module.Logger'
类型一样。当你用 Logger()
调用 logger 时,Python 首先询问 Logger
、Singleton
的元类,要做什么,允许实例创建被抢占。此过程与当您通过执行 myclass.attribute
引用某个类的属性时,Python 通过调用 __getattr__
询问该类做什么是相同的。
元类本质上决定了类的定义意味着什么以及如何实现该定义。参见示例 http://code.activestate.com/recipes/498149/,它实质上是使用元类在 Python 中重新创建 C 风格的 struct
。线程 What are some (concrete) use-cases for metaclasses? 还提供了一些示例,它们通常似乎与声明式编程有关,尤其是在 ORM 中使用时。
在这种情况下,如果您使用 方法 #2,并且子类定义了一个 __new__
方法,那么它将在您每次调用 SubClassOfSingleton()
时执行 - 因为它负责调用返回存储实例的方法。使用元类,它只会被调用一次,当唯一的实例被创建时。您想自定义调用类的含义,这由它的类型决定。
一般来说,使用元类来实现单例是有意义的。单例是特殊的,因为它只创建一次,而元类是您自定义创建类的方式。如果您需要以其他方式自定义单例类定义,则使用元类可为您提供更多控制。
您的单例不需要多重继承(因为元类不是基类),但对于使用多重继承的已创建类的子类,您需要确保单例类是第一个/最左边的类,其元类重新定义了 __call__
这不太可能成为问题。实例字典不在实例的命名空间中,因此它不会意外覆盖它。
您还会听说单例模式违反了“单一职责原则”——每个类应该只做一件事。这样一来,如果您需要更改另一件事,您就不必担心会弄乱代码所做的一件事,因为它们是独立的且被封装的。元类实现通过了这个测试。元类负责执行模式,创建的类和子类不需要知道它们是单例。方法 #1 未能通过此测试,正如您在“MyClass 本身是一个函数,而不是类,因此您不能从中调用类方法”中所指出的那样。
Python 2 和 3 兼容版本
编写适用于 Python2 和 3 的东西需要使用稍微复杂的方案。由于元类通常是 type
类型的子类,因此可以使用一个在运行时动态创建一个中间基类,并将其作为元类,然后使用 that 作为公共 {2 } 基类。解释起来比做起来难,如下图所示:
# works in Python 2 & 3
class _Singleton(type):
""" A metaclass that creates a Singleton base class when called. """
_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 Singleton(_Singleton('SingletonMeta', (object,), {})): pass
class Logger(Singleton):
pass
这种方法具有讽刺意味的是,它使用子类化来实现元类。一个可能的优点是,与纯元类不同,isinstance(inst, Singleton)
将返回 True
。
更正
在另一个主题上,您可能已经注意到这一点,但是您原始帖子中的基类实现是错误的。 _instances
需要在类上引用,您需要使用 super()
或者您正在递归,而 __new__
实际上是您拥有的静态方法将类传递给,而不是类方法,因为实际类在调用时尚未创建。所有这些事情对于元类实现也是如此。
class Singleton(object):
_instances = {}
def __new__(class_, *args, **kwargs):
if class_ not in class_._instances:
class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
return class_._instances[class_]
class MyClass(Singleton):
pass
c = MyClass()
装饰器返回一个类
我本来是写评论的,但是太长了,所以我会在这里添加。方法 #4 比其他装饰器版本更好,但它的代码比单例所需的要多,而且不清楚它的作用。
主要问题源于该类是它自己的基类。首先,让一个类成为一个几乎相同的类的子类,该类具有相同的名称,并且只存在于它的 __class__
属性中,这不是很奇怪吗?这也意味着您不能使用 super()
定义任何在其基类上调用同名方法的方法,因为它们会递归。这意味着您的类不能自定义 __new__
,也不能派生自任何需要调用 __init__
的类。
何时使用单例模式
您的用例是想要使用单例的更好示例之一。您在其中一条评论中说“对我来说,日志记录似乎一直是单身人士的自然候选人。”你是绝对正确的。
当人们说单例不好时,最常见的原因是它们是隐式共享状态。虽然全局变量和顶级模块导入是显式共享状态,但传递的其他对象通常是实例化的。这是一个很好的观点,但有两个例外。
第一个,也是在不同地方提到的一个,是当单例是常量时。全局常量的使用,尤其是枚举,被广泛接受,并且被认为是理智的,因为无论如何,没有一个用户可以为任何其他用户搞砸它们。这同样适用于常量单例。
第二个例外,很少提及,是相反的——当单例只是一个数据接收器,而不是一个数据源(直接或间接)。这就是为什么记录器感觉像是单例的“自然”用途。由于各种用户没有以其他用户关心的方式更改记录器,因此没有真正的共享状态。这否定了反对单例模式的主要论点,并使它们成为合理的选择,因为它们易于用于任务。
以下是来自 http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html 的引述:
现在,有一种单例是可以的。这是一个单例,其中所有可访问的对象都是不可变的。如果所有对象都是不可变的,那么 Singleton 就没有全局状态,因为一切都是不变的。但是很容易把这种单例变成可变单例,很滑坡。因此,我也反对这些 Singletons,不是因为它们不好,而是因为它们很容易变坏。 (作为旁注,Java 枚举就是这类单例。只要您不将状态放入枚举中就可以了,所以请不要。)另一种半可接受的单例是那些这不会影响您的代码的执行,它们没有“副作用”。日志记录就是一个很好的例子。它加载了单例和全局状态。这是可以接受的(因为它不会伤害您),因为无论是否启用给定的记录器,您的应用程序的行为都没有任何不同。这里的信息流向一种方式:从您的应用程序到记录器。即使认为记录器是全局状态,因为没有信息从记录器流入您的应用程序,记录器也是可以接受的。如果您希望您的测试断言正在记录某些内容,您仍然应该注入您的记录器,但一般来说,尽管记录器充满了状态,但它并没有害处。
class Foo(object):
pass
some_global_variable = Foo()
模块只导入一次,其他一切都想多了。不要使用单例并且尽量不要使用全局变量。
s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
is
运算符测试指针是否相等。如果 pickle.loads
返回对预先存在的对象的引用而不是对新创建的对象的引用,我会感到相当惊讶——甚至称其为错误。因此,测试 s is s1
是否不能告诉您任何关于将模块用作单例的适用性。
True
、False
和 None
进行实习的副作用,与 pickle.loads
后面的代码无关。此外,只对只读对象进行操作是安全的。如果 pickle.loads
返回对已经存在的 modifiable 对象(例如模块)的引用,那将是一个错误。 (所以我坚持我的暗示,即 dividebyzero 的代码示例并不能证明任何事情。)
import ABC as X
将模块导入了几个不同的脚本文件(所有这些文件都将使用“singleton”),并且实际上对于每个具有“singleton”模块的 import
语句的脚本都导入了一次。因此,每次导入模块时,我定义的变量都会重新分配其值。我还尝试删除别名,甚至将其导入项目的每个文件中,结果相同。我在 Python 3.9.6 上。带有元类的单例(来自 agf 的回答)对于我的用例(日志记录,必须在编译时初始化一次的变量)完成了绝妙的技巧
使用一个模块。它只导入一次。在其中定义一些全局变量——它们将是单例的“属性”。添加一些功能 - 单例的“方法”。
import * from base_module
...重新考虑 OOP 我的朋友!哈哈哈
你可能永远不需要 Python 中的单例。只需在模块中定义所有数据和函数,您就拥有了一个事实上的单例:
import datetime
file_name=None
def set_file_name(new_file_name: str):
global file_name
file_name=new_file_name
def write(message: str):
global file_name
if file_name:
with open(file_name, 'a+') as f:
f.write("{} {}\n".format(datetime.datetime.now(), message))
else:
print("LOG: {}", message)
要使用:
import log
log.set_file_name("debug.log")
log.write("System starting")
...
如果你真的必须有一个单例课程,那么我会选择:
class MySingleton(object):
def foo(self):
pass
my_singleton = MySingleton()
要使用:
from mysingleton import my_singleton
my_singleton.foo()
其中 mysingleton.py
是定义 MySingleton
的文件名。这是因为第一次导入文件后,Python 不会重新执行代码。
这是给你的单线:
singleton = lambda c: c()
以下是你如何使用它:
@singleton
class wat(object):
def __init__(self): self.x = 1
def get_x(self): return self.x
assert wat.get_x() == 1
您的对象被急切地实例化。这可能是也可能不是您想要的。
wat2 = type(wat)()
,但这是 python,我们都是同意的成年人等等。你不能保证只有一个实例,但你可以保证如果人们再做第二个,它看起来会很丑,而且——如果他们是正派、正直的人——就像一个警告给他们签名。我错过了什么?
查看 Stack Overflow 问题 Is there a simple, elegant way to define singletons in Python? 以及几个解决方案。
我强烈建议观看 Alex Martelli 关于 Python 设计模式的演讲:part 1 和 part 2。特别是,在第 1 部分中,他谈到了单例/共享状态对象。
如果想拥有多个同一个类的实例,但前提是args或kwargs不同,可以使用第三方python包Handy Decorators(包装饰器)。
前任。如果您有一个处理串行通信的类,并且要创建一个您想将串行端口作为参数发送的实例,那么使用传统方法将不起作用使用上述装饰器,如果参数是不同的。对于相同的参数,装饰器将返回已创建的相同实例。
如果您有一个处理串行通信的类,并且要创建一个您想将串行端口作为参数发送的实例,那么使用传统方法将不起作用
使用上面提到的装饰器,如果参数不同,可以创建类的多个实例。
对于相同的参数,装饰器将返回已创建的相同实例。
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
... def __init__(self, *args, **kwargs):
... pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b # has to be different
False
>>> b is c # has to be same
True
>>>
pip install handy-decorators
,得到了 ERROR: Could not find a version that satisfies the requirement handy-decorators
。有什么建议吗?
@singleton
实现的 previous_instances
字典看起来不是线程安全的。如果一个线程正在构造一个对象,而另一个对象检查字典,则存在竞争条件......
使用函数属性也很简单
def f():
if not hasattr(f, 'value'):
setattr(f, 'value', singletonvalue)
return f.value
这是我自己的单例实现。您所要做的就是装饰班级;要获得单例,您必须使用 Instance
方法。这是一个例子:
@Singleton
class Foo:
def __init__(self):
print 'Foo created'
f = Foo() # Error, this isn't how you get the instance of a singleton
f = Foo.Instance() # Good. Being explicit is in line with the Python Zen
g = Foo.Instance() # Returns already created instance
print f is g # True
这是代码:
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. 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.
Limitations: The decorated class cannot be inherited from.
"""
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)
SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())
应该以真正的单例打印 True
; with your code prints False
我更喜欢这个解决方案,我发现它非常清晰明了。例如,如果其他线程已经创建了它,它正在使用双重检查。另外要考虑的是确保反序列化不会创建任何其他实例。 https://gist.github.com/werediver/4396488
import threading
# Based on tornado.ioloop.IOLoop.instance() approach.
# See https://github.com/facebook/tornado
class SingletonMixin(object):
__singleton_lock = threading.Lock()
__singleton_instance = None
@classmethod
def instance(cls):
if not cls.__singleton_instance:
with cls.__singleton_lock:
if not cls.__singleton_instance:
cls.__singleton_instance = cls()
return cls.__singleton_instance
if __name__ == '__main__':
class A(SingletonMixin):
pass
class B(SingletonMixin):
pass
a, a2 = A.instance(), A.instance()
b, b2 = B.instance(), B.instance()
assert a is a2
assert b is b2
assert a is not b
print('a: %s\na2: %s' % (a, a2))
print('b: %s\nb2: %s' % (b, b2))
__singleton_instance
?你能不能总是拿__singleton_lock
然后只检查一次?
from functools import cache
@cache
class xxx:
....
死容易和工作!
方法 3 看起来很简洁,但是如果你想让你的程序同时在 Python 2 和 Python 3 中运行,它就行不通了。即使使用 Python 版本的测试来保护单独的变体也会失败,因为 Python 3 版本在 Python 2 中会出现语法错误。
感谢 Mike Watkins:http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/。如果您希望程序同时在 Python 2 和 Python 3 中运行,则需要执行以下操作:
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]
MC = Singleton('MC', (object), {})
class MyClass(MC):
pass # Code for the class implementation
我认为分配中的“对象”需要替换为“BaseClass”,但我没有尝试过(我已经尝试过如图所示的代码)。
class MyClass(metaclass=Singleton)
我会把我的扔进戒指。这是一个简单的装饰器。
from abc import ABC
def singleton(real_cls):
class SingletonFactory(ABC):
instance = None
def __new__(cls, *args, **kwargs):
if not cls.instance:
cls.instance = real_cls(*args, **kwargs)
return cls.instance
SingletonFactory.register(real_cls)
return SingletonFactory
# Usage
@singleton
class YourClass:
... # Your normal implementation, no special requirements.
我认为它优于其他一些解决方案的好处:
它清晰简洁(在我看来;D)。
它的作用是完全封装的。您不需要更改有关 YourClass 的实现的任何事情。这包括不需要为您的类使用元类(请注意,上面的元类在工厂中,而不是“真正的”类)。
它不依赖于猴子修补任何东西。
它对调用者是透明的:调用者仍然只是简单地导入 YourClass,它看起来像一个类(因为它是),并且他们正常使用它。无需使调用者适应工厂功能。 YourClass() 实例化的仍然是您实现的 YourClass 的真实实例,而不是任何类型的代理,因此不会产生副作用。 isinstance(instance, YourClass) 和类似的操作仍然可以按预期工作(尽管这个位确实需要 abc 所以排除了 Python <2.6)。
调用者仍然只是简单地导入YourClass,它看起来像一个类(因为它是),并且他们正常使用它。无需使调用者适应工厂功能。
YourClass() 实例化的仍然是您实现的 YourClass 的真实实例,而不是任何类型的代理,因此不会产生副作用。
isinstance(instance, YourClass) 和类似的操作仍然可以按预期工作(尽管这个位确实需要 abc 所以排除了 Python <2.6)。
我确实想到了一个缺点:真实类的类方法和静态方法不能通过隐藏它的工厂类透明地调用。我很少使用它,以至于我从未碰巧遇到过这种需求,但是通过在实现 __getattr__()
的工厂上使用自定义元类来将所有 ish 属性访问委托给真实类,它可以很容易地纠正。
我实际上发现更有用的一个相关模式(并不是说我说这些东西是经常需要的)是一种“独特”模式,其中用相同的参数实例化类会导致返回相同的实例。即“每个参数的单例”。以上很好地适应了这一点,变得更加简洁:
def unique(real_cls):
class UniqueFactory(ABC):
@functools.lru_cache(None) # Handy for 3.2+, but use any memoization decorator you like
def __new__(cls, *args, **kwargs):
return real_cls(*args, **kwargs)
UniqueFactory.register(real_cls)
return UniqueFactory
说了这么多,我确实同意一般建议,如果你认为你需要这些东西之一,你真的应该停下来问问自己是否真的需要。 99% 的时间,YAGNI。
我将推荐一个使用元类的优雅解决方案
class Singleton(type):
# Inherit from "type" in order to gain access to method __call__
def __init__(self, *args, **kwargs):
self.__instance = None # Create a variable to store the object reference
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
# if the object has not already been created
self.__instance = super().__call__(*args, **kwargs) # Call the __init__ method of the subclass (Spam) and save the reference
return self.__instance
else:
# if object (Spam) reference already exists; return it
return self.__instance
class Spam(metaclass=Singleton):
def __init__(self, x):
print('Creating Spam')
self.x = x
if __name__ == '__main__':
spam = Spam(100)
spam2 = Spam(200)
输出:
Creating Spam
从输出中可以看出,只实例化了一个对象
使用类变量(无装饰器)
通过覆盖 __new__
方法以返回该类的相同实例。仅第一次初始化类的布尔值:
class SingletonClass:
_instance = None
def __new__(cls, *args, **kwargs):
# If no instance of class already exits
if cls._instance is None:
cls._instance = object.__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, *args, **kwargs):
if self._initialized:
return
self.attr1 = args[0]
# set the attribute to `True` to not initialize again
self._initialized = True
好吧,除了同意 Pythonic 关于模块级全局的一般建议之外,这个怎么样:
def singleton(class_):
class class_w(class_):
_instance = None
def __new__(class2, *args, **kwargs):
if class_w._instance is None:
class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs)
class_w._instance._sealed = False
return class_w._instance
def __init__(self, *args, **kwargs):
if self._sealed:
return
super(class_w, self).__init__(*args, **kwargs)
self._sealed = True
class_w.__name__ = class_.__name__
return class_w
@singleton
class MyClass(object):
def __init__(self, text):
print text
@classmethod
def name(class_):
print class_.__name__
x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)
输出是:
111 # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True # this is actually the same instance
_sealed
属性的意义何在?据我所知,这没有任何作用?对此我感到很烦,说它不应该表现良好……我将在本周晚些时候进行一些比较测试。
__init__
中包含要在每次初始化时调用的内容。只是一个简单的“在 class.method 中初始化”。至于缩进——你使用了制表符和空格——我修复了大部分,但如果你想得到它似乎错过了一个(只需检查编辑日志)
这个怎么样:
def singleton(cls):
instance=cls()
cls.__new__ = cls.__call__= lambda cls: instance
cls.__init__ = lambda self: None
return instance
将它用作应该是单例的类的装饰器。像这样:
@singleton
class MySingleton:
#....
这类似于另一个答案中的 singleton = lambda c: c()
装饰器。与其他解决方案一样,唯一的实例具有类的名称 (MySingleton
)。但是,使用此解决方案,您仍然可以通过执行 MySingleton()
从类中“创建”实例(实际上是获取唯一的实例)。它还可以防止您通过执行 type(MySingleton)()
来创建其他实例(这也返回相同的实例)。
type(MySingleton)()
时,都会调用 MySingleton.__init__()
并多次初始化对象;您可以在 singleton
中写入 cls.__init__ = lambda self: pass
来修复它。此外,覆盖 cls.__call__
似乎毫无意义,甚至有害 - 在此上下文中定义的 __call__
在您调用 MySingleton(any, list, of, arguments)
时使用,而不是在您调用 type(MySingleton)(any, list, of, arguments)
时使用。
type(MySingleton)()
时会再次调用 __init__()
。您提出的解决方案(添加 cls.__init__ = lambda self: pass
)给出了语法错误,因为 lambda 表达式的最后一部分需要是表达式,而不是语句。但是,添加 cls.__init__ = lambda self: None
有效,因此我将其添加到我的答案中。
__call__
的使用。我的意图是让 type(MySingleton)()
和 MySingleton()
返回实例。所以它正在做我想做的事。您可以将 MySingleton 视为单例的类型或单例的实例(或两者兼而有之)。
这个答案可能不是您想要的。我想要一个单例,因为只有那个对象有它的身份,以便进行比较。在我的例子中,它被用作 Sentinel Value。答案很简单,让任何对象 mything = object()
并且根据 python 的性质,只有那个东西才会有它的身份。
#!python
MyNone = object() # The singleton
for item in my_list:
if item is MyNone: # An Example identity comparison
raise StopIteration
eval
或 importlib.reload
之外,我看不到让该模块再次运行的方法。
优点 这是一个真正的类 自动神奇地涵盖继承 将元类用于其正确目的(并让我意识到) 缺点 有吗?
这将是序列化的问题。如果您尝试从文件 (pickle) 中反序列化对象,它将不会使用 __call__
,因此它将创建新文件,您可以使用带有 __new__
的基类继承来防止这种情况发生。
我也更喜欢装饰器语法而不是从元类派生。我的两分钱:
from typing import Callable, Dict, Set
def singleton(cls_: Callable) -> type:
""" Implements a simple singleton decorator
"""
class Singleton(cls_): # type: ignore
__instances: Dict[type, object] = {}
__initialized: Set[type] = set()
def __new__(cls, *args, **kwargs):
if Singleton.__instances.get(cls) is None:
Singleton.__instances[cls] = super().__new__(cls, *args, **kwargs)
return Singleton.__instances[cls]
def __init__(self, *args, **kwargs):
if self.__class__ not in Singleton.__initialized:
Singleton.__initialized.add(self.__class__)
super().__init__(*args, **kwargs)
return Singleton
@singleton
class MyClass(...):
...
这比提供的其他装饰器有一些好处:
isinstance(MyClass(), MyClass) 仍然有效(从子句返回一个函数而不是一个类将使 isinstance 失败)
property、classmethod 和 staticmethod 仍将按预期工作
__init__() 构造函数只执行一次
您可以再次使用 @singleton 从您的装饰类继承(没用?)
缺点:
print(MyClass().__class__.__name__) 将返回 Singleton 而不是 MyClass。如果您仍然需要这个,我建议使用上面建议的元类。
如果您需要基于构造函数参数的不同实例,则需要改进此解决方案(siddhesh-suhas-sathe 提供的解决方案提供了此解决方案)。
最后,正如其他人建议的那样,考虑在 python 中使用模块。模块是对象。您甚至可以将它们传递给变量并将它们注入其他类。
我只是偶然做了一个简单的,并认为我会分享它......
class MySingleton(object):
def __init__(self, *, props={}):
self.__dict__ = props
mything = MySingleton()
mything.test = 1
mything2 = MySingleton()
print(mything2.test)
mything2.test = 5
print(mything.test)
它与 fab 的答案略有相似,但不完全相同。
singleton pattern 不要求我们能够多次调用构造函数。作为一个单例应该只创建一次,不应该被视为只创建一次吗? “欺骗”构造函数可以说会损害易读性。
所以我的建议是这样的:
class Elvis():
def __init__(self):
if hasattr(self.__class__, 'instance'):
raise Exception()
self.__class__.instance = self
# initialisation code...
@staticmethod
def the():
if hasattr(Elvis, 'instance'):
return Elvis.instance
return Elvis()
这不排除用户代码使用构造函数或字段 instance
:
if Elvis() is King.instance:
...如果您确定 Elvis
尚未创建,而 King
已创建。
但它鼓励用户普遍使用 the
方法:
Elvis.the().leave(Building.the())
要完成此操作,您还可以覆盖 __delattr__()
以在尝试删除 instance
时引发异常,并覆盖 __del__()
以引发异常(除非我们知道程序正在结束......)
进一步改进
我感谢那些帮助评论和编辑的人,欢迎更多。虽然我使用 Jython,但这应该更普遍,并且是线程安全的。
try:
# This is jython-specific
from synchronize import make_synchronized
except ImportError:
# This should work across different python implementations
def make_synchronized(func):
import threading
func.__lock__ = threading.Lock()
def synced_func(*args, **kws):
with func.__lock__:
return func(*args, **kws)
return synced_func
class Elvis(object): # NB must be subclass of object to use __new__
instance = None
@classmethod
@make_synchronized
def __new__(cls, *args, **kwargs):
if cls.instance is not None:
raise Exception()
cls.instance = object.__new__(cls, *args, **kwargs)
return cls.instance
def __init__(self):
pass
# initialisation code...
@classmethod
@make_synchronized
def the(cls):
if cls.instance is not None:
return cls.instance
return cls()
注意事项:
如果你不从 python2.x 中的对象子类化你将得到一个不使用 __new__ 的旧式类 装饰 __new__ 时,你必须使用 @classmethod 进行装饰,否则 __new__ 将是一个未绑定的实例方法 这可能会被改进使用元类,因为这将允许您创建类级属性,可能将其重命名为实例
__new
__ 而不是 __init__
,因为它纯粹作用于类属性,这会阻止那里短暂地成为第二个例子。这与方法 2 之间的区别在于,尝试多次初始化是返回单个实例还是引发异常。我想我很高兴要么满足单例模式,一个更容易使用,而另一个更明确地说它是一个单例。
__init__
中使用类名会阻止子类化,但这虽然使事情变得更容易,但这不是必需的
__init__
,希望这应该是可子类化的......
the
可能会因为类似的原因而受益于成为类方法
我不记得我在哪里找到了这个解决方案,但从我的非 Python 专家的角度来看,我发现它是最“优雅”的:
class SomeSingleton(dict):
__instance__ = None
def __new__(cls, *args,**kwargs):
if SomeSingleton.__instance__ is None:
SomeSingleton.__instance__ = dict.__new__(cls)
return SomeSingleton.__instance__
def __init__(self):
pass
def some_func(self,arg):
pass
为什么我喜欢这个?没有装饰器,没有元类,没有多重继承……如果您决定不再希望它成为单例,只需删除 __new__
方法。由于我是 Python 的新手(以及一般的 OOP),我希望有人会让我明白为什么这是一种糟糕的方法?
__new__
。 Don't repeat yourself。
*args
和 **kwargs
,然后什么都不做?以这种方式将它们传递到 dict.__new__
:dict.__new__(cls, *args, **kwargs)
。
__init__
方法。如果您的 __init__
方法确实做了一些事情,您会注意到问题。每当您执行 SomeSingleton()
时,您的单例状态都会由 __init__
方法重置。
基于 Tolli's answer 的代码。
#decorator, modyfies new_cls
def _singleton(new_cls):
instance = new_cls() #2
def new(cls):
if isinstance(instance, cls): #4
return instance
else:
raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
new_cls.__new__ = new #3
new_cls.__init__ = lambda self: None #5
return new_cls
#decorator, creates new class
def singleton(cls):
new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
return _singleton(new_cls)
#metaclass
def meta_singleton(name, bases, attrs):
new_cls = type(name, bases, attrs) #1
return _singleton(new_cls)
解释:
创建新类,继承自给定的 cls(它不会修改 cls,以防有人想要例如 singleton(list)) 创建实例。在覆盖 __new__ 之前,这很容易。现在,当我们轻松创建实例时,使用刚才定义的方法覆盖 __new__。该函数仅在调用者期望的情况下返回实例,否则引发 TypeError。当有人试图从装饰类继承时,条件不满足。如果 __new__() 返回 cls 的实例,则新实例的 __init__() 方法将像 __init__(self[, ...]) 一样被调用,其中 self 是新实例,其余参数与传递给的参数相同__新的__()。 instance 已经初始化,所以 function 将 __init__ 替换为 function 什么都不做。
如果您不需要 Singleton 实例的延迟初始化,那么以下应该是简单且线程安全的:
class A:
instance = None
# Methods and variables of the class/object A follow
A.instance = A()
这种方式 A
是在模块导入时初始化的单例。
也许我误解了单例模式,但我的解决方案就是这么简单实用(pythonic?)。这段代码实现了两个目标
使 Foo 的实例随处可访问(全局)。 Foo 只能存在一个实例。
这是代码。
#!/usr/bin/env python3
class Foo:
me = None
def __init__(self):
if Foo.me != None:
raise Exception('Instance of Foo still exists!')
Foo.me = self
if __name__ == '__main__':
Foo()
Foo()
输出
Traceback (most recent call last):
File "./x.py", line 15, in <module>
Foo()
File "./x.py", line 8, in __init__
raise Exception('Instance of Foo still exists!')
Exception: Instance of Foo still exists!
在为此苦苦挣扎了一段时间后,我最终想出了以下方法,以便从单独的模块调用配置对象时只会加载一次。元类允许将全局类实例存储在内置字典中,目前这似乎是存储适当程序全局的最简洁的方式。
import builtins
# -----------------------------------------------------------------------------
# So..... you would expect that a class would be "global" in scope, however
# when different modules use this,
# EACH ONE effectively has its own class namespace.
# In order to get around this, we use a metaclass to intercept
# "new" and provide the "truly global metaclass instance" if it already exists
class MetaConfig(type):
def __new__(cls, name, bases, dct):
try:
class_inst = builtins.CONFIG_singleton
except AttributeError:
class_inst = super().__new__(cls, name, bases, dct)
builtins.CONFIG_singleton = class_inst
class_inst.do_load()
return class_inst
# -----------------------------------------------------------------------------
class Config(metaclass=MetaConfig):
config_attr = None
@classmethod
def do_load(cls):
...<load-cfg-from-file>...
如果您想使用 instance
作为属性,您可以使用 metaclass
。例如;
class SingletonMeta(type):
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
cls._instance = None
cls._locker = threading.Lock()
@property
def instance(self, *args, **kwargs):
if self._instance is None:
with self._locker:
if self._instance is None:
self._instance = self(*args, **kwargs)
return self._instance
class MyClass(metaclass=SingletonMeta):
def __init__(self):
# init here
pass
# get the instance
my_class_instance = MyClass.instance
MyClass
两次时会发生什么?我在这里有两个不同的地址......它似乎并没有避免新的实例
一个班轮(我并不自豪,但它确实完成了工作):
import sys
class Myclass:
def __init__(self):
# do your stuff
vars(sys.modules[__name__])[type(self).__name__] = lambda: self # singletonify
方法:单次使用后覆盖 __new__
class Singleton():
def __init__(self):
Singleton.instance = self
Singleton.__new__ = lambda _: Singleton.instance
优点
极其简单和简洁
真正的类,不需要模块
正确使用 lambda 和 pythonic 猴子补丁
缺点
__new__ 可以再次被覆盖
__new__
是当 class 是新的 - 当它被定义时,而不是当 instance 是新的时。调用类 (MyClass()
) 是您要覆盖的操作,而不是类的定义。如果您真的想了解 Python 的工作原理,最好的办法(除了继续使用它)就是阅读 docs.python.org/reference/datamodel.html。关于元类的一个很好的参考是 eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example。一篇关于单例的好文章是我在这个答案中链接的谷歌博客的系列。