在Python中创建单例

这个问题并不是要讨论单例设计模式是否可取,是否是一种反模式,或者是否适用于任何宗教战争,而是要讨论如何在Python中以最Pythonic的方式最好地实现这种模式。在这种情况下,我定义“最pythonic”是指它遵循“最小惊讶原则”

我有多个类将成为单例(我的用例是用于记录器,但这并不重要)。当我可以简单地继承或装饰时,我不希望用添加的gumph杂乱几个类。

最佳方法:


方法1:装饰器

def singleton(class_):instances = {}def getinstance(*args, **kwargs):if class_ not in instances:instances[class_] = class_(*args, **kwargs)return instances[class_]return getinstance
@singletonclass MyClass(BaseClass):pass

优点

  • 装饰器是添加剂,通常比多重继承更直观。

缺点

  • 虽然使用MyClass()创建的对象将是真正的单例对象,但MyClass本身是一个函数,而不是一个类,因此您不能从中调用类方法。也为了

    x = MyClass();y = MyClass();t = type(n)();

然后x == y但是x != t && y != t


方法2:A基类

class Singleton(object):_instance = Nonedef __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:A元类

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]
#Python2class MyClass(BaseClass):__metaclass__ = Singleton
#Python3class MyClass(BaseClass, metaclass=Singleton):pass

优点

  • 这是真正的课堂
  • 自动神奇地覆盖继承
  • 使用__metaclass__的正确目的(并让我意识到它)

缺点

  • 有吗?

方法4:装饰器返回同名的类

def singleton(class_):class class_w(class_):_instance = Nonedef __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 = Falsereturn class_w._instancedef __init__(self, *args, **kwargs):if self._sealed:returnsuper(class_w, self).__init__(*args, **kwargs)self._sealed = Trueclass_w.__name__ = class_.__name__return class_w
@singletonclass MyClass(BaseClass):pass

优点

  • 这是真正的课堂
  • 自动神奇地覆盖继承

缺点

  • 创建每个新类没有开销吗?在这里,我们为每个我们希望制作单例的类创建了两个类。虽然这在我的情况下很好,但我担心这可能无法扩展。当然,关于扩展这种模式是否太容易存在争议…
  • _sealed属性的重点是什么
  • 不能使用super()在基类上调用同名方法,因为它们会递归。这意味着您不能自定义__new__,也不能子类化需要您最多调用__init__的类。

方法5:一个模块

模块文件singleton.py

优点

  • 简洁胜于复杂

缺点

  • 未懒惰实例化
599571 次浏览
class Foo(object):pass
some_global_variable = Foo()

模块只导入一次,其他一切都是过度思考。不要使用单例,尽量不要使用全局。

使用一个模块。它只被导入一次。在其中定义一些全局变量-它们将是单例的“属性”。添加一些函数-单例的“方法”。

查看Stack Overflow问题在Python中有没有一种简单而优雅的方法来定义单例?和几个解决方案。

我强烈建议观看Alex Martelli关于python设计模式的演讲:第一部分第2。特别是,在第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

        else:cls._instances[cls].__init__(*args, **kwargs)

Singleton.__call__中的if语句。

关于元类的几句话。元类是type1;也就是说,一个类是一个type2。你在Python中用type(obj)找到一个对象的元类。正常的新式类是type类型。上面代码中的Logger将是class 'your_module.Singleton'类型,就像Logger的(唯一)实例将是class 'your_module.Logger'类型一样。当你用Logger()调用logger时,Python首先询问LoggerSingleton的元类该做什么,允许实例创建被抢占。这个过程与Python通过调用__getattr__询问类该做什么相同,当你通过做type0引用它的一个属性时。

元类本质上决定了类的定义是什么意思以及如何实现该定义。参见示例http://code.activestate.com/recipes/498149/,它本质上使用元类在Python中重新创建了C风格的struct。线程元类的一些(具体)用例是什么?还提供了一些示例,它们通常似乎与声明式编程有关,尤其是在ORM中使用。

