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__
方法创建的name
和age
属性,这也符合预期:__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__
方法。换言之,元类是构造类的方法。