Python深入:super函数

        新式类中最酷的,或者也是最不平常的特性之一,可能就是编写“cooperative类”。‘cooperative类’通过多继承,使用我称之为‘cooperative super call’的模式。

        先来看一下传统的,非cooperative super call的特点:当类C继承了基类B时,C希望覆盖定义在B中的一个方法m,当在C中定义的方法m中,调用了B中定义的m方法时,一个super call就产生了。比如:

class B:
   def m(self):
       print "B here"

class C(B):
   def m(self):
       print "C here"
       B.m(self)

        这时,我们称C的方法m扩展了B的方法m。这种模式在单继承中工作的很好,但是在多继承中就未必了。我们来看一下下面的4个类,他们的继承关系形成了一个菱形图。

class A(object): ..

class B(A): ...

class C(A): ...

class D(B, C): ...

        假设A定义了方法m,然后B和C都扩展了它。那么D怎么办呢?它继承了m的两个实现,一个来自B,一个来自C。传统上,python简单的使用它最早发现的那个。这种情况下,B中的m就是第一个被检索到的。

        但是这并不理想,因为这完全忽视了C中的定义。看一下忽视C中m的错误在哪,假设这些类代表了某种类型的持续性容器。并且,考虑一个方法实现了‘将数据存储到磁盘’的操作。大概情况下,D的实例拥有B和C的数据,以及A的数据。那忽略C中该方法的定义,意味着D的实例仅仅会保存A和B的数据,而不会保存C中定义的数据。

        在C++中,D继承了两个相互冲突的m的定义,那会引发一个错误信息。然后D的作者就会重写m来避免这种错误。但是D中的m会做什么呢?它会调用C的m,然后是B的m。但是它们又都继承自A。所以A的m会被调用两次。经典的python会有同样的问题,除了它根本不认为继承两个冲突定义的方法是错误的:它简单的选择第一个。

        传统的解决方法是:将m分为两部分,一部分是_m,它仅保存自己的数据,另一部分是m,它会调用自己的_m以及父类的_m们。比如:

class A(object):
   def m(self): "save A's data"

class B(A):
   def _m(self): "save B's data"
   def m(self):  self._m(); A.m(self)

class C(A):
   def _m(self): "save C's data"
   def m(self):  self._m(); A.m(self)

class D(B, C):
   def _m(self): "save D's data"
   def m(self):  self._m();B._m(self); C._m(self); A.m(self)

        这种模式有多个问题,首先是冗余的方法和定义,但是更重要的是:它引起了继承类对父类不必要的依赖:A的存在不仅是B和C要考虑的实现细节,甚至D也需要知道。如果未来我们需要移除A和B,C之间依赖关系,这也会影响到D。类似的,如果我们为B和C增加一个父类AA,那么B和C的子类也必须知道这一点。

        ‘call-next-method’的模式,结合新的MRO,很好的解决了这个问题,如下:

class A(object):
   def m(self): "save A's data"

class B(A):
   def m(self): "save B's data"; super(B, self).m()

class C(A):
   def m(self): "save C's data"; super(C, self).m()

class D(B, C):
   def m(self): "save D's data"; super(D, self).m()

 

        注意:这里super的第一个参数永远是调用它的类;第二个参数总是self。现在,为了解释super是如何工作的,考虑每个类的MRO,可以查看每个类的__mro__属性:

A.__mro__ == (A, object)

B.__mro__ == (B, A, object)

C.__mro__ == (C, A, object)

D.__mro__ == (D, B, C, A, object)

        表达式super(C,self).m应该仅用在类C的m的实现中。要牢记在心的是:self是C的实例,但是self.__class__不一定就是C;他可能是C的子类,比如D。

        表达式super(C,self).m会查询self.__class__.__mro__中,C的位置,然后,在这个位置之后,开始寻找m方法的实现。比如,如果self是C的实例,那么super(C,self).m将会找到A中的m,同样的,super(B,self).m中,如果self是B的实例,也会有同样的效果。

        但是考虑D的实例,在D中的m方法中,super(D,self).m将会搜索,然后找到了B.m(self),因为B是在D.__mro__中紧跟在D之后的,第一个实现了m的类。然后,在B.m中,调用了super(B,self).m()。因为self是D的实例,而MRO是(D,B,C,A,object),并且,跟在B之后的是C,所以就会调用C.m。在C.m中,调用super(C, self).m(),依然是寻找相同的MRO,可以看到跟在C后的是A,因此A.m被调用了,而这就是终点了。

        要注意的是:同样的super表达式,却找到了不同的实现了m的类,这取决于self。这是cooperativesuper 机制的关键所在。

        下面是一个super内嵌类的完整实现:

class Super(object):
   def __init__(self, type, obj=None):
       self.__type__ = type
       self.__obj__ = obj

   def __get__(self, obj, type=None):
       if self.__obj__ is None and obj is not None:
            return Super(self.__type__, obj)
       else:
            return self

   def __getattr__(self, attr):
       if isinstance(self.__obj__, self.__type__):
            starttype = self.__obj__.__class__
       else:
            starttype = self.__obj__

       mro = iter(starttype.__mro__)

       for cls in mro:
            if cls is self.__type__:
                break

       # Note: mro is an iterator, so the second loop
       # picks up where the first one left off!
       for cls in mro:
            if attr in cls.__dict__:
                x = cls.__dict__[attr]
                if hasattr(x,"__get__"):
                    x = x.__get__(self.__obj__)
                return x

       raise AttributeError, attr

 
class A(object):
   def m(self):
       return "A"


class B(A):
   def m(self):
       return "B" + Super(B, self).m()


class C(A):
   def m(self):
       return "C" + Super(C, self).m()

 
class D(C, B):
   def m(self):
       return "D" + Super(D, self).m()


print D().m() # "DCBA"


https://www.python.org/download/releases/2.2.3/descrintro/

 

posted @ 2015-06-12 14:41  gqtc  阅读(156)  评论(0编辑  收藏  举报