欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python单例模式已经实现上的一些坑(语法层面)

程序员文章站 2022-06-03 19:00:56
...

原理及代码实现

实现环境:Python3.7,也就是说定义类的时候会默认继承object类的

原理

Python在实例化对象时会先调用__new__方法,所以在那里拦截住就行。

实现

经过一番百度,找到实现:

class Singleton1:
    def __init__(self, a, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.a = a

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton1, cls).__new__(cls)
        return cls._instance

挖坑与跳坑

看到上述代码,手有点痒,所以就改了一下,那个__new__里面能不能传递*args,**kwargs呢,说做就做,

class Singleton1:
    def __init__(self, a, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.a = a

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton1, cls).__new__(cls, *args, **kwargs)
        return cls._instance

然而现实是:
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
错误的原因是其父类object__new__方法只有一个参数。于是我特地去看了下,是这么写的:

@staticmethod # known case of __new__
def __new__(cls, *more): # known special case of object.__new__
     """ Create and return a new object.  See help(type) for accurate signature. """
        pass

我感到很疑惑,因为学习单例模式时我是用pycharm写的代码,我敲__new__后会提示有def __new__(cls, *args, **kwargs),这就真奇怪了,难道pycharm提示错了?感觉可能性微乎其微。又经过一番百度,可以这样写

class Singleton1:
    def __init__(self, a, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.a = a

    def __new__(cls, a, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton1, cls).__new__(cls, *args, **kwargs)
        return cls._instance

如果debug一下可以发现,你在实例化的时候,比如
s = Singleton1(1, 2)
会将参数全部带到__new__中去,从而导致object.__new__() takes exactly one argument这个错误,而如果将__init__的参数也传到__new__中去,则不会有这种问题。比如这样:

class Singleton1:
 def __init__(self, a, b, c, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.a = a
     self.b = b
     self.c = c

 def __new__(cls, a, b, c, *args, **kwargs):
     if not hasattr(cls, '_instance'):
         cls._instance = super(Singleton1, cls).__new__(cls, *args, **kwargs)
     return cls._instance


single = Singleton1(2, {1, 2}, {1: 2})

但是上面好像也说的过去,我又进行了下一步操作,即拷贝单例模式

from copy import deepcopy
class Singleton1:
    def __init__(self, a, b, c, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.a = a
        self.b = b
        self.c = c

    def __new__(cls, a, b, c, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton1, cls).__new__(cls, *args, **kwargs)
        return cls._instance


single = Singleton1(2, {1, 2}, {1: 2})

f = deepcopy(single)

结果是报错,信息如下,这里只选取了异常栈信息中真正错误的原因:

File "C:\Program Files\Python37\lib\copyreg.py", line 88, in __newobj__
    return cls.__new__(cls, *args)
TypeError: __new__() missing 3 required positional arguments: 'a', 'b', and 'c'

原来是deepcopy调用了cls.__new(cls, *args)造成的错误,而我们这里的单例模式导致了这里参数传递的不统一。所以还是建议使用文章开头的那种方式创建单例模式。我们来看看使用开头那种方法创建单例模式并用deepcopy的效果:

from copy import deepcopy
class Singleton1:
    def __init__(self, a, b, c, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.a = a
        self.b = b
        self.c = c

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton1, cls).__new__(cls)
        return cls._instance


single = Singleton1(2, {1, 2}, {1: 2})
copyed = deepcopy(single)
print(id(copyed) == id(single))  # True

不过话说回来,如果不希望单例模式创建的对象被deepcopy,还是使用后来的方式吧。

有趣的事

class Singleton1:
    def __init__(self, a, b, c, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.a = a
        self.b = b
        self.c = c

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = Singleton1(1, 2, 3)
        return cls._instance

single = Singleton1(2, {1, 2}, {1: 2})

错误是
RecursionError: maximum recursion depth exceeded

参考资料

相关标签: 夏季蚊子咬