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

方法一 直接重写构造方法

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

python构造函数重载 python构造类的方法_实例化

实例代码

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 
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 
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)

输出

{'__module__': '__main__', 'sex': 1, '__init__': , '__new__': , '__dict__': , '__weakref__': , '__doc__': None}

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

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

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

方法二 使用元类

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

python构造函数重载 python构造类的方法_python中类的构造方法_02

此时的代码示例如下

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 ->

元类构造方法 name -> Person

元类构造方法 bases -> (,)

元类构造方法 attrs_dict -> {'__module__': '__main__', '__qualname__': 'Person', 'sex': 1, '__init__': }

实例化Person

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

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