欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python中super函数用法实例分析

程序员文章站 2022-12-24 19:51:28
本文实例讲述了python中super函数用法。分享给大家供大家参考,具体如下: 这是个高大上的函数,在python装13手册里面介绍过多使用可显得自己是高手 23333...

本文实例讲述了python中super函数用法。分享给大家供大家参考,具体如下:

这是个高大上的函数,在python装13手册里面介绍过多使用可显得自己是高手 23333. 但其实他还是很重要的. 简单说, super函数是调用下一个父类(超类)并返回该父类实例的方法. 这里的下一个的概念参考后面的mro表介绍.

help介绍如下:

super(type, obj) -> bound super object; requires isinstance(obj, type)
super(type) -> unbound super object
super(type, type2) -> bound super object; requires issubclass(type2, type)
typical use to call a cooperative superclass method:
class c(b):
    def meth(self, arg):
        super(c, self).meth(arg)

由此可知, super有三种用法, 第一参数总是召唤父类的那个类, 第二参数可缺(返回非绑定父类对象),也可以是实例对象或该类的子类. 最终返回的都是父类的实例(绑定或非绑定). 在python3中,super函数多了一种用法是直接super(),相当于super(type,首参), 这个首参就是一般的传入的self实例本身啦. 因为在py2里面常用也是这种写法.

另外, 在py2中, super只支持新类( new-style class, 就是继承自object的).

为什么要调用父类?

在类继承时, 要是重定义某个方法, 这个方法就会覆盖掉父类的相应同名方法. 通过调用父类实例, 可以在子类中同时实现父类的功能.例如:

# should be new-class based on object in python2.
class a(object):
 def __init__(self):
  print "enter a"
  print "leave a"
class b(a):
 def __init__(self):
  print "enter b"
  super(b, self).__init__()
  print "leave b"
>>> b = b()
enter b
enter a
leave a
leave b

通过调用super获得父类实例从而可以实现该实例的初始化函数. 这在实践中太常用了 (因为要继承父类的功能, 又要有新的功能).

直接使用父类来调用的差异

事实上, 上面的super函数方法还可以这么写:

class a(object):
 def __init__(self):
  print "enter a"
  print "leave a"
class b(a):
 def __init__(self):
  print "enter b"
  a.__init__(self)
  print "leave b"

通过直接使用父类类名来调用父类的方法, 实际也是可行的. 起码在上面的例子中效果上他们现在是一样的. 这种方法在老式类中也是唯一的调用父类的方法 (老式类没有super).

通过父类类名调用方法很常用, 比较直观. 但其效果和super还是有差异的. 例如:

class a(object):
 def __init__(self):
  print "enter a"
  print "leave a"
class b(a):
 def __init__(self):
  print "enter b"
  a.__init__(self)
  print "leave b"
class c(a):
 def __init__(self):
  print "enter c"
  a.__init__(self)
  print "leave c"
class d(b,c):
 def __init__(self):
  print "enter d"
  b.__init__(self)
  c.__init__(self)
  print "leave d"
>>> d=d()
enter d
enter b
enter a
leave a
leave b
enter c
enter a
leave a
leave c
leave d

可以发现, 这里面a的初始化函数被执行了两次. 因为我们同时要实现b和c的初始化函数, 所以分开调用两次, 这是必然的结果.

但如果改写成super呢?

class a(object):
 def __init__(self):
  print "enter a"
  print "leave a"
class b(a):
 def __init__(self):
  print "enter b"
  super(b,self).__init__()
  print "leave b"
class c(a):
 def __init__(self):
  print "enter c"
  super(c,self).__init__()
  print "leave c"
class d(b,c):
 def __init__(self):
  print "enter d"
  super(d,self).__init__()
  print "leave d"
>>> d=d()
enter d
enter b
enter c
enter a
leave a
leave c
leave b
leave d

会发现所有父类abc只执行了一次, 并不像之前那样执行了两次a的初始化.

