在Python中创建Singleton类并计算实例数

时间:2018-10-24 12:00:41

标签: python

我试图了解如何在Python中创建Singleton类。以下是我的尝试

class Singleton(object):
    _instance = None

    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_)
        return class_._instance

class MyClass(Singleton):
    num_of_instances = 0
    def __init__(self, real = 5, imaginary = 6):
        self.real = real
        self.imaginary = imaginary
        MyClass.num_of_instances += 1

a = MyClass(10, 20)
print(a.real)
print(a.imaginary)
b = MyClass()

print(MyClass.num_of_instances)  # 2

理想情况下,__new__()使用对象实例调用__init__(),但是在上述情况下,当我尝试创建第二个对象b时,不会调用__new__,因为MyClass的实例已经存在,那么为什么打印num_of_instances的打印语句打印2

1 个答案:

答案 0 :(得分:4)

对于每个__new__呼叫,

MyClass(...)被称为 。如果没有被调用,它将无法返回单例实例。

并且当__new__方法返回一个对象并且该对象是传递给cls(或子类)的__new__参数的实例时,则__init__方法也称为。

因此,对于每个MyClass(...)调用,都会调用__new____new__方法始终返回当前类的实例,因此每次都调用__init__。没关系,每次都是同一实例。

来自__new__ method documentation

  

如果__new__()返回cls的实例,则新实例的__init__()方法将像__init__(self[, ...])一样被调用,其中self是新实例,其余参数与传递给__new__()的参数相同。

如果在方法中添加一些print()调用,您会看到这种情况:

>>> class Singleton(object):
...     _instance = None
...     def __new__(class_, *args, **kwargs):
...         print(f'Calling {class_!r}(*{args!r}, **{kwargs!r})')
...         if not isinstance(class_._instance, class_):
...             print(f'Creating the singleton instance for {class_!r}')
...             class_._instance = object.__new__(class_)
...         return class_._instance
...
>>> class MyClass(Singleton):
...     num_of_instances = 0
...     def __init__(self, real=5, imaginary=6):
...         print(f'Calling {type(self)!r}.__init__(self, real={real!r}, imaginary={imaginary!r})')
...         self.real = real
...         self.imaginary = imaginary
...         MyClass.num_of_instances += 1
...
>>> a = MyClass(10, 20)
Calling <class '__main__.MyClass'>(*(10, 20), **{})
Creating the singleton instance for <class '__main__.MyClass'>
Calling <class '__main__.MyClass'>.__init__(self, real=10, imaginary=20)
>>> b = MyClass()
Calling <class '__main__.MyClass'>(*(), **{})
Calling <class '__main__.MyClass'>.__init__(self, real=5, imaginary=6)

您不能阻止自动__init__调用,至少不能不重写其他内容。如果您想避免每次__init__被调用,则可以选择以下几种方式:

您不必在子类上使用__init__方法。您可以发明自己的机制,__new__可以寻找__singleton_init__方法并调用:

class Singleton(object):
    _instance = None

    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_)
            if hasattr(class_._instance, '__singleton_init__'):
                class_._instance.__singleton_init__(*args, **kwargs)`
        return class_._instance

或您的__init__方法可以检查vars(self)(或self.__dict__)中是否已经设置了属性,而不再设置属性:

class MyClass(Singleton):
    def __init__(self, real=5, imaginary=6):
        if vars(self):
            # we already set attributes on this instance before
            return
        self.real = real
        self.imaginary = imaginary

__new____init__逻辑是在type.__call__中实现的;您可以创建一个覆盖该逻辑的metaclass。虽然您仅可以仅调用__new__(并保持所有状态不变),但使元类负责处理Singleton模式很有意义:

class SingletonMeta(type):
    def __new__(mcls, *args, **kwargs):
        cls = super().__new__(mcls, *args, **kwargs)
        cls._instance = None
        return cls

    def __call__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__call__(*args, **kwargs)
        return cls._instance

然后将其用作metaclass=...而不是基类。如果更简单,您可以创建一个空的基类:

class Singleton(metaclass=SingletonMeta):
    pass

class MyClass(Singleton):
    # ...

以上代码将在类上调用__new__,然后在结果实例上调用__init__,只需一次。然后,SingletonMeta.__call__实现永远在没有进一步调用的情况下返回单例实例:

>>> class SingletonMeta(type):
...     def __new__(mcls, *args, **kwargs):
...         cls = super().__new__(mcls, *args, **kwargs)
...         cls._instance = None
...         return cls
...     def __call__(cls, *args, **kwargs):
...         print(f'Calling {cls!r}(*{args!r}, **{kwargs!r})')
...         if cls._instance is None:
...             cls._instance = super().__call__(*args, **kwargs)
...         return cls._instance
...
>>> class Singleton(metaclass=SingletonMeta):
...     pass
...
>>> class MyClass(Singleton):
...     def __init__(self, real=5, imaginary=6):
...         print(f'Calling {type(self)!r}.__init__(self, real={real!r}, imaginary={imaginary!r})')
...         self.real = real
...         self.imaginary = imaginary
...
>>> a = MyClass(10, 20)
Calling <class '__main__.MyClass'>(*(10, 20), **{})
Calling <class '__main__.MyClass'>.__init__(self, real=10, imaginary=20)
>>> MyClass()
Calling <class '__main__.MyClass'>(*(), **{})
<__main__.MyClass object at 0x10bf33a58>
>>> MyClass() is a
Calling <class '__main__.MyClass'>(*(), **{})
True
>>> MyClass().real
Calling <class '__main__.MyClass'>(*(), **{})
10