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

标签 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 的实例已经存在那么为什么 print 语句打印 num_of_instances 打印 2

最佳答案

每次 MyClass(...) 调用都会调用

__new__。如果没有被调用,它将无法返回单例实例。

__new__方法返回一个对象并且该对象是传递给__new__(或子类)的cls参数的实例时,那么 __init__ 方法也会被调用。

因此,对于每个 MyClass(...) 调用,都会调用 __new____new__ 方法始终返回当前类的实例,因此每次都会调用 __init__。这里每次都是同一个实例并不重要。

来自__new__ method documentation :

If __new__() returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self[, ...]), where self is the new instance and the remaining arguments are the same as were passed to __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__ (并保留所有内容),但让元类负责处理单例模式是有意义的:

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

关于python - 在Python中创建Singleton类并计算实例数量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52968491/

相关文章:

python - mysql-python : building a completely standalone _mysql. 所以在 Mac OS X 上?

python - 使用 pyPdf 打开 pdf url

python - 关闭 Snowflake DB 日志记录,同时仍将日志级别保持为 DEBUG

python - 纬度、经度和 RSSI 转 3D 图形

python - 如何使用 Django 渲染局部?

python - "import"语句如何确定在 Python 中导入了哪个文件?

python - 如何从 gtkMenu 中删除一个项目?

python - 如何用 BeautifulSoup 抓取页面?页面源不匹配检查元素

python - 覆盖 setup.cfg 选项以实现默认行为

python - 如何在 Keras 中将张量的 2D 子集分配给另一个 2D 张量?