然后, 又发现一个很奇怪的: 父类的执行是 bca 的顺序并且是全进入后再统一出去. 这是mro表问题, 后面继续讨论.

如果没有多继承, super其实和通过父类来调用方法差不多. 但, super还有个好处: 当b继承自a, 写成了a.__init__, 如果根据需要进行重构全部要改成继承自 e,那么全部都得改一次! 这样很麻烦而且容易出错! 而使用super()就不用一个一个改了(只需类定义中改一改就好了)

anyway, 可以发现, super并不是那么简单.

mro 表

mro是什么? 可以通过以下方式调出来:

>>> d.mro() # or d.__class__.mro() or d.__class__.mro(d) 
[d, b, c, a, object]
>>> b.mro()
[b, a, object]
>>> help(d.mro)
#docstring:
#mro() -> list
#return a type's method resolution order
#type:  method_descriptor

mro就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表 (类继承顺序表去理解也行) 啦.

这个表有啥用? 首先了解实际super做了啥:

def super(cls, inst):
 mro = inst.__class__.mro()
 return mro[mro.index(cls) + 1]

换而言之, super方法实际是调用了cls的在mro表中的下一个类. 如果是简单一条线的单继承, 那就是父类->父类一个一个地下去罗. 但对于多继承, 就要遵循mro表中的顺序了. 以上面的d的调用为例:

d的初始化

-> d (进入d) super(d,self)
-> 父类b (进入b) super(b,self)
-> 父类c (进入c) super(c,self)
-> 父父类a (进入a)  (退出a) # 如有继续super(a,self)  -> object (停了)
-> (退出c)
-> (退出b)
-> (退出d)

所以, 在mro表中的超类初始化函数只执行了一次!

那么, mro的顺序究竟是怎么定的呢? 这个可以参考官方说明the python 2.3 method resolution order . 基本就是, 计算出每个类(从父类到子类的顺序)的mro, 再merge 成一条线. 遵循以下规则:

在 mro 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。 这个原则包括两点:

1. 基类永远在派生类后面
2. 类定义时的继承顺序影响相对顺序.

如果有以下继承 (python中的super用法详解):

    object
     /   \
    /      a
   |     /   \
  b-1  c-2   d-2
    \   /    /
     e-1    /
        \  /
          f

那么mro是: f -> e -> b -> c -> d -> a -> object

怎么解释呢?

根据官方的方法, 是:

l(o) = o
l(b) = b o
l(a) = a o
l(c) = c a o
l(d) = d a o
l(e) = e + merge(l(b),l(c))
  = e + merge(bo,cao)
  = e + b + merge(o,cao)
  = e + b + c + merge(o,ao)
  = e + b + c + a + merge(o,o)
  = e b c a o
l(f) = f + merge(l(e),l(d))
  = f + merge(ebcao,dao)
  = f + ebc + merge(ao,dao)
  = f + ebc + d + merge(ao,ao)
  = f ebc d ao

看起来很复杂..但还是遵循在 mro 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。所以, 我个人认为可以这么想:

  • 先找出最长深度最深的继承路线f->e->c->a->object. (因为必然基类永远出现在派生类后面)
  • 类似深度优先, 定出其余顺序: f->e->b->obj, f->d->a-object
  • 如果有多个基类,基类的相对顺序保持不变, 类似于merge时优先提前面的项. 所以排好这些路线: (febo, fecao, fdao)
  • f->e->b->obj且e(b,c)决定b在c前面.所以f->e->b->c->a->obj (相当于f+merge(ebo,ecao)).
  • f->d->a-object且f(e,d)决定了d在e后, 所以d在e后a前. 因为相对顺序, 相当于fe+merge(bcao, dao), 所以fe bc d ao

更多可参考

关于python相关内容感兴趣的读者可查看本站专题:《python面向对象程序设计入门与进阶教程》、《python数据结构与算法教程》、《python函数使用技巧总结》、《python字符串操作技巧汇总》、《python编码操作技巧总结》及《python入门与进阶经典教程

希望本文所述对大家python程序设计有所帮助。