the*_*oom 829 python singleton metaclass decorator base-class
这个问题不是讨论单身人士设计模式是否可取,反模式,还是任何宗教战争,而是讨论如何以最蟒蛇的方式在Python中最好地实现这种模式.在这种情况下,我将"最pythonic"定义为表示它遵循"最小惊讶原则".
我有多个类可以成为单例(我的用例是记录器,但这并不重要).当我可以简单地继承或装饰时,我不希望在添加gumph的几个类中混乱.
最好的方法:
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
Run Code Online (Sandbox Code Playgroud)
优点
缺点
m = MyClass(); n = MyClass(); o = type(n)();那时m == n && m != o && n != oclass 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
Run Code Online (Sandbox Code Playgroud)
优点
缺点
__new__在从第二个基类继承期间可能被覆盖?人们必须思考的不仅仅是必要的.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
Run Code Online (Sandbox Code Playgroud)
优点
__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
Run Code Online (Sandbox Code Playgroud)
优点
缺点
_sealed属性的重点是什么?super()因为它们会递归.这意味着您无法自定义__new__并且无法子类化需要您调用的类__init__.agf*_*agf 590
我建议使用方法#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
Run Code Online (Sandbox Code Playgroud)
或者在Python3中
class Logger(metaclass=Singleton):
pass
Run Code Online (Sandbox Code Playgroud)
如果要在__init__每次调用类时运行,请添加
else:
cls._instances[cls].__init__(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)
在if声明中Singleton.__call__.
关于元类的几句话.元类是类的类 ; 也就是说,类是其元类的实例.你可以在Python中找到一个对象的元类type(obj).普通的新式类是类型type.Logger在上面的代码中将是类型class 'your_module.Singleton',就像(唯一)实例Logger将是类型class 'your_module.Logger'.当你调用记录仪与Logger(),Python的首先要求的元类Logger,Singleton,做什么,允许实例创建是捷足先登.这个过程与Python一样,__getattr__当你通过调用引用它的一个属性时,通过调用来询问类做什么myclass.attribute.
元类本质上决定了类的定义意味着什么以及如何实现该定义.例如,参见http://code.activestate.com/recipes/498149/,它基本上struct使用元类在Python中重新创建C样式.线程Python中元类的(具体)用例是什么?还提供了一些示例,它们通常似乎与声明性编程有关,尤其是在ORM中使用.
在这种情况下,如果使用方法#2,并且子类定义了一个__new__方法,则每次调用时都会执行SubClassOfSingleton() - 因为它负责调用返回存储实例的方法.对于元类,只有在创建唯一实例时才会调用一次.您想要自定义调用类的含义,这取决于它的类型.
通常,使用元类来实现单例是有意义的.单例是特殊的,因为只创建一次,而元类是自定义类创建的方式.如果需要以其他方式自定义单例类定义,则使用元类可以提供更多控制.
你的单例不需要多重继承(因为元类不是基类),但是对于使用多重继承的创建类的子类,你需要确保单例类是第一个/最左边的一个带有重新定义的元类的类.__call__这不太可能成为一个问题.实例dict 不在实例的命名空间中,因此不会意外覆盖它.
你还会听到单身人士模式违反了"单一责任原则" - 每个班级应该只做一件事.这样,如果您需要更改另一个代码,您不必担心代码会弄乱一件事,因为它们是分开的并且是封装的.元类实现通过了此测试.元类负责执行模式,并且创建的类和子类不需要知道它们是单例.方法#1未通过此测试,因为您注意到"MyClass本身是一个函数,而不是一个类,因此您无法从中调用类方法."
编写适用于Python2和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
Run Code Online (Sandbox Code Playgroud)
这种方法的一个具有讽刺意味的方面是它使用子类来实现元类.一个可能的优点是,与纯元类不同,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()
Run Code Online (Sandbox Code Playgroud)
我最初写的是评论,但是时间太长了,所以我会在这里添加.方法#4比其他装饰器版本更好,但是它比单例所需的代码更多,而且它的功能并不清楚.
主要问题源于该类是它自己的基类.首先,让一个类成为一个几乎完全相同的类的子类,并且只存在于其__class__属性中,这是不是很奇怪?这也意味着你不能定义调用同名的方法对它们的基类的任何方法用super(),因为他们会重复.这意味着您的类无法自定义__new__,也无法从需要__init__调用它们的任何类派生.
您的用例是想要使用单例的更好示例之一.你在其中一条评论中说:"对我而言,伐木似乎一直是单身人士的天生候选人." 你说得对.
当人们说单身人士不好时,最常见的原因是他们是隐性共享状态.虽然全局变量和顶级模块导入是显式共享状态,但传递的其他对象通常是实例化的.这是一个好点,有两个例外.
第一个,也就是各个地方提到的一个,就是单身人士不变的时候.使用全局常量,特别是枚举,被广泛接受,并且被认为是理智的,因为无论如何,没有任何用户可以将它们搞砸到任何其他用户.对于常数单例也是如此.
第二个例外,提到的更少,恰恰相反 - 当单例只是数据接收器而不是数据源(直接或间接)时.这就是伐木工人对单身人士的"自然"用途的原因.由于各种用户没有以其他用户关心的方式更改记录器,因此没有真正的共享状态.这否定了针对单例模式的主要论点,并且因为它们易于用于任务而使它们成为合理的选择.
以下是http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html的引用:
现在,有一种Singleton可以.这是一个单例,其中所有可到达的对象都是不可变的.如果所有对象都是不可变的,那么Singleton没有全局状态,因为一切都是常量.但是很容易将这种单体变成可变的单体,它是非常滑的斜率.因此,我也反对这些单身人士,不是因为他们不好,而是因为他们很容易变坏.(作为旁注,Java枚举只是这些单例.只要你没有将状态放入枚举中就可以了,所以请不要.)
另一种半单可接受的单身人士是那些不影响你的代码执行的人,他们没有"副作用".记录是完美的例子.它充满了单身人士和全球状态.这是可以接受的(因为它不会伤害你),因为无论是否启用给定的记录器,您的应用程序都不会有任何不同.这里的信息以一种方式流动:从您的应用程序到记录器.即使记录器是全局状态的,因为没有信息从记录器流入您的应用程序,因此记录器是可接受的.如果您希望测试断言某些内容已被记录,您仍应注入记录器,但一般情况下,尽管状态已满,但记录器无害.
Cat*_*lus 85
class Foo(object):
pass
some_global_variable = Foo()
Run Code Online (Sandbox Code Playgroud)
模块只导入一次,其他一切都是过度思考.不要使用单身并尽量不使用全局变量.
war*_*iuc 65
使用模块.它只导入一次.在其中定义一些全局变量 - 它们将是单例的'属性'.添加一些函数 - 单例的'方法'.
小智 23
你可能永远不需要Python中的单例.只需在模块中定义所有数据和功能,您就拥有了一个事实上的单例.
如果你真的必须有一个单身课程,那么我会选择:
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
Run Code Online (Sandbox Code Playgroud)
使用:
from mysingleton import my_singleton
my_singleton.foo()
Run Code Online (Sandbox Code Playgroud)
其中mysingleton.py是您定义My_Singleton的文件名.这是有效的,因为在第一次导入文件后,Python不会重新执行代码.
Ces*_*aro 19
你只需要一个装饰器,具体取决于 python 版本:
执行
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
Run Code Online (Sandbox Code Playgroud)
用法
c1 = CustomClass("foo")
c2 = CustomClass("foo")
c3 = CustomClass("bar")
print(c1 == c2)
print(c1 == c3)
Run Code Online (Sandbox Code Playgroud)
输出
>>> CustomClass initialised with foo
>>> CustomClass initialised with bar
>>> True
>>> False
Run Code Online (Sandbox Code Playgroud)
注意如何
foo只打印一次
执行:
c1 = CustomClass("foo")
c2 = CustomClass("foo")
c3 = CustomClass("bar")
print(c1 == c2)
print(c1 == c3)
Run Code Online (Sandbox Code Playgroud)
Jon*_*ker 15
这是你的单行:
singleton = lambda c: c()
Run Code Online (Sandbox Code Playgroud)
以下是您使用它的方式:
@singleton
class wat(object):
def __init__(self): self.x = 1
def get_x(self): return self.x
assert wat.get_x() == 1
Run Code Online (Sandbox Code Playgroud)
您的对象急切地被实例化.这可能是也可能不是你想要的.
小智 9
decorators)。serial,并且要创建一个想要将串行端口作为参数发送的实例,那么使用传统方法将不起作用>>> 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
>>>
Run Code Online (Sandbox Code Playgroud)
使用函数属性也很简单
def f():
if not hasattr(f, 'value'):
setattr(f, 'value', singletonvalue)
return f.value
Run Code Online (Sandbox Code Playgroud)
查看Stack Overflow问题是否有一种简单,优雅的方法来定义Python中的单例?有几个解决方案.
我强烈建议观看Alex Martelli关于python设计模式的讨论:第1 部分和第2部分.特别是,在第1部分中,他讨论了单身人士/共享状态对象.
这是我自己的单例实现。你所要做的就是装饰班级;要获得单例,您必须使用该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
Run Code Online (Sandbox Code Playgroud)
这是代码:
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)
Run Code Online (Sandbox Code Playgroud)
小智 7
我会推荐一个使用元类的优雅解决方案
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)
Run Code Online (Sandbox Code Playgroud)
输出:
Creating Spam
Run Code Online (Sandbox Code Playgroud)
从输出中可以看到,只实例化了一个对象
我更喜欢这个解决方案,我发现它非常清晰和直接。例如,它正在使用双重检查,如果其他线程已经创建了它。需要考虑的其他事情是确保反序列化不会创建任何其他实例。 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))
Run Code Online (Sandbox Code Playgroud)
通过重写该__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
Run Code Online (Sandbox Code Playgroud)
from functools import cache
@cache
class xxx:
....
Run Code Online (Sandbox Code Playgroud)
简单又有效!
| 归档时间: |
|
| 查看次数: |
293100 次 |
| 最近记录: |