Python单例类可以被继承吗?

5

我的想法是单例类的所有子类都应该存在任何一个对象。 我一直在尝试的代码和结果矩阵如下所示。在子类的情况下,矩阵似乎运行良好。我是不是走错了方向?在父类对象和子类对象的情况下,它发生了什么?

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(
                                cls, *args, **kwargs)
        return cls._instance

class A(Singleton):
    def __new__(cls, *args, **kwargs):
        super(A, cls).__new__(cls, *args, **kwargs)

class B(Singleton):
    def __new__(cls, *args, **kwargs):
        super(B, cls).__new__(cls, *args, **kwargs)

class C(B):
    def __new__(cls, *args, **kwargs):
        super(B, cls).__new__(cls, *args, **kwargs)


if __name__ == '__main__':
    s1=Singleton()
    s2=Singleton()
    if(id(s1)==id(s2)):
        print "Same"
    else:
        print "Different"

'''
I got a result matrix for s1 and s2
           |************ s2 **************************|           
s1         |Singleton() |A()      | B()     | C()     |
===========|==========================================|
Singleton()|Same        |Different|Different|Different|
A()        |Different   |Same     |Same     |Same     |
B()        |Different   |Same     |Same     |Same     |
C()        |Different   |Same     |Same     |Same     |
'''

2
单例模式本质上等同于全局变量,只是通过额外的代码混淆了这一事实,而这些代码可能会出现微妙的错误。为什么不直接使用 class A(object): ... 然后立即创建一个全局实例 a = A() 呢?你将获得一个看起来像单个全局实例的单个全局实例,并且它在完全相同的作用域中可用。如果您想要懒惰初始化,有方法可以实现这一点,看起来就像您正在执行此操作。 - Ben
1
如果你修复了子类没有返回任何内容的错误,那么你会得到非常奇怪的行为。 如果你执行s1=Singleton()然后s2=A(),那么它们都将是同一个对象(Singleton的实例),但如果你执行s1=A()然后s2=Singleton(),那么它们将是不同的对象(分别是ASingleton的实例)。 - Ben
这意味着在程序的某个远程部分,A().method_of_a()将根据Singleton是否已经实例化而工作、抛出异常(或莫名其妙地调用超类实现)。我无法想象这可能是期望的行为;您打算如何处理这个单例类层次结构? - Ben
这给了我一些启示。我一直试图避免为每个子类显式检查是否存在任何实例(全局实例)。现在我想对每个子类进行这样的检查是必要的。 - user1061147
一个可以被子类化的单例本质上是自相矛盾的。你要么违反使其成为单例的类不变量,要么违反LSP原则。 - Karl Knechtel
3个回答

4

所有子类的 __new__() 方法都没有显式的 return 语句,因此它们都返回 None。这就是为什么它们的新实例都是相同的原因。使用此代码,它们也将与 Singleton() 相同:

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance

class A(Singleton):
    def __new__(cls, *args, **kwargs):
        return super(A, cls).__new__(cls, *args, **kwargs)

class B(Singleton):
    def __new__(cls, *args, **kwargs):
        return super(B, cls).__new__(cls, *args, **kwargs)

class C(B):
    def __new__(cls, *args, **kwargs):
        return super(C, cls).__new__(cls, *args, **kwargs)

不需要为子类定义__new__()方法:
class A(Singleton):
    pass

class B(Singleton):
    pass

class C(B):
    pass

2
请注意,使用此代码如果我直接实例化Singleton(而不是通过子类),那么它会在Singleton上设置_instance。这意味着所有后续被实例化的子类都将在其MRO中找到一个非Nonecls._instance,并返回该实例,而不是创建自己的实例。 - Ben
1
我理解这是所期望的行为。 - Michael Hoffman
我怀疑,虽然我认为缺陷确实在设计中而不是你的代码中。我想不出一个合理的情况,其中这种行为会有用,而不是一个错误。我认为发帖者可能并不打算直接实例化“Singleton”(或创建进一步的“Singleton”子类)。 - Ben
1
OP说,期望的行为是id(s1)== id(s2),并且当s1s2都是子类时,“运行良好”,但其中一个是Singleton而另一个是子类时就不行了。由于测试代码包括直接实例化Singleton,因此很难看出这不是预期的。就设计而言,正如您所指出的,强制实施单例的类在Python中既不必要也不是一个好主意。但我只是在这里工作。 - Michael Hoffman
哦!对不起。你说得很对,我没有仔细阅读。那看起来很荒谬。 - Ben
如何在类的__init__需要参数的情况下正确扩展它?目前这种方法会失败。 - Gulzar

3
代码看起来不错,但是选择一个单例值共享给一个类及其子类是没有意义的。子类存在的意义在于与父类和兄弟子类有所不同。每个类都应该有自己独特的单例实例,这种做法让我感到奇怪。

0

我通过抽象类解决了这个问题。我的代码如下:

from abc import ABCMeta, abstractmethod


class BasicClass(object):
    __metaclass__ = ABCMeta

    instance = None

    def __new__(cls, *args, **kwargs):
        if not cls.instance:
            cls.instance = super(BasicClass, cls).__new__(
                                    cls, *args, **kwargs)

        return cls.instance

    @abstractmethod
    def action(self):
        pass#do smthing


class FirstLevelChild(BasicClass):
    __metaclass__ = ABCMeta

    @abstractmethod
    def action(self):
        pass#do smthing in this or other abstract method
        # also pearent method can be called with help of super

class SecondLevelChild1(FirstLevelChild):
    def action(self):
        pass#do smthing unique for this class
        # in this or other abstract method
        # also pearent method can be called with help of super

class SecondLevelChild2(FirstLevelChild):
    def action(self):
        pass#do smthing unique for this class
        # in this or other abstract method
        # also pearent method can be called with help of super

让我们来检查一下

>>> a1 = SecondLevelChild1()
>>> a2 = SecondLevelChild1()
>>> b1 = SecondLevelChild2()
>>> b2 = SecondLevelChild2()
>>> a1 == b1
False
>>> a1 ==a2
True
>>> b1 == b2
True
>>> 

注意!如果您需要实例化仅最后一级子类,则此解决方案将有所帮助。您将无法实例化抽象类。


FirstLevelChild 真的需要将 ABCMeta 作为其元类吗?如果我们移除它,我相信语句 a1 == b1 将变为 True。 - Krishna Oza

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