使用new构造单例模式

Python中的new? 单例模式?

Posted by Sylvia on March 9, 2018
1. self 和 cls

首先来简要介绍一下类中的self和cls,如下栗:

class A(object):
   def foo1(self):
      print("Hello", self)

   @classmethod
   def foo2(cls):
      print("Hello", cls)

调用foo1:

>>>a = A()
>>>a.foo1()
Hello <__main__.A object at 0x1040263c8>
>>>print(a)
Hello <__main__.A object at 0x1040263c8>

可以发现self和实例a指向了同一个对象,再调用类方法foo2:

>>>A.foo2()
Hello <class '__main__.A'>

发现此时指向的就是A类本身,从这个栗子我们可以得出,self指向了实例化对象而cls指向了类本身。

2. __init____new__

我们再来看一下__init__,相信大家对__init__已经很熟悉了,__init__通常用于初始化一个类实例,如下例:

class A(object):
   def __init__(self, x, y):
       self.x = x
       self.y = y

由于它是初始化一个类实例的,所有需要传入self。

那么__new__方法又是做什么用的呢?看下面的栗子:

class A(object):
    def __init__(self, x, y):
        print('__init__ has been called')
        self.__x = x
        self.__y = y

    def __new__(cls, *args, **kwargs):
        print('__new__ has been called')
        return super(A, cls).__new__(cls, *args, **kwargs)

    def __str__(self):
        return 'A : x is {}, y is {}'.format(self.__x, self.__y)

这里有个小插曲,当我尝试实例化A类,并将其传入参数x,y时,报了以下错误:

>>>tt=A('xxx','yyy')

in __new__
    return super(A, cls).__new__(cls, *args, **kwargs)
TypeError: object() takes no parameters

仔细阅读报的错,发现是由于__new__返回的是父类实例化后的对象的—__new__方法,而父类object__new__方法不接受任何参数,可我却对其传了参。解决方法就是不对父类实例化的对象的__new__方法传参,将上述__new__方法改成如下:

    def __new__(cls, *args, **kwargs):
        print('__new__ has been called')
        return super(A, cls).__new__(cls)

再次运行

>>>tt=A('xxx','yyy')
>>>print(tt)

__new__ has been called
__init__ has been called
A : x is xxx, y is yyy

一切都平静了,可以看到,当实例化对象时,先是调用了__new__,然后才调用了__init__进行了初始化。其中__new__方法中可以return父类__new__出来的实例,也可以直接将其他类__new__出来的实例返回(但这就意义不大了)。
那可不可以调用自身的__new__来制造实例呢?当然不行,因为这会造成死循环(一定要避免return cls.__new__(cls))。

新式类(最终继承到object)开始实例化时,__new__会返回一个实例,然后该类的__init__方法作为构造方法回接收这个实例(即self)作为自己的第一个参数,然后依次传入__new__方法中接收的其它参数。

如果 __new__并未返回实例,那么当前类的__init__方法是不会被调用的。

3. 使用__new__构造单例模式

说了那么多,__new__方法到底有什么作用呢?接下来介绍一下如何使用它来创建单例模式。

单例模式简单来说就是即一个类只有一个实例,看下面的栗子:

class A(object):
    _a = {}

    def __new__(cls, *args, **kwargs):
        ob = super(A, cls).__new__(cls)
        ob.__dict__ = cls._a
       
        return ob

实例化A,赋给a,并向_a添加新的键值对:

>>>a = A()
>>>a._a['a'] = 1

再次实例化A,赋给b,查看b的__dict__

>>>b = A()
>>>print(b.__dict__)
{'a':1}
>>>print(b.a)
1

居然有先前实例a的属性,而且居然还可以直接调用! 我们将实例b也赋予一些属性,并尝试通过实例a去调用:

>>>b.b=2
>>>print(a.b)
2

仿佛实例a与实例b成了同一个实例,只是有了两个名字!这就实现了单例模式。
我们来看一下上面的代码,__dict__是用来存储对象属性的一个字典,在A类中我们创建了一个空字典_a并将其传入__dict__,由于字典恰好是可变对象,所以当我们对任何A的实例添加属性进入__dict__时,_a也进行了更新,可以说_a中保存了最新的所有的A对象的属性键值对。将_a赋给实例的__dict__,每个实例也就拥有了同一个__dict__,真是神奇。

于此类似的,利用__new__构建单例模式还有一种方法,如下栗:

class Singleton(object):
    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
        return cls._instance

class MyClass(Singleton):
    a = 1

具体的实现原理就不做赘述啦,相信读到这里你一定可以理解~