美文网首页python自学
[Python] 使用new构造单例模式

[Python] 使用new构造单例模式

作者: 敲代码的密斯想 | 来源:发表于2018-03-09 11:42 被阅读27次
    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
    

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

    相关文章

      网友评论

        本文标题:[Python] 使用new构造单例模式

        本文链接:https://www.haomeiwen.com/subject/faspfftx.html