Python Cookbook-6.19 调用超类的__init__方法
任务
想确保所有的超类的__init__方法被自动调用(如果该超类定义过__init__方法)但 Python 并不会自动调用此方法。
解决方案
如果你的类是新风格类,内建的 super 会使这个任务变得极其简单(如果所有的超类的__init__方法也用相似的方式使用 super):
class NewstyleOnly(A,B,C):def __init__(self):super(NewStyleOnly,self).__init__()initialization specific to subclass NewstyleOnly
讨论
对新的开发任务,经典类不太值得推荐:它们的存在只是为了保证对于老版本Python的向后兼容性。应当尽量使用新风格类(直接或间接从object派生)。在新风格类中唯一你不能做的事情是将它的实例当做异常对象抛出,异常对象必须是旧风格的,不过对这些类你并不需要使用本节提供的功能。因此,下面将要进行的讨论会比较深入同时适用面也更窄,你如果不感兴趣的话可以直接略过。
你可能需要在经典类中使用这个功能,或者,你也想在新风格类中使用这个功能,但是这个类的某些超类并不满足前面提到的条件,即以正确的方式调用内建函数 super。针对这些情况,首先你要做的是前期准备工作——将所有的类转变为新风格类并且使它们正确地调用 super。如果你确实无法完成准备工作,那么你能做的就是循环检查基类–对每个基类,都看它是否拥有__init__,如果有的话,调用该方法:
class LookBeforeYouLeap(X,Y,Z):def __init__(self):for base in self__class__.__bases__:if hasattr(base,'__init__'):base.__init__(self)initialization specific to subclass lookBeforeYouLeap
更一般的,并不仅限于__init__ 方法,我们常常需要调用一个实例或者类的其他方法如果这个方法存在的话;如果实例或类的此方法不存在,我们就什么也不做,或者只做默认的动作。解决方案中展示的基于内建的 super 技术并不具有通用性:它只适用于当前对象的超类,要求那些超类以正确的方式调用 super,还要求被调用的方法在那些超类中存在。不过,所有的新风格类都拥有__init__方法:它们都是object的子类,而object定义了__init__(默认什么也不做,接受并忽略任何参数)。因此,所有的新风格类都拥有__init__方法,无论是继承来的还是重载过的。
在 LookBeforeYouLeap 类中展示的 LBYL, 技术具有更宽广的应用面,它可被用于包括__init__ 在内的各种方法。LBYL还可以和 super 合用,如下面这个例子所示:
class Base1(object):def met(self):print 'met in Base1'
class Der1(Base1):def met(self):s = super(Der1,self)if hasattr(s,'met'):s.met()print 'met in Der1'
class Base2(object):Pass
class Der2(Base2):def met(self):s = super(Der2,self)if hasattr(s,'met'):s.met()print 'met in Der2'
Der1().met()
Der2().met()
这个片段的输出是:
met in Base1
met in Derl
met in Der2
met 的实现对于所有的派生类,包括Der1(超类是 Base1,超类有 met 方法)和 Der2(超类是 Base2,超类没有这个方法),都具有相同的结构。通过将局部名字s绑定到super 的结果,并在调用方法之前先用 hasattr 检查超类是否拥有这个方法,这个LBYL结构使得可以用相似的代码处理两种情况。当然,在编写子类的时候,你通常就已经知道了超类有哪些方法以及是否需要和怎样调用它们。有时,当需要子类略微偏离它的超类的时候,这种技术能够提供一些额外的灵活度。
不过 LBYL 技术还远远谈不上完美:超类可能定义一个属性名为 met,这个 met 不能被调用也不需要参数。如果你确实非常需要灵活性,必须排除这种情况,可以获取超类的方法对象(如果有的话),然后用标准库模块inspect提供的getargspec函数来检查。但是当试图将这个概念推及到一般的情况下,复杂性就增加了。下面是一个例子,这个类的方法会试图不用参数调用超类中同名的方法(如果在超类中该属性存在而且可调用):
import inspect
class Der(A,B,C,D):def met(self):s = super(Der,self)#获取超类的被绑定对象,如果没有的话返回Nonem = getattr(s,'met',None)try:args,varargs,varkw,defaults = inspect.getargspec(m)except TypeError:#m不是方法,忽略passelse:#m是方法,它的所有参数都有默认值吗?if len(defaults) == len(args):#是的,调用之:m()print 'met in Der'
如果传递给 inspect.getargspec 的参数不是方法或函数,它会抛出一个TypeError 的异常这样我们就可以通过 try/except声明发现这种情况,如果确实有异常发生,我们只需在except子句中放入一个什么也不做的pass声明将其忽略即可。为了简化代码,我们并没有首先用hasattr 进行检查工作,而是用带有第三个参数的getattr 来获取“met”属性。因此,如果超类并没有任何一个叫做“met”的属性,m 会被设置成None,如果我们用 None 作为inspect.getargspec 的参数,同样会引发TypeError 异常——这样起到了一石二鸟的作用。如果在 ty子句中对inspect.getargspec 的调用没有引发 TypeError 异常,程序会继续执行 else 子句。
如果inspect.getargspec没有抛出TypeError,它会返回一个含有四个子项的元组,我们可以将各项绑定到一个本地名。在这里,我们关心的是 args,它是m的参数名列表,以及 defaults,一个 m 为它的参数提供的默认值元组。很显然,当且仅当 m 为它的参数提供了默认值时,我们才可以不使用参数调用 m。因此,通过比较args 列表和 defaults元组的长度,我们可以知道默认值的数目是否和参数的数目一致,只要两者一致我们就可以调用 m。
很显然,在大多数的情况下没必要使用这种高级的内省技术并进行细致地检查,但如果你确实需要这种能力,Python 提供了所有需要的技术。