在 Python 中实现单例的最佳方法是什么
- 2024-11-25 08:50:00
- admin 原创
- 156
问题描述:
这个问题不是为了讨论单例设计模式是否可取、是否是反模式,也不是为了任何宗教战争,而是为了讨论如何以最符合 Python 风格的方式在 Python 中最好地实现这种模式。在这种情况下,我将“最符合 Python 风格”定义为它遵循“最小惊讶原则”。
我有多个类将成为单例(我的用例是用于记录器,但这并不重要)。当我可以简单地继承或装饰时,我不希望用额外的麻烦弄乱几个类。
最佳方法:
方法 1:装饰器
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 = type(n)();
然后x == y
但是x != t && y != t
方法 2:基类
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__
正确目的(并让我意识到这一点)
缺点
有吗?
方法4:装饰器返回同名的类
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
优点
简单胜过复杂
缺点
不是惰性实例化
解决方案 1:
使用元类
我推荐方法 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)
if
中的声明Singleton.__call__
。
关于元类的几句话。元类是类的类;也就是说,类是其元类的实例。在 Python 中,可以使用 找到对象的元类type(obj)
。普通的新式类的类型为type
。Logger
上面代码中的 将是 类型class 'your_module.Singleton'
,就像 的(唯一)实例Logger
将是 类型一样class 'your_module.Logger'
。当您使用 调用 logger 时Logger()
,Python 首先会询问 的元类要做Logger
什么,从而允许抢先创建实例。此过程与当您通过执行 引用类的某个属性时,Singleton
Python 通过调用 询问类要做什么相同。__getattr__
`myclass.attribute`
元类本质上决定了类的定义意味着什么以及如何实现该定义。例如,请参阅http://code.activestate.com/recipes/498149/,它本质上使用元类在 Python 中重新创建了 C 样式struct
。线程元类的一些(具体)用例是什么?也提供了一些示例,它们通常似乎与声明式编程有关,尤其是在 ORM 中使用的声明式编程。
在这种情况下,如果您使用方法 #2,并且子类定义了一个方法,则每次调用时__new__
都会执行该方法——因为它负责调用返回存储实例的方法。使用元类时,它只会在创建唯一实例时调用一次。您想自定义调用类的含义,这取决于其类型。SubClassOfSingleton()
一般来说,使用元类来实现单例是有意义的。单例之所以特殊,是因为它的实例只创建一次,而元类是自定义类创建的方式,允许它的行为与普通类不同。如果您需要以其他方式自定义单例类定义,使用元类可以让您拥有更多的控制权。
您的单例不需要多重继承(因为元类不是基类),但对于使用多重继承的已创建类的子类,您需要确保单例类是第一个/最左边的具有重新定义元类的类__call__
。这不太可能成为问题。实例字典不在实例的命名空间中,因此不会意外覆盖它。
您还会听说单例模式违反了“单一职责原则”——每个类应该只做一件事。这样,如果您需要更改另一件事,您就不必担心会弄乱代码执行的一件事,因为它们是分开且封装的。元类实现通过了此测试。元类负责执行模式,并且创建的类和子类不需要知道它们是单例。方法 #1未通过此测试,正如您指出的“MyClass 本身是一个函数,而不是一个类,因此您无法从中调用类方法。”
Python 2 和 3 兼容版本
编写在 Python2 和 Python 3 中都能正常工作的代码需要使用稍微复杂一些的方案。由于元类通常是类型的子类type
,因此可以使用元类在运行时动态创建一个中间基类,并将其作为元类,然后将其用作公共Singleton
基类的基类。解释起来比做起来难,如下所示:
# 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的引文:
现在,有一种单例是可以的。即所有可访问对象都是不可变的单例。如果所有对象都是不可变的,那么单例就没有全局状态,因为一切都是恒定的。但是将这种单例变成可变的单例非常容易,这是一个非常危险的趋势。因此,我也反对这些单例,不是因为它们不好,而是因为它们很容易变坏。(顺便说一句,Java 枚举就是这种单例。只要你不把状态放入枚举中,你就没问题,所以请不要这么做。)
另一种半可接受的单例是那些不会影响代码执行的单例,它们没有“副作用”。日志记录就是一个完美的例子。它加载了单例和全局状态。它是可以接受的(因为它不会伤害你),因为无论是否启用了给定的记录器,你的应用程序的行为都不会有任何不同。这里的信息流向单向:从你的应用程序到记录器。即使记录器是全局状态,因为没有信息从记录器流入你的应用程序,记录器也是可以接受的。如果你想让你的测试断言某些东西正在被记录,你仍然应该注入你的记录器,但一般来说,尽管记录器充满了状态,但并没有什么害处。
解决方案 2:
class Foo(object):
pass
some_global_variable = Foo()
模块仅导入一次,其他一切都是多虑了。不要使用单例,尽量不要使用全局变量。
解决方案 3:
使用模块。它仅被导入一次。在其中定义一些全局变量 - 它们将成为单例的“属性”。添加一些函数 - 单例的“方法”。
解决方案 4:
你可能永远不需要 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("{} {}
".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 不会重新执行代码。
解决方案 5:
你只需要一个装饰器,取决于 Python 版本:
Python 3.2+
执行
from functools import lru_cache
@lru_cache(maxsize=None)
class CustomClass(object):
def __init__(self, arg):
print(f"CustomClass initialised with {arg}")
self.arg = arg
用法
c1 = CustomClass("foo")
c2 = CustomClass("foo")
c3 = CustomClass("bar")
print(c1 == c2)
print(c1 == c3)
输出
>>> CustomClass initialised with foo
>>> CustomClass initialised with bar
>>> True
>>> False
注意
foo
只打印了一次
Python 3.9+
执行:
from functools import cache
@cache
class CustomClass(object):
...
解决方案 6:
以下是一句话:
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
你的对象被急切地实例化。这可能是你想要的,也可能不是。
解决方案 7:
如果想要拥有同一个类的多个实例,但前提是 args 或 kwargs 不同,则可以使用第三方 python 包Handy Decorators(包
decorators
)。前任。
1. 如果你有一个处理`serial`通信的类,并且要创建一个实例,你想将串行端口作为参数发送,那么使用传统方法是行不通的
2. 使用上面提到的装饰器,如果参数不同,则可以创建该类的多个实例。
3. 对于相同的参数,装饰器将返回已经创建的相同实例。
>>> 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
>>>
解决方案 8:
使用函数属性也非常简单
def f():
if not hasattr(f, 'value'):
setattr(f, 'value', singletonvalue)
return f.value
解决方案 9:
我将推荐一种使用元类的优雅解决方案
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
从输出中可以看出,仅实例化了一个对象
解决方案 10:
我更喜欢这个解决方案,我发现它非常清晰明了。例如,它使用双重检查,看看其他线程是否已经创建了它。另外要考虑的是确保反序列化不会创建任何其他实例。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
a2: %s' % (a, a2))
print('b: %s
b2: %s' % (b, b2))
解决方案 11:
一行解决方案:
将其添加到任何初始化self.__class__.__new__ = lambda _: self
class WhateverClass():
def __init__(self):
self.__class__.__new__ = lambda _: self
优点
非常简单明了。只需添加一行即可
__init__
真正的课程,无需模块
Python 式的 lambda、闭包和 monkey patching 用法
它是如何工作的?__new__
在第一次调用之后被覆盖,__init__
因此构造函数本质上是一次性的。lambda 用于self
使用闭包返回引用,这使得一切都非常匿名。
解决方案 12:
这是我自己实现的单例。你所要做的就是装饰类;要获得单例,你必须使用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)
解决方案 13:
使用类变量(无装饰器)
通过重写__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
解决方案 14:
from functools import cache
@cache
class xxx:
....
非常简单而且有效!
解决方案 15:
方法 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
我推测赋值中的“object”需要替换为“BaseClass”,但我还没有尝试过(我已经尝试过如图所示的代码)。
解决方案 16:
我会把我的扔进戒指里。这是一个简单的装饰品。
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)。
我确实想到了一个缺点:真实类的类方法和静态方法不能通过隐藏它的工厂类透明地调用。我很少使用这个,所以我从来没有遇到过这种需要,但是通过在工厂上使用自定义元类来轻松纠正这个问题,该元类实现__getattr__()
将所有属性访问委托给真实类。
我发现一个更有用的相关模式(我并不是说这些事情经常需要)是“唯一”模式,其中使用相同参数实例化类会导致返回相同的实例。即“每个参数一个单例”。上面的代码很好地适应了这一点,并且变得更加简洁:
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。
解决方案 17:
也许我误解了单例模式,但我的解决方案非常简单实用(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!
解决方案 18:
好吧,除了同意关于模块级全局的一般 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
解决方案 19:
这个怎么样:
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)()
(也返回相同的实例)创建其他实例。
解决方案 20:
这个答案可能不是您想要的。我想要一个单例,即只有该对象才具有其身份,以便进行比较。在我的例子中,它被用作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
解决方案 21:
优点
它是一个真正的类,自动神奇地覆盖继承,使用元类
来实现其正确的目的(并且让我意识到这一点)缺点有吗?
这将是序列化的问题。如果您尝试从文件 (pickle) 反序列化对象,它将无法使用__call__
,因此它将创建新文件,您可以使用基类继承来__new__
防止这种情况。
解决方案 22:
metaclass
如果您想用作属性,可以使用instance
。例如;
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
解决方案 23:
我也更喜欢装饰器语法,而不是从元类派生。我的看法是:
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 中的模块。模块是对象。您甚至可以将它们传递到变量中并将它们注入其他类中。
解决方案 24:
我只是偶然做了一个简单的并想分享它......
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)
解决方案 25:
它与 fab 的答案略有相似,但并不完全相同。
单例模式不需要我们能够多次调用构造函数。由于单例应该只创建一次,那么它难道不应该看起来只创建了一次吗?“欺骗”构造函数可能会损害可读性。
所以我的建议是这样的:
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__()
以便它引发异常(除非我们知道程序正在结束......)
这里有一些本体论问题:一个单例能成为另一个单例吗?对单例类进行子类化似乎充满了问题。即便如此,你也可能会问
isinstance()
,不是is
。
进一步改进
感谢那些帮助我发表评论和编辑的人,欢迎大家多多指教。虽然我使用的是 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 中从 object 子类化,那么你将得到一个旧式类,它不使用
__new__
装饰时
__new__
必须使用@classmethod进行装饰,否则__new__
将成为未绑定的实例方法这可能可以通过使用元类来改进,因为这将允许您创建
the
一个类级属性,可能将其重命名为instance
解决方案 26:
我不记得在哪里找到这个解决方案,但从我非 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)的新手,我希望有人能告诉我为什么这是一种糟糕的方法?
解决方案 27:
代码基于Tolli 的回答。
#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__
使用刚才定义的方法进行覆盖。仅当函数
instance
符合调用者期望时,它才会返回,否则会引发TypeError
。
当有人试图从装饰类继承时,条件不满足。
如果
__new__()
返回的实例cls
,那么新实例的__init__()
方法将像一样被调用__init__(self[, ...])
,其中 self 是新实例并且其余参数与传递给的相同__new__()
。
instance
已经初始化,因此函数将替换__init__
为不执行任何操作的函数。
在线查看其运行情况
解决方案 28:
如果您不需要对 Singleton 实例进行延迟初始化,那么以下内容应该很容易并且是线程安全的:
class A:
instance = None
# Methods and variables of the class/object A follow
A.instance = A()
这种方式A
是在模块导入时初始化的单例。
解决方案 29:
经过一段时间的挣扎,我最终想出了下面的方法,这样配置对象在从单独的模块调用时只会被加载一次。元类允许将全局类实例存储在内置字典中,目前看来这是存储适当的程序全局变量的最简洁的方法。
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>...
解决方案 30:
一句话(我并不自豪,但它确实起了作用):
import sys
class Myclass:
def __init__(self):
# do your stuff
vars(sys.modules[__name__])[type(self).__name__] = lambda: self # singletonify