Python3构造方法__new__在普通类和元类中的差别

最近想自己写一个异步ORM框架,在构造方法遇到了几个问题,记录一下。Python中创建一个对象,会调用__new__方法,通常情况下我们是不需要定义这个方法的,会随着继承一路调用object类的__new__方法,如果想对这个实例对象做一些额外的处理,可以重写这个方法。

方法一 直接重写构造方法

直接在定义的类中重写__new__方法,此时我们实例化一个对象的流程为:

实例代码

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __new__(cls, *args, **kwargs):
        print('构造方法 args ->', args)
        print('构造方法 kwargs ->', kwargs)
        return super().__new__(cls)


p1 = Person('张三', 18)
p2 = Person(name='李四', age=21)
p3 = Person()

输出如下:

构造方法 args -> ('张三', 18)
构造方法 kwargs -> {}
构造方法 args -> ()
构造方法 kwargs -> {'name': '李四', 'age': 21}
构造方法 args -> ()
构造方法 kwargs -> {}
Traceback (most recent call last):
  File "/Users/sw/test.py", line 122, in <module>
    p3 = Person()
TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'

可以看到在调用构造方法__new__时,我们初始化参数也一并传入了__new__,随后再将参数传入__init__方法。但是我们在不传入任何参数实例化p3时__new__方法并没有报错,表明__new__方法是不依赖初始化参数的,我们在调用父类方法return super().__new__(cls)时也仅仅传入了cls。

如果将__new__方法改成

    def __new__(cls):
        # print('构造方法 args ->', args)
        # print('构造方法 kwargs ->', kwargs)
        return super().__new__(cls)

此时依旧实例化上面的三个对象,会直接报错

Traceback (most recent call last):
  File "/Users/sw/test.py", line 122, in <module>
    p1 = Person('张三', 18)
TypeError: __new__() takes 1 positional argument but 3 were given

这表明虽然__new__方法默认不使用初始化参数,但它必须要有一个载体来传递初始化参数。

接下来看看__new__中的cls这个变量是什么东西,对代码做一点点修改

class Person(object):
    sex = 1

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __new__(cls, *args, **kwargs):
        print(cls)
        print(type(cls))
        print(cls.__dict__)
        return super().__new__(cls)


p1 = Person('张三', 18)

输出

<class '__main__.Person'>
<class 'type'>
{'__module__': '__main__', 'sex': 1, '__init__': <function Person.__init__ at 0x10b01aae8>, '__new__': <staticmethod object at 0x10b04e320>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}

第一行表明cls变量是一个Person类,第二行表明cls属于type类,类的类是type没毛病。

第三行打印了cls的属性字典,可以发现包含我们定义的sex属性,但没有__init__方法创建的nameage属性,这也符合预期:__new__方法优先于__init__方法调用。

所以如果我们要在__new__方法中对类的属性执行一些操作,可以使用cls.__dict__

方法二 使用元类

使用元类其实也是重写__new__方法,对于实例化对象来说流程和方法一是一样的,但是实现元类中的__new__和方法一是存在明显区别的,使用元类的流程:

此时的代码示例如下

class MetaClass(type):

    def __new__(cls, name, bases, attrs_dict):
        print('元类构造方法 cls ->', cls)
        print('元类构造方法 name ->', name)
        print('元类构造方法 bases ->', bases)
        print('元类构造方法 attrs_dict ->', attrs_dict)
        # 此处 type.__new__ == super().__new__
        return type.__new__(cls, name, bases, attrs_dict)


print('定义Person类')
class Person(object, metaclass=MetaClass):
    sex = 1

    def __init__(self, name, age):
        self.name = name
        self.age = age


print('实例化Person')
p1 = Person('张三', 18)
p2 = Person(name='李四', age=21)

输出

定义Person类
元类构造方法 cls -> <class '__main__.MetaClass'>
元类构造方法 name -> Person
元类构造方法 bases -> (<class 'object'>,)
元类构造方法 attrs_dict -> {'__module__': '__main__', '__qualname__': 'Person', 'sex': 1, '__init__': <function Person.__init__ at 0x104aa6ae8>}
实例化Person

先从代码来看,最明显的区别是__new__方法传入的参数固定为4个,传入的参数意义和内置方法type是一致,name参数为要创建的类名,bases参数是一个所继承的父类组成的元组,attrs_dict则是一个类属性组成的字典。而在方法一定义的__new__方法,传入参数的数量实际上是由__init__方法接受的参数数量决定的。其次cls参数变成了元类MateClass本身,而不是Person类,这意味不能使用变量cls来获取Person类的属性,而要使用attrs_dict

再来观察输出,我们发现当我们实例化对象p1、p2时根本没有任何输出,那么输出的信息显然是我们在定义Person类中打印的,这表明使用元类时,只有在定义类时才会调用__new__方法,而实例化类对象时不会调用__new__方法。换言之,元类是构造类的方法

posted @ 2020-06-23 12:54  秋叶红了  阅读(693)  评论(0编辑  收藏  举报