python 什么场景使用python的__new__魔法方法,以及__new__作用和原理

1. __new__是什么

__new__是python内置的魔术方法,并且是一个类方法,作用也很简单,就是在python的类进行实例化的时候,创建实例。并且__new__的执行是一定在__init__之前的

class T:
    def __new__(cls, *args, **kwargs):
        print("__new__ is executing")
        return super().__new__(cls)

    def __init__(self):
        print("__init__ is executing")
        self.a = 1
>> t = T()
__new__ is executing
__init__ is executing

所以可以在构造函数__init__运行之前,可通过__new__方法对实例的创建进行一些改造。

2. __new__的使用

2.1 参数

cls

先来讲第一个参数,也是最重要的参数:
__new__的第一个参数一定是会传递需要被实例化的类,这是在python底层就写死了的。通常来说,其约定俗成的参数名是cls

熟悉类方法修饰器@classmethod一定也熟悉这个约定俗成的参数名,在类方法修饰器修饰的函数中,第一个参数一般也是cls,代表类本身

所以,如果你要把super().__new__(cls)中的cls变量换成类名本身,当然也是没问题的;

class T:
    def __new__(cls, *args, **kwargs):
        print("__new__ is executing")
        # 这里的 T 即是该类的类名,等同于这里的cls,即类本身
        return super().__new__(T)

    def __init__(self):
        print("__init__ is executing")
        self.a = 1

同样,因为clsself一样,是约定俗成的写法,而非python的特殊保留字,而仅仅是代表了实例,所以把cls的参数名换成其他乱七八糟的参数名称也是可以正常执行的,但是这样的写法不符合python代码的书写规范,极其不建议这样违背常规的写法。

class T:
    def __new__(abc, *args, **kwargs):
	    """
	    !!切勿模仿该参数的命名!!
	    ”“”
        print("__new__ is executing")
        return super().__new__(abc)

    def __init__(self):
        print("__init__ is executing")
        self.a = 1

如上2种写法的执行均可正常运行,如下:

>> t = T()
__new__ is executing
__init__ is executing

*args, **kwargs

这俩参数应该都很熟悉了,和传递到__init__的参数是相同的,所以需保持一致

class T:
    def __new__(cls, *args):
        print("__new__ is executing")
        print("__new__ args: " + str(args))
        return super().__new__(cls)

    def __init__(self, *args):
        print("__init__ is executing")
        print("__init__ args: " + str(args))

所以可以讲初始化参数在传递到__init__前对类实例化的参数进行预处理。

>> t = T(1,2,3)
__new__ is executing
__new__ args: (1, 2, 3)
__init__ is executing
__init__ args: (1, 2, 3)

2.2 返回值

覆写的__new__必须返回由object__new__方法创建的实例

    def __new__(cls, *args, **kwargs):
        print("__new__ is executing")
        return super().__new__(cls)

如上的super().__new__(cls),即是通过super()调用父类object的初始__new__方法来创建实例。

但是如果__new__方法没有返回实例,则该类不会执行__init____new__返回什么,该类就是什么;下面的例子返回的None,就相当于是一个空类型,即None本身(NoneType):

class T:
    def __new__(cls, *args, **kwargs):
        print("__new__ is executing")
        # return super().__new__(cls)
        return None

    def __init__(self):
        print("__init__ is executing")
        self.a = 1

    def whatever(self):
        print(self.a)
        print("done")

输出如下:

>> t = T()
__new__ is executing
>> type(t)
Out[20]: NoneType

3. __new__的使用场景

__new__有很多使用的场景,下面简单举2个例子:

单例模式

不清楚单例模式的盆友,可以看我的这篇文章 单例模式是什么以及如何创建单例模式

通过__new__方法,以及通过一个类变量,控制类的实例化,保证参数相同的实例,有且仅有1个实例对象,减少内存空间:


class T:
    INSTANCE_DICT = {}

    def __new__(cls, *args):
        if cls.INSTANCE_DICT.get(str(args), None):
            print(f"已存在已创建的参数为{args}的实例,直接返回该实例")
            return cls.INSTANCE_DICT[str(args)]
        else:
            print(f"还未创建过参数为{args}的实例,新建一个实例")
            cls.INSTANCE_DICT[str(args)] = super().__new__(cls)
            return cls.INSTANCE_DICT[str(args)]

    def __init__(self, a):
        self.a = a

    def test(self):
        print(self.a)

运行如下:

>> t1 = T(1)
还未创建过参数为(1,)的实例,新建一个实例
>> t3 = T(1)
已存在已创建的参数为(1,)的实例,直接返回该实例

>> t2 = T(2)
还未创建过参数为(2,)的实例,新建一个实例
>> t4 = T(2)
已存在已创建的参数为(2,)的实例,直接返回该实例

>> t1 == t2
False

>> t1 == t3
True

>> t4 == t2
True

>> t1.INSTANCE_DICT
{'(1,)': <__main__.T at 0x2912f5fdbe0>, '(2,)': <__main__.T at 0x2912f5fd860>}

这样就是单例模式了。

构建元类

元类是python里一切元素的基础,是构造类、修饰类的方法
元类的构建需要继承type类,并且需要用__new__方法去定义元类的构造。
这里针对元类的__new__会和普通类的__new__有所不同,这里不详细描述了,后面应该会专门开一篇讲元类。

class TestMeta(type):
    def __new__(mcs, *args, **kwargs):
        print(mcs.__dict__)
        print(type(mcs))
        return super().__new__(mcs, *args, **kwargs)


class T(object, metaclass=TestMeta):
    def __new__(cls, *args, **kwargs):
        print("new")
        print(type(cls))
        return super().__new__(cls)

    def __init__(self):
        print("init")
{'__module__': '__main__', '__new__': <staticmethod object at 0x000002912F510470>, '__doc__': None}
<class 'type'>
>> t = T()
new
<class '__main__.TestMeta'>
init