Python的新式类和经典类

晓杰0 2019-06-30

问题

在Python中定义类时,我们经常看到两种写法:

class PersonOne:
    name = "person one"
    
class PersonTwo(object):
    name = "person two"

也就是说在Python中,继承object 与不继承object的写法有什么区别?
在Python2.X中,第一种写法称之为经典类,第二种写法称之为新式类。Python2.2之前只能支持第一种写法,在Python2.2到Python2.7,两种写法都可以,但是不同写法定义出来的类是不一样的;在Python3.X中两种写法都可以,而且定义出来的类是完全一样的,都是新式类,可以理解为和Java一样,Python3.0之后Object已经作为所有类的基类,因此是否显示指明已经不重要。因此,如果你是使用Python3.X的版本,完全可以无视这个问题,怎么写都行;但是如果不是就需要搞清楚区别了。

使用新式类和经典类的区别

个人理解,两种定义核心的区别就是定义类的MRO是不同的,在多重继承中,MRO直接决定了方法的调用顺序,因此会产生很大的影响。

  • 经典类的MRO
    经典类的MRO的生成时基于深度优先遍历算法的,以下面的继承关系为例,生成的MRO为:[D,B,A,C],因此,在调用test方法时,按照此顺序查找必然先找到A中的test方法,但是这其实是不太不合理的。

    class A:
          def test(self):
              print('in a test')
    
      class B(A):
          pass
      
      class C(A):
          def test(self):
              print('in c test')
      
      class D(B, C): 
          pass
      
      if __name__ == '__main__':
          d = D() 
          d.test()
  • 新式类的MRO
    在新式类中,MRO的生成时基于C3算法的,关于C3算法计算过程参见链接,核心就是Merge函数的计算过程,有的文章中直接说是广度优先其实并不准确。此处生成的类D的MRO为[D,B,C,A],那么,按照这个顺序调用test方法自然找到的是C中的test。

扩展

  1. super的调用关系

    在Python3中,我们经常使用到super方法,在单继承中没有什么疑问,直接调用super方法自然是访问其唯一的父类。但是再多继承中,又是如何决定调用哪一个父类的呢?这个还是和MRO有关。
    准确来说,super方法的调用方式为super(ClassName, self).func(),那么此处的func方法就是属于MRO中ClassName下一个类。如果确实没有参数,即super().func(),等价于super(MRO中的第一个类, self).func()
    还是以上面的继承关系为例,重写类D的__init__()方法,第一个super调用的是B,第二个是C。

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

相关推荐