在这种情况下,如果您使用方法#2,并且子类定义了__new__方法,则调用SubClassOfSingleton()的将是每次执行-因为它负责调用返回存储实例的方法。使用元类,当创建唯一实例时,它将只能被召唤一次。您想要自定义调用类的含义,这是由其类型决定的。

一般来说,使用元类来实现单例是0。单例是特殊的,因为它是只创造了一次,而元类是您自定义创建一个类的方式。如果您需要以其他方式自定义单例类定义,使用元类会为您提供更多的控制

你的单例类不需要多重继承(因为元类不是基类),但是对于使用多重继承的创建类的子类,你需要确保单例类是第一/最左边,它的元类重新定义了__call__这不太可能是一个问题。实例字典是不在实例的命名空间中,所以它不会意外覆盖它。

你还会听到单例模式违反了“单一职责原则”——每个类都应该做只有一件事。这样,如果你需要改变另一件事,你就不必担心弄乱代码所做的一件事,因为它们是独立的和封装的。元类实现通过这个测试。元类负责执行该模式,创建的类和子类不需要意识到他们是单身方法#1没有通过这个测试,正如你所指出的“MyClass本身是一个函数,而不是一个类,所以你不能从中调用类方法。”

Python 2和3兼容版本

编写同时适用于Python2和Python3的东西需要使用稍微复杂一点的方案。由于元类通常是类型type的子类,因此可以使用元类在运行时动态创建一个中介基类,并将其作为元类,然后使用作为公共Singleton基类的基类。解释起来比做起来难,如下所示:

# works in Python 2 & 3class _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__调用它们的类派生。

何时使用单例模式

您的用例是想要使用单例的第0个用例。您在其中一条评论中说:“对我来说,日志记录一直是单例的自然候选者。”你是绝对正确

当人们说单例不好时,最常见的原因是它们是隐式共享状态。虽然全局变量和顶级模块导入是明确共享状态,但传递的其他对象通常是实例化的。这是一个很好的观点,除了两个例外

第一个,也是在很多地方被提到的,是当单例是恒定时。使用全局常量,尤其是枚举,被广泛接受,并被认为是合理的,因为无论如何,任何用户都不能为任何其他用户搞砸它们。对于常量单例来说也是如此。

第二个例外,被提及较少,相反-当单例是只有一个数据接收器时,而不是数据源(直接或间接)。这就是为什么记录器感觉是单例的“自然”使用。由于各种用户在其他用户关心的方面是而不是更换记录器,所以有不是真正的共享状态。这否定了反对单例模式的主要论点,并使它们成为合理的选择,因为它们是任务的易用性

以下是来自http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html的引用:

现在,有一种Singleton是可以的。这是一种所有可达对象都是不可变的单例。如果所有对象都是不可变的,那么Singleton就没有全局状态,因为一切都是恒定的。但是很容易将这种单例变成可变的,这是非常滑坡的。因此,我也反对这些Singleton,不是因为它们不好,而是因为它们很容易变坏。(需要注意的是Java枚举就是这种单例。只要你不将状态放入枚举中,你就没问题,所以请不要。)

另一种半可接受的单例是那些不影响代码执行的单例,它们没有“副作用”。日志记录是一个完美的例子。它加载了单例和全局状态。这是可以接受的(因为它不会伤害你),因为无论给定的记录器是否启用,你的应用程序的行为都没有任何不同。这里的信息以一种方式流动:从你的应用程序流入记录器。即使思想记录器是全局状态的,因为没有信息从记录器流入你的应用程序,记录器也是可以接受的。如果您希望您的测试断言某些东西正在被记录,您仍然应该注入您的记录器,但一般来说,记录器尽管充满了状态,但并不有害。

好吧,除了同意Pythonic关于拥有模块级全局的一般建议之外,不如这样:

