在Python中创建单例模式

1655
这个问题并不讨论单例设计模式是否可取,是否是反模式或任何宗教战争,而是讨论如何以最符合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

优点

  • 装饰器的特性通常比多重继承更加直观。

缺点

  • While objects created using MyClass() would be true singleton objects, MyClass itself is a function, not a class, so you cannot call class methods from it. Also for

    x = MyClass();
    y = MyClass();
    t = type(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__?这需要比必要更多的思考。

方法三:元类

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__的类。

方法五:一个模块

一个名为singleton.py的模块文件。

优点

  • 简单胜于复杂。

缺点

  • 不是延迟实例化的。

20
另外三种技术:使用模块替代(通常情况下,我认为这是Python中更适合的模式,但这也取决于你用它做什么);创建一个单一实例并直接操作它(foo.x 或者如果你坚持可以用 Foo.x 替代 Foo().x);使用类属性和静态/类方法(Foo.x)。 - Chris Morgan
23
如果你只打算使用类方法或静态方法,那么实际上就不需要创建一个类了。 - Cat Plus Plus
5
@猫: 效果类似,但创建全局变量的原因可能是任何事情,包括不知道更好的方法。为什么要创建一个单例?如果你不知道答案,那就不应该在这里。这种明确性不仅更符合Python的风格,而且使维护更加简单。是的,单例是全局变量的语法糖,但类也是一堆难看东西的语法糖,没有人会告诉你总是最好不用它们。 - theheadofabroom
35
反单例情绪是最糟糕的模拟船运动编程。对于那些听过(很少有人真正阅读过)“Goto语句被认为是有害的”并认为无论上下文如何,goto语句都是糟糕代码的标志的人也是如此。 - Hejazzman
5
你好,感谢你详细的帖子。我对模式编程和Python都比较新,而且我很惊讶尽管方法2似乎是最为人熟知的一种(它无处不在),但几乎没有人提到每次使用Singleton()或MyClass()时,尽管仅创建了一个对象,init()也会被调用。我没有尝试过,但据我所知,其他方法也是如此。 当实现单例模式时,这似乎并不理想,难道我漏掉了什么吗?当然,解决方案是设置属性以避免两次执行__init__。只是好奇。 - chrisvp
显示剩余30条评论
42个回答

0

使用未装饰的静态方法

我不知道我是否漏掉了什么,但是使用未装饰的静态方法怎么样呢?

参考在Python中声明静态方法是否需要@staticmethod装饰器?Python中@staticmethod与@classmethod的区别,似乎在这里不使用任何装饰器是期望的行为,因为这样的话在对象上调用会失败,这对于单例模式来说是理想的:

  • 不需要实例化单例
  • 不需要调用get_instance()
  • 只需使用单例的名称,例如在这种情况下是Day
class Day():
  # Class variable
  dow = {
    0: 'Sun',
    1: 'Mon',
    2: 'Tue',
    3: 'Wed'
  }

  # Called when a class is instantiated as an object
  def __init__(self):
    raise 'You should not instantiate a Singleton'

  # # Should not define instance methods
  # def instance_method(self):
  #   return self
    
  # static method, undecorated
  #   works when called from class
  #   fails when called from object (because it is called with 'self') and this failure is desired
  def day(d):
    day= 'X' # Not found
    try:
      day =  Day.dow[d]
    except:
      pass
    return day
  
  def add_day():
    for d in Day.dow:
      Day.dow[d] = f'{Day.dow[d]}day'


def main():
  print('Static Method, undecorated')
  print('--------------------------')
  day = Day.day(1) # Call static method of singleton
  print(f'day={day}')

  Day.add_day()    # Call another static method of singleton to change its state and messup the names of the days

  day = Day.day(1) # Call static method of singleton after state change
  print(f'day={day}')

  # obj = Day()    # Do not instantiate singleton as object

  print('\nClass variable')
  print('--------------')
  print(f'dow={Day.dow}')

if __name__ == '__main__':
    main()


这给我提供了所期望的结果。
Static Method, undecorated
--------------------------
day=Mon
day=Monday

Class variable
--------------
dow={0: 'Sunday', 1: 'Monday', 2: 'Tueday', 3: 'Wedday'}

-2

这个解决方案在模块级别引入了一些命名空间污染(三个定义而不仅仅是一个),但我觉得很容易理解。

我希望能够像这样写一些代码(惰性初始化),但不幸的是,在类自身的定义体中无法使用类。

# 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__ = Foo
        return 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_instance
        if foo_instance is None:
            foo_instance = FooMaker()
        return foo_instance


foo_instance = None

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接