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类,以避免不必要的问题。