Python中的旧式类和新式类有什么区别?

Python中的旧式类和新式类有什么区别?

在Python中,类可以分为旧式类和新式类。在Python 3.x中,只存在新式类,而在Python 2.x中,既有旧式类,也有新式类。那么,旧式类和新式类有什么区别呢?

阅读更多:Python 教程

旧式类和新式类的定义

旧式类是指在Python 2.x中默认继承的类型,定义类时没有继承object类。例如:

class OldClass:
    pass

而新式类是Python 2.x引入的概念,也是Python 3.x中默认继承的类型,定义类时必须继承object类。例如:

class NewClass(object):
    pass

属性查找顺序的不同

旧式类和新式类在属性查找过程中有所不同。对于继承链中的每个对象,Python在查找属性时都会沿着继承链从下到上依次查找。但是,旧式类和新式类的继承链不同,因此二者在属性查找顺序上也有所不同。

对于旧式类,Python的属性查找顺序并不遵循广度优先原则,而是采用经典类查找顺序(depth-first,即深度优先)。如下所示,当对旧式类obj执行obj.attr方法时,Python会首先搜索obj.__class__,如果obj.__class__是经典类,则查找obj.__class__的基类,即obj.__class__.__bases__,直到发现属性为止,如果所有的基类都没有找到,则沿着继承链查找obj.__class__.__bases__.__bases__(即obj.__class__.__bases__的基类),以此类推,直到遇到object类。

class OldClassA:
    x = 'OldClassA'

class OldClassB:
    x = 'OldClassB'

class OldClassC(OldClassA, OldClassB):
    pass

obj = OldClassC()

print obj.x    # 'OldClassA'

对于新式类,Python的属性查找顺序则遵循广度优先原则(breadth-first)。如下所示,当对新式类obj执行obj.attr方法时,Python会首先搜索obj.__class__,然后依次搜索obj.__class__.__bases__中的类(按照从左到右的顺序),以此类推,直到找到属性或遍历完所有基类为止。

class NewClassA(object):
    x = 'NewClassA'

class NewClassB(object):
    x = 'NewClassB'

class NewClassC(NewClassA, NewClassB):
    pass

obj = NewClassC()

print obj.x    # 'NewClassA'

MRO(Method Resolution Order)的不同

MRO是指方法解析顺序(Method Resolution Order),即在搜索方法时,Python按照什么样的顺序去查找方法。旧式类和新式类在MRO上也有区别。

在Python 2.x中,旧式类是没有MRO的。在旧式类中,由于其属性查找顺序采用的是深度优先原则,容易出现方法解析的歧义。例如:

class OldClassA: 
    def foo(self):
        print 'oldClassA' 

class OldClassB(OldClassA): 
    pass 

class OldClassC(OldClassA): 
    def foo(self):
        print 'oldClassC' 

class OldClassD(OldClassB, OldClassC): 
    pass 

obj = OldClassD() 
obj.foo()    # 'oldClassA' 

在该例子中,OldClassD同时继承了OldClassB和OldClassC,由于旧式类没有MRO,因此Python按照深度优先的原则查找方法foo,发现OldClassB和OldClassC都有foo方法的定义,但是由于深度优先的原则,Python遵循OldClassB -> OldClassA -> OldClassC的顺序查找,因此输出结果为’oldClassA’,并没有输出OldClassC的定义。

而在Python 2.x中,新式类是有MRO的。在新式类中,Python采用C3算法来确定MRO的顺序。C3算法是一种保证广度优先和线性化的算法,能够保证继承链的结构单一且无歧义。例如:

class NewClassA(object):
    def foo(self):
        print 'NewClassA'

class NewClassB(object):
    pass

class NewClassC(object):
    def foo(self):
        print 'NewClassC'

class NewClassD(NewClassA, NewClassB, NewClassC):
    pass

obj = NewClassD()
obj.foo()    # 'NewClassA'

在该例子中,NewClassD同时继承了NewClassA、NewClassB和NewClassC,由于新式类采用C3算法,因此其MRO顺序为NewClassD -> NewClassA -> NewClassC -> NewClassB -> object,因此在执行obj.foo()时,搜索到NewClassA后即返回并输出结果’NewClassA’。

魔术方法的不同

旧式类和新式类在魔术方法的处理方式上也有所不同。

对于旧式类,如果在定义类时未显式地继承object类,那么旧式类也不存在继承自object的特殊魔术方法,如__new____init__。例如:

class OldClass:
    def __init__(self):
        print '__init__ in OldClass'

obj = OldClass()    # TypeError: this constructor takes no arguments

在该例子中,OldClass定义了__init__方法,但是由于旧式类没有继承自object类,因此无法自动调用基类的init方法,所以在执行OldClass()时报错。

对于新式类,由于必须继承自object类,因此如果子类未定义某些特殊魔术方法,Python会自动查找其基类的同名方法并调用。例如:

class NewClassA(object):
    def __init__(self):
        print '__init__ in NewClassA'

class NewClassB(NewClassA):
    pass

obj = NewClassB()    # '__init__ in NewClassA'

在该例子中,NewClassA定义了__init__方法,NewClassB未定义__init__方法,但由于NewClassB继承自object,因此Python会自动调用NewClassA的__init__方法。

总结

旧式类和新式类在属性查找顺序、MRO和魔术方法的处理方式上存在区别。在Python 3.x中只存在新式类,因此无需考虑旧式类和新式类的区别问题,但在Python 2.x中需要注意这些差异。因此建议在Python 2.x中定义类时都继承自object类,以避免不必要的问题。

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程