[Python] 使用new构造单例模式
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
具体的实现原理就不做赘述啦,相信读到这里你一定可以理解~