python 魔法方法(二): __new__和__init__
python
本文字数:2.1k 字 | 阅读时长 ≈ 8 min

python 魔法方法(二): __new__和__init__

python
本文字数:2.1k 字 | 阅读时长 ≈ 8 min

本篇需要读者提前了解 self 和 cls,这里简述一下:cls 是类的引用,self 是实例化后的对象的引用,self 和 cls 都是约定俗成的,可以用其他的代替,但是不建议,因为这样会让人困惑,不知道这个参数是什么意思,所以我们还是遵循约定俗成的规则,使用 self 和 cls

1. __new__

__new__

在 python 中,object 是所有类的父类,即所有类都继承自 object。__new__则由 object 基类提供的内置静态方法,用于创建对象并返回对象,是一个类方法,第一个参数是 cls,表示当前类,其余参数是用来直接传递给__init__方法的参数。

一句话总结 __new__ 方法的作用:为对象分配空间并返回对象引用

2. __init__

__init__

__init__ 是一个实例方法,用于初始化对象,即为对象的成员变量赋值,第一个参数是 self,表示当前对象,其余参数是用来接收__new__方法传递的参数

一句话总结 __init__ 方法的作用:为初始化对象并其成员变量赋值

__new____init__ 一般要联合使用,下面我们通过一个例子来看一下其使用方法

class Student(object):

    def __new__(cls, *args, **kwargs):
        print("this is new 方法")
        return super().__new__(cls)

    def __init__(self):
        print("this is init 方法")


stu = Student()
print(stu)

'''
this is new 方法
this is init 方法
<__main__.Student object at 0x7f9312f9a740>
'''

上面例子中我们复写了 __new__ 方法,并且上述例子就是类的正确的实例化过程。下面我们分析一下类实例构建的底层逻辑,加深理解类实例的构造过程

我们有一个 Student 类,当我们执行 stu = Student() 时,会首先调用 Student 类中的 __new__ 方法,他的第一个参数为 cls,表示当前类 Student,其余的为初始化参数,如之前所述,__new__ 方法的作用有两个,为对象分配空间,返回对象引用,首先为当前类分配了一块空间来实例化他,然后将其返回,返回到哪里呢?返回到 __init__ 方法中,__init__ 方法的作用是初始化对象,为其成员变量赋值__init__ 方法的第一个参数是 self,表示当前实例化后的对象即 __new__ 方法返回的实例化后的 Student 对象,其余参数是用来接收 __new__ 方法传递的参数,__init__ 方法执行完毕后,返回 Student 对象的引用,赋值给 stu,这样就完成了类的实例化过程

带类实例属性的实例化过程

下面展示了一个带类实例属性的实例化过程,和上一个例子的区别就是在 __init__ 函数中加入了 name 变量

class Student(object):

    def __new__(cls, *args, **kwargs):
        print("this is new 方法")
        return super().__new__(cls)

    def __init__(self, name):
        self.name = name
        print("this is init 方法")
        print("Student's name is: ", self.name)


stu = Student("harry")
print(stu)

'''
this is new 方法
this is init 方法
Student's name is:  harry
<__main__.Student object at 0x7f529854a770>
'''

上述程序执行过程

3. super()方法

3.1 super 语法

super(type[, object-or-type])

super 是一个类,实例化时即实例化 super 类

在 python3 中,在类中可以直接使用 super().xxx 代替 super(Class, self).xxx。但是在多继承中,super()内的参数还是有用的,后两节会详细介绍

继续上述的例子,super() 用于调用父类的方法,这里我们使用 super() 方法调用父类的 __new__ 方法,super() 方法的作用是返回当前类的父类,这里我们的父类是 object,所以 super() 方法返回的是 object 类,然后调用 object 类的 __new__ 方法,而 __new__ 方法的作用是为对象分配空间并返回对象引用,所以 super().__new__(cls) 的作用是为 Student 类分配空间并返回 Student 对象的引用

上述例子就是 super 在单继承中的作用,下面我们分别看一下在单继承和多继承中的作用

3.2 super 在单继承中的作用

本例子均来源于菜鸟 Python

class A:
    def __init__(self):
        self.n = 2

    def add(self, m):
        print('self is {0} @A.add'.format(self))
        self.n += m


class B(A):
    def __init__(self):
        self.n = 3

    def add(self, m):
        print('self is {0} @B.add'.format(self))
        super().add(m)
        self.n += 3

b = B()
b.add(2)
print(b.n)

'''
self is <__main__.B object at 0x7fdc45a3be50> @B.add
self is <__main__.B object at 0x7fdc45a3be50> @A.add
8
'''

上述例子中,B 类继承了 A 类,B 类中的 add 方法中调用了 super().add(m)(如果是 python2 需要改为 super(B, self).add(m))。首先实例化了 B 类,然后调用 B 类中的 add 函数,运行到 super().add(m) 时,会调用父类中的 add 函数,而 super 中的 self 是 B 类的实例化对象,所以 self.n 的值是 3 而不是类 A 中的 2。n 值的最终计算过程为 self.n=3 加上在 A 类中的 self.n+2=5,然后回到 B 类中,self.n+3=8,所以最终的结果为 8

3.3 super 在多继承中的作用

在单继承过程中,我们直接调用 super() 方法是没有问题的,不需要加上额外的参数,但是涉及到多继承,会涉及到查找顺序(MRO)的问题,MRO 就是类方法的解析顺序表,即继承父类方法时的顺序表。我们直接看下面例子,类 BC 继承了类 A,然后类 D 继承了 B,C,即继承了两个

class A:
    def __init__(self):
        self.n = 2

    def add(self, m):
        print('self is {0} @A.add'.format(self))
        self.n += m


class B(A):
    def __init__(self):
        self.n = 3

    def add(self, m):
        print('self is {0} @B.add'.format(self))
        super().add(m)
        self.n += 3

class C(A):
    def __init__(self):
        self.n = 4

    def add(self, m):
        print('self is {0} @C.add'.format(self))
        super().add(m)
        self.n += 4


class D(B, C):
    def __init__(self):
        self.n = 5

    def add(self, m):
        print('self is {0} @D.add'.format(self))
        super(D, self).add(m)
        self.n += 5

d = D()
d.add(2)
print(d.n)

'''
self is <__main__.D object at 0x7f0f1f6bfdc0> @D.add
self is <__main__.D object at 0x7f0f1f6bfdc0> @B.add
self is <__main__.D object at 0x7f0f1f6bfdc0> @C.add
self is <__main__.D object at 0x7f0f1f6bfdc0> @A.add
19
'''

MRO 顺序表

这里我们实例化了类 D,并调用了 add 方法,运行到 super(D, self).add(m) 这一行代码时,所以涉及 MRO,在本例中,MRO 顺序表为 [D, B, C, A, Object]。python 也提供了一个类内属性来输出 MRO 顺序,这里我们运行 print(D.__mro__),输出

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

MRO 有以下特性

代码执行过程

由于我们调用的是 super(D, self).add(m),因此在实例化时只会在 D 之后的类中找,即 [B, C, A, Object],这里只有 [B, C, A] 中有 add 函数,所以会依次调用在 BCA 中的 add 方法。如上所示,初始 self.n=5

9月 09, 2024
9月 06, 2024