def singleton(class_):class class_w(class_):_instance = Nonedef __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 = Falsereturn class_w._instancedef __init__(self, *args, **kwargs):if self._sealed:returnsuper(class_w, self).__init__(*args, **kwargs)self._sealed = Trueclass_w.__name__ = class_.__name__return class_w
@singletonclass MyClass(object):def __init__(self, text):print text@classmethoddef 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 timeMyClass # the __name__ is preservedTrue    # this is actually the same instance

这是我自己的单例实现。你所要做的就是装饰类;要获得单例,你必须使用Instance方法。这是一个例子:

@Singletonclass 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 Zeng = 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 theclass that should be a singleton.
The decorated class can define one `__init__` function thattakes only the `self` argument. Other than that, there areno restrictions that apply to the decorated class. 
To get the singleton instance, use the `Instance` method. Tryingto 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 anew instance of the decorated class and calls its `__init__` method.On all subsequent calls, the already created instance is returned.
"""try:return self._instanceexcept 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)

方法3看起来很简洁,但如果你想让你的程序同时在python2python3中运行,它就行不通了。即使用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”,但我还没有尝试过(我已经尝试了如图所示的代码)。

这里有一个单行给你:

singleton = lambda c: c()

以下是你如何使用它:

@singletonclass wat(object):def __init__(self): self.x = 1def get_x(self): return self.x
assert wat.get_x() == 1

您的对象会急切地实例化。这可能是也可能不是您想要的。

这个怎么样:

def singleton(cls):instance=cls()cls.__new__ = cls.__call__= lambda cls: instancecls.__init__ = lambda self: Nonereturn instance

将其用作应该是单例的类的装饰器。像这样:

@singletonclass MySingleton:#....

这类似于另一个答案中的singleton = lambda c: c()装饰器。与其他解决方案一样,唯一的实例具有类的名称(MySingleton)。但是,使用此解决方案,您仍然可以通过执行MySingleton()从类中“创建”实例(实际上获得唯一的实例)。它还防止您通过执行type(MySingleton)()(也返回相同的实例)创建额外的实例。

我不记得我在哪里找到了这个解决方案,但我发现从我的非Python专家的角度来看,它是最“优雅”的:

class SomeSingleton(dict):__instance__ = Nonedef __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),我希望有人能直截了当地告诉我为什么这是一种糟糕的方法?

你可能永远不需要Python中的单例。只需在模块中定义你的所有数据和函数,你就有了一个事实上的单例:

import datetimefile_name=None
def set_file_name(new_file_name: str):global file_namefile_name=new_file_name
def write(message: str):global file_nameif file_name:with open(file_name, 'a+') as f:f.write("{} {}\n".format(datetime.datetime.now(), message))else:print("LOG: {}", message)

要使用:

import loglog.set_file_name("debug.log")log.write("System starting")...

如果你真的必须有一个单例类,那么我会选择:

class MySingleton(object):def foo(self):pass
my_singleton = MySingleton()

要使用:

from mysingleton import my_singletonmy_singleton.foo()

其中mysingleton.py是定义MySingleton的文件名。这是有效的,因为在第一次导入文件后,Python不会重新执行代码。

基于托利的回答的代码。

#decorator, modyfies new_clsdef _singleton(new_cls):instance = new_cls()                                              #2def new(cls):if isinstance(instance, cls):                                 #4return instanceelse:raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))new_cls.__new__  = new                                            #3new_cls.__init__ = lambda self: None                              #5return new_cls

#decorator, creates new classdef singleton(cls):new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1return _singleton(new_cls)

#metaclassdef meta_singleton(name, bases, attrs):new_cls = type(name, bases, attrs)                                #1return _singleton(new_cls)

说明:

  1. 创建新类,继承自给定的cls
    (如果有人想要,它不会修改cls,例如singleton(list)

  2. 创建实例。在覆盖__new__之前,这很容易。

  3. 现在,当我们轻松创建实例时,使用刚才定义的方法覆盖__new__
  4. 该函数仅在调用者期望的情况下返回instance,否则会引发TypeError
    当有人试图从装饰类继承时,条件不满足。

  5. <区块链>

    如果#0返回#1的实例,则将调用新实例的#2方法类似于__init__(self[, ...]),其中self是新实例,其余参数与传递给__new__()的参数相同。

    instance已经初始化,因此函数将__init__替换为什么都不做的函数。

看到它在线工作

这与fab的答案略有相似,但不完全相同。

单例模式不要求我们能够多次调用构造函数。由于单例应该创建一次并且只创建一次,它不应该被视为只创建一次吗?“欺骗”构造函数可能会损害易读性。

所以我的建议是这样的:

class Elvis():def __init__(self):if hasattr(self.__class__, 'instance'):raise Exception()self.__class__.instance = self# initialisation code...
@staticmethoddef the():if hasattr(Elvis, 'instance'):return Elvis.instancereturn Elvis()

这并不排除用户代码使用构造函数或字段instance

if Elvis() is King.instance:

…如果您确定Elvis尚未创建,而King已经创建。

但它鼓励用户普遍使用the方法:

Elvis.the().leave(Building.the())

要完成此操作,您还可以覆盖__delattr__()以在尝试删除instance时引发异常,并覆盖__del__()以引发异常(除非我们知道程序正在结束…)

进一步改进


我感谢那些帮助评论和编辑的人,欢迎更多。当我使用Jython时,这应该更普遍地工作,并且是线程安全的。

try:# This is jython-specificfrom synchronize import make_synchronizedexcept ImportError:# This should work across different python implementationsdef make_synchronized(func):import threadingfunc.__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_synchronizeddef __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_synchronizeddef the(cls):if cls.instance is not None:return cls.instancereturn cls()

注意事项:

  1. 如果你不从python2. x中的对象子类化,你会得到一个老式的类,它不使用__new__
  2. 当装饰__new__时,您必须使用@类方法进行装饰,否则__new__将是一个未绑定的实例方法
  3. 这可以通过使用元类来改进,因为这将允许您使the成为类级属性,可能将其重命名为instance

我会把我的扔进戒指里。这是一个简单的装饰。

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@singletonclass YourClass:...  # Your normal implementation, no special requirements.

我认为它比其他一些解决方案有好处:

  • 它清晰简洁(在我看来;D)。
  • 它的操作是完全封装的。你不需要改变YourClass的实现。这包括不需要为你的类使用元类(注意上面的元类是在工厂,而不是“真正的”类)。
  • 它不依赖于猴子修补任何东西。
  • 对调用者是透明的:
    • 调用者仍然简单地导入YourClass,它看起来像一个类(因为它是),并且他们正常使用它。不需要使调用者适应工厂函数。
    • YourClass()实例化的仍然是您实现的YourClass的真实实例,而不是任何类型的代理,因此不会产生副作用。
    • isinstance(instance, YourClass)和类似的操作仍然按预期工作(尽管该位确实需要abc,因此排除了Python<2.6)。

我确实想到了一个缺点:真实类的类方法和静态方法不能通过隐藏它的工厂类透明地调用。我很少使用这个,以至于我从来没有碰巧遇到过这种需求,但通过在工厂上使用自定义元类可以很容易地纠正它,该元类实现__getattr__()将所有的属性访问委托给真实类。

我实际上发现了一个更有用的相关模式(并不是说我经常需要这些东西)是一个“Unique”模式,其中使用相同的论点实例化类会导致返回相同的实例。即“每个参数的单例”。上面的内容很好地适应了这一点,变得更加简洁:

def unique(real_cls):
class UniqueFactory(ABC):
@functools.lru_cache(None)  # Handy for 3.2+, but use any memoization decorator you likedef __new__(cls, *args, **kwargs):return real_cls(*args, **kwargs)
UniqueFactory.register(real_cls)return UniqueFactory

话虽如此,我确实同意一般的建议,如果你认为你需要这些东西之一,你真的应该停下来问自己是否真的需要。

这个答案可能不是你想要的。我想要一个单例,因为只有那个对象有它的身份,用于比较。在我的例子中,它被用作哨兵值。答案很简单,让任何对象mything = object(),根据python的本质,只有那个东西才会有它的身份。

#!pythonMyNone = object()  # The singleton
for item in my_list:if item is MyNone:  # An Example identity comparisonraise StopIteration

这个解决方案在模块级别造成了一些命名空间污染(三个定义而不是一个定义),但我发现它很容易理解。

我希望能够编写这样的东西(惰性初始化),但不幸的是,类在它们自己的定义主体中不可用。

# wouldn't it be nice if we could do this?class Foo(object):instance = None
def __new__(cls):if cls.instance is None:cls.instance = object()cls.instance.__class__ = Fooreturn cls.instance

因为这是不可能的,我们可以打破初始化和静态实例

急切初始化:

import random

class FooMaker(object):def __init__(self, *args):self._count = random.random()self._args = args

class Foo(object):def __new__(self):return foo_instance

foo_instance = FooMaker()foo_instance.__class__ = Foo

懒惰初始化:

急切初始化:

import random

class FooMaker(object):def __init__(self, *args):self._count = random.random()self._args = args

class Foo(object):def __new__(self):global foo_instanceif foo_instance is None:foo_instance = FooMaker()return foo_instance

foo_instance = None

一个班轮(我并不自豪,但它确实有效):

import sys
class Myclass:def __init__(self):# do your stuffvars(sys.modules[__name__])[type(self).__name__] = lambda: self # singletonify

如果您不需要对Singleton实例进行延迟初始化,那么以下操作应该很简单且线程安全:

class A:instance = None# Methods and variables of the class/object A followA.instance = A()

这样,A是在模块导入时初始化的单例。

  • 如果想要拥有多个相同类的实例,但前提是args或kwargs不同,则可以使用第三方python包方便的装饰器(包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 differentFalse>>> b is c  # has to be sameTrue>>>

也许我没有理解单例模式,但我的解决方案是简单实用的(pythonic?)。这段代码实现了两个目标

  1. 使Foo的实例在任何地方都可访问(全局)。
  2. 只有一个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_instclass_inst.do_load()
return class_inst
# -----------------------------------------------------------------------------
class Config(metaclass=MetaConfig):
config_attr = None
@classmethoddef do_load(cls):...<load-cfg-from-file>...

使用函数属性也很简单

def f():if not hasattr(f, 'value'):setattr(f, 'value', singletonvalue)return f.value

我会推荐一个使用元类的优雅解决方案

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 referencesuper().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):if self.__instance is None:# if the object has not already been createdself.__instance = super().__call__(*args, **kwargs) # Call the __init__ method of the subclass (Spam) and save the referencereturn self.__instanceelse:# if object (Spam) reference already exists; return itreturn 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

从输出中可以看到,只有一个对象被实例化

我更喜欢这个解决方案,我发现它非常清晰明了。例如,它使用双重检查,如果其他线程已经创建了它。另外要考虑的是确保反序列化不会创建任何其他实例。https://gist.github.com/werediver/4396488

import threading

# Based on tornado.ioloop.IOLoop.instance() approach.# See https://github.com/facebook/tornadoclass SingletonMixin(object):__singleton_lock = threading.Lock()__singleton_instance = None
@classmethoddef 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 a2assert b is b2assert a is not b
print('a:  %s\na2: %s' % (a, a2))print('b:  %s\nb2: %s' % (b, b2))

我也更喜欢装饰器语法而不是从元类派生。我的两分钱:

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

@singletonclass MyClass(...):...

这比提供的其他装饰器有一些好处:

  • isinstance(MyClass(), MyClass)仍然可以工作(从clausure返回一个函数而不是一个类会使is实例失败)
  • propertyclassmethodstaticmethod仍将按预期工作
  • __init__()构造函数只执行一次
  • 你可以从你的装饰类继承(无用?)再次使用@Singleton

缺点:

  • print(MyClass().__class__.__name__)将返回Singleton而不是MyClass。如果您仍然需要此功能,我建议使用上面建议的元类。

如果您需要基于构造函数参数的不同实例,则需要改进此解决方案(siddhesh-suhas-sathe提供的解决方案提供了此解决方案)。

最后,正如其他建议的那样,考虑在python中使用模块。模块对象。您甚至可以在变量中传递它们并将它们注入其他类中。

优点

这是一个真正的类自动神奇地覆盖继承使用元类为了它的正确目的(并让我意识到这一点)缺点

有吗?

这将是序列化的问题。如果您尝试从文件中反序列化对象(泡菜),它将不会使用__call__,因此它将创建新文件,您可以使用__new__的基类继承来防止这种情况。

如果您想使用instance作为属性,您可以使用metaclass。例如;

class SingletonMeta(type):def __init__(cls, *args, **kwargs):super().__init__(*args, **kwargs)cls._instance = Nonecls._locker = threading.Lock()
@propertydef 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 herepass

# get the instancemy_class_instance = MyClass.instance

我只是偶然做了一个简单的,并认为我应该分享它。

class MySingleton(object):def __init__(self, *, props={}):self.__dict__ = props
mything = MySingleton()mything.test = 1mything2 = MySingleton()print(mything2.test)mything2.test = 5print(mything.test)

使用类变量(没有装饰器)

通过重写__new__方法返回类的相同实例。仅首次初始化类的布尔值:

class SingletonClass:_instance = None
def __new__(cls, *args, **kwargs):# If no instance of class already exitsif cls._instance is None:cls._instance = object.__new__(cls)cls._instance._initialized = Falsereturn cls._instance        
def __init__(self, *args, **kwargs):if self._initialized:return
self.attr1 = args[0]# set the attribute to `True` to not initialize againself._initialized = True
from functools import cache
@cacheclass xxx:....

死容易和工作!

这是结合@agf和@(Siddhesh Suhas Sathe)解决方案的简单实现,它使用元类并考虑构造函数参数,因此如果您创建了具有完全相同参数的foo类,则可以返回相同的实例

class SingletonMeta(type):_instances = {}
def __call__(cls, *args, **kwargs):"""Possible changes to the value of the `__init__` argument do not affectthe returned instance."""cls_instances = cls._instances.get(cls) or []matching_instances = list(filter(lambda x: x["args"] == args and x["kwargs"] == kwargs,cls_instances,))if len(matching_instances) == 1:return matching_instances[0]["instance"]else:instance = super().__call__(*args, **kwargs)cls_instances.append({"instance": instance, "args": args, "kwargs": kwargs})cls._instances[cls] = cls_instancesreturn instance

class foo(metaclass=SingletonMeta):def __init__(self, param, k_param=None) -> None:print("Creating new instance")self.param = paramself.k_param = k_paramself._creation_time = time.time()

我更喜欢使用静态方法GetInstance()来创建单例对象(也不允许任何其他方法这样做),以强调我使用的是单例设计模式。

import inspectclass SingletonMeta(type):__instances = {}GET_INSTANCE = 'GetInstance' # class method ussed to create Singleton instance
def __call__(cls, *args, **kwargs):caller_frame = inspect.currentframe().f_back
caller_class = caller_frame.f_locals.get('cls_ref')caller_method_name = caller_frame.f_code.co_nameif caller_class is cls and \caller_method_name == SingletonMeta.GET_INSTANCE:obj = super(SingletonMeta, cls).__call__(*args, **kwargs)else:raise Exception(f"Class '{cls.__name__}' is a singleton! Use '{cls.__name__}.{SingletonMeta.GET_INSTANCE}()' to create its instance.")
return obj
def __new__(cls, name, bases, dct):def GetInstance(cls_ref):if cls_ref not in cls_ref.__instances:cls_ref.__instances[cls_ref] = cls_ref()
return cls_ref.__instances[cls_ref]       
return super().__new__(cls, name, bases, {**dct, GetInstance.__name__: classmethod(GetInstance)})#------------------------if __name__ == '__main__':class SingletonSample1(metaclass=SingletonMeta):def __init__(self):self.__x = 1
@propertydef x(self) -> int:return self.__x
@x.setterdef x(self, value):self.__x = value
s1 = SingletonSample1.GetInstance()s1.x = 3
try:s2 = SingletonSample1()Exception as error:print(repr(error))

我想指出的是,第一种方法定义了一个用于查找的字典,直到今天我还不理解,我看到这个解决方案到处传播,所以我想每个人都只是从这里复制粘贴它。

我说的是这一个:

def singleton(class_):instances = {} # <-- will always only have one entry.def getinstance(*args, **kwargs):if class_ not in instances:instances[class_] = class_(*args, **kwargs)return instances[class_]return getinstance

这对于元类解决方案是有意义的,但是对于一次性装饰器解决方案,每次调用装饰器时,都会定义一个新函数以及一个新的实例变量,因此每个“实例”始终只有一个条目,除非您将其设为全局。无论如何,它也不适用于继承。

一个类似的,但更简单,也更好的可调解决方案:

def singleton(class_):def wrapper(*args, **kwargs):if not wrapper._instance:wrapper._instance = class_(*args, **kwargs)return wrapper._instance
wrapper._instance = Nonereturn wrapper

添加一个简单

    ...wrapper.__wrapped__ = class_return wrapper

此外,还允许通过访问__wrapped__来继承或模拟,这在内部字典查找中也是不可能的。

(当然,如果我只是不理解字典查找背后的奥秘,请原谅我。也许是我的错,没有理解它背后的特定意图)

方法:一次性使用后覆盖__new__

class Singleton():def __init__(self):Singleton.instance = selfSingleton.__new__ = lambda _: Singleton.instance

优点

  • 非常简单和简洁
  • 真正的类,不需要模块
  • 正确使用lambda和pythonic猴子补丁

缺点

  • __new__可能再次被推翻

正如@Staale提到的这里,在python中创建单例的最简单方法是使用具有全局变量(作为“属性”和作为“方法”的全局函数)的模块。但我想在这个已经令人惊叹的答案中添加一些非常重要的东西:继承在这里也有效!

所有你需要做的是从另一个“单例模块”A.py继承的“单例模块”B.pyB.py开始:from A import *,这尊重私有变量(默认情况下不导入它们)。

我想强调的几个警告是,

  1. metaclass方法:
  • 您不能从2个元类继承。检查
  • 如果您使用的是工厂模式,所有这些都是单例类,那么它将不起作用。
  • 你的父类已经从ABC继承(这是元类),那么你不能从单例元类继承
  1. 装饰方法:
  • 我使用了一个函数作为工厂接口,它从base.__subclasses__()创建了T的实例。
  • 这将跳过子类初始化的单例装饰器

看看这个。这个想法是通过args和kwargs散列实例键。https://stackoverflow.com/a/73495782/2910384

M o s t̲͟͟ ̲͟͟r e c e n t̲͟͟ ̲͟͟v e r s i o n s̲͟͟ ̲͟͟o f̲͟͟ ̲͟͟p y t h o n̲͟͟ ̲͟͟a l r e a d y̲͟͟ ̲͟͟h a v e̲͟͟ ̲͟͟t h e̲͟͟ ̲͟͟d e c o r a t o r̲͟͟ ̲͟͟t h a t̲͟͟ ̲͟͟P w a n t s,它只是不叫singleton,因为它的用法更通用,也适用于函数:

python3.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)

产出(注意foo只打印了一次):

>>> CustomClass initialised with foo>>> CustomClass initialised with bar>>> True>>> False

python3.9+

from functools.cache
@cacheclass CustomClass(object):...