Python创建对象之new/super/init都干了什么

其他 · 07-24 · 328 人浏览

Python中创建对象时,会涉及__init____new__两个函数,个人感觉前者较为常见。

创建对象时发生了什么

创建一个类对象首先会调用该类的__new__方法(接下来省略下划线),new方法本身接受一个类参数cls,一般就是自身,随后会通过super函数得到该类的父类,并调用父类的new方法进行对象的创建(因为所有类都基于object,所以最终都会调用object的new方法)

super函数(实际上是类,这里为了简明,使用相同功能的函数替代)的输入为类cls,和某个实例obj,super做的事情就是找到obj的父类并返回,这里我们使用python的mro函数得到obj对象对应类的父类列表。mro这个类方法是python解决多重继承问题的方案,简单理解就是mro会返回调用类的父类链,列表靠前的元素是靠后元素的子类。那么super的做法就是得到obj对象类的mro列表,取出mro列表中cls的后一个元素,也就是cls的最近父类。

def super(cls, obj):
    mro = obj.__class__.mro()
    return mro[mro.index(cls) + 1]

当然,super函数的输入也不一定是obj,比如super(cls, obj_cls),因为我们想在new函数中调用父类的方法,此时对象还没创建,所以只能传obj对应的类。对应的操作无非就是通过obj_cls.mro()来得到父类列表了。

注1:super函数既适用于实例也适用于类本身,例如super(cls, cls)返回的是cls的父类,而super(cls, cls_obj)返回的是一个cls的父类实例;两者的区别在于调用init或其他实例方法时是否要传入self,这个细节可以看接下来的代码注释

类实例创建完成后,就会调用该实例的init方法进行初始化,init函数至少有一个参数self,这就指向之前new方法创建出来的对象。

一段例子

以一段代码为例,首先定义class B,父类是object(缺省也可以,从python3开始默认的基类就是object);定义class A,父类是B;A和B都各自重写了new/init两个函数。接下来创建A对象,结果见注释。

class B(object):
    def __new__(cls):
        print("B.__new__ called")
        return super(B, cls).__new__(cls)

    def __init__(self):
        pass

class A(B):

    def __new__(cls):
        print("A.__new__ called")
        print(cls.mro())
        return super(A, cls).__new__(cls)

    def __init__(self):
        print("A.__init__ called")
        super(A, self).__init__() # 注1:如果super传入的不是实例self,而是类对象本身,那么这行代码应该写成 super(A, A).__init__(self)

A()

# output
# A.__new__ called
# [<class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
# B.__new__ called
# A.__init__ called

结果解释:

  • 首先A()会调用A本身的new方法,从而打印出了A.__new__ called
  • 随后super(A, cls)返回了cls的父类,实际上这里的cls也就是A,写成super(A, A)或者super(cls, cls)都可以
    • 得到父类的方法是:首先调用cls的mro类方法,返回父类列表lst,lst中定位到第一个参数A的位置,返回下一个元素。通过打印A.mro()可以知道,A的下一个元素是B,所以super(A, cls)返回了class B
  • 接下来调用super(A, cls).__new__(cls),即B.__new__(cls),因此输出B.__new__ called
  • 同理,super(B, cls).__new__(cls)等同于object.__new__(cls),也就是一切对象创建的起点,然后嵌套(嵌套这里表达欠妥,可见注3)返回,此时我们得到一个创建出来的A对象a
  • 最后这个对象a会调用自身的init方法,即a.__init__(a),从而输出A.__init__ called

注2:以上代码中很多可以省略,例如__init__(self)中的self,super(A, cls)中的A和cls,这里写出来是为了更好的解释每个函数的作用

在菱形继承中,init调用顺序更加复杂,但本质上还是和super函数涉及到的mro列表有关,可见以下代码

class A(object):
    def __init__(self):
        print("A init")
        super().__init__()

class B(A):
    def __init__(self):
        print("B init")
        print("B's mro: " + str(B.__mro__))
        print("self's mro: " + str(type(self).__mro__))
        super().__init__() # 根据self实例对应类的mro列表调用init函数,如果这里使用super(B, B).__init__(),就会跳过C的实例化

class C(A):
    def __init__(self):
        print("C init")
        super().__init__()

class D(B, C):
    def __init__(self):
        print("D init")
        super().__init__()

d = D()

# output
# D init
# B init
# B's mro:(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
# self's mro:(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
# C init
# A init

撒花,mro相关内容可能之后会更吧(

  1. Axuanz (作者)  08-06

    这里说的其实不太清楚,把类对象换成实例会好一点(毕竟类本身也是一个对象

Theme Jasmine by Kent Liao