30. 类基础-类的继承

假如已经有几个类,而类与类之间有共同的变量属性和函数属性,那就可以把这几个变量属性和函数属性提取出来作为基类的属性。而特殊的变量属性和函数属性,则在本类中定义,这样只需要继承这个基类,就可以访问基类的变量属性和函数属性。可以提高代码的可扩展性。通过继承可以快速扩展和实现函数的多样性

30.1 继承

类的继承比较简单只需在类后边给出类的父类即可,语法如下。

class 子类名(父类):
    类里的其他内容

这里可以先创建一个父类aa。然后基于aa类创建子类bb。

class aa(object):
    def __init__(self, v):
        self.x = v
        self.__pa = 10
    def p(self):
        print "class aa's instance method"
    def info(self):
        print "info of aa instance"
class cc(aa):
    def __init__(self, v):
        self.z = v
        self__pc = 10
    def p(self):
        print "class cc's instance method"
a = aa(10)
c = cc(30)
a.p()
a.info()
c.p()
c.info()

程序执行结果:

class aa's instance method
info of aa instance
class cc's instance method
info of aa instance

语句class cc(aa):说明cc类继承aa类,cc是aa的子类或继承类,aa是cc的父类或基类,在aa和cc里都有各自的构造函数__init__p函数,但功能各异这种现象称多态。子类和父类的多态函数功能不同,对于子类而言则是重写了父类里的函数。

info函数是非私有的函数,定义在aa类里,但cc里没有设计(重写)info函数,但cc是aa的子类,那么父类的函数被子类继承,子类cc可以使用非私有的info函数。

  • 属性数据,类里定义的实际对象的属性数据的继承问题。

对于__init__函数比较特殊,它是构造函数,一般每个类都有自己的构造函数,当然子类也可以不自己写自己的构造函数,继承父类的构造函数。

class aa(object):
    def __init__(self, v):
        self.x = v
        self.__pa = 10
    def p(self):
        print "class aa's instance method"
    def info(self):
        print "info of aa instance"
class cc(aa):
    '''
    def __init__(self, v):
        self.z = v
        self.__pc = 10
    '''
    def p(self):
        print "class cc's instance method"

a = aa(10)
c = cc(30)
a.p()
a.info()
c.p()
c.info()
print c.x
print dir(c)

程序执行结果:

class aa's instance method
info of aa instance
class cc's instance method
info of aa instance
30
class aa's instance method
info of aa instance
class cc's instance method
info of aa instance
30
['__class__', 。。。, '__subclasshook__', '__weakref__', 'info', 'p', 'x']

cc类的构造函数被注释掉了,说明cc类没有自己的构造函数,继承aa里的构造函数,语句print c.x打印结果是30。在创建类cc的实例对象c的时候调用的是aa的构造函数,所以定义在aa里的x属性存在,而__pa是aa类对象的私有属性,故在子类的对象里没有__pa,即私有属性数据不能被子类继承。

如果cc类即子类有自己的构造函数,这些情况(x属性被继承)还存在么?

class aa(object):
    def __init__(self, v):
        self.x = v
        self.__pa = 10
    def p(self):
        print "class aa's instance method"
    def info(self):
        print "info of aa instance"
class cc(aa):
    def __init__(self, v):
        self.z = v
        self.__pc = 10
    def p(self):
        print "class cc's instance method"

a = aa(10)
c = cc(30)
a.p()
a.info()
c.p()
c.info()
print c.z
print dir(c)
print c.x
print c.__pc

程序执行结果:

class aa's instance method
info of aa instance
class cc's instance method
info of aa instance
30
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hasTraceback (most recent call last):
  File "D:\js\tt3.py", line 24, in <module>
    print c.x
AttributeError: 'cc' object has no attribute 'x'
h__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cc__pc', 'info', 'p', 'z']
[Finished in 0.1s with exit code 1]

报错的原因在c.x类cc的实例对象c里没有x属性,这可以从print dir(c)的打印结果里看出,由于cc定义了自己的构造函数,初始化了z和__pc两个数据属性,所以print dir(c)的打印结果里有z和__pc而没有x。如果想让类cc的实例对象有父类aa的所有公开属性,那么需要在cc类的构造函数里主动调用aa类的构造函数,那么cc类的实例对象就有定义在父类aa里的所有公开属性了。

class aa(object):
    def __init__(self, v):
        self.x = v
        self.__pa = 10
    def p(self):
        print "class aa's instance method"
    def info(self):
        print "info of aa instance"
class cc(aa):
    def __init__(self, v):
        super(cc, self).__init__(12)
        #aa.__init__(self, 12)
        self.z = v
        self.__pc = 10
    def p(self):
        print "class cc's instance method"

a = aa(10)
c = cc(30)
a.p()
a.info()
c.p()
c.info()
print dir(c)
print c.x, c.z

程序执行结果:

class aa's instance method
info of aa instance
class cc's instance method
info of aa instance
['__class__',..., '__weakref__', '_cc__pc', 'info', 'p', 'x', 'z']
12 30

子类cc的实例对象的属性数据如果想具有父类实例对象里的属性数据,可以再在子类的构造函数再次定义一下和父类构造函数定义的属性相同的公开(有)的数据,这样做可以,但丧失了类的优点,所以可以但不建议这么去做,原因如果父类为实例对象定义的属性很多,那么子类也得写这么多,代码冗长无意义。所以可以考虑直接在子类的构造函数里调用父类的构造函数,因为python语言的特点是赋值即可存在。那调用了父类的构造函数,属性被赋值,子类里就存在了父类实例对象的各个公开、公有属性数据了。调用父类构造函数有两种方式:

父类名.__init__(self[,args])

或者

super(子类的类名,self).__init__([argx])

使用方式如上边的代码、从上边的程序的执行结果可以看出子类主动调用了父类的构造函数,子类的实例对象便具有了父类的实例对象的属性数据,公有的。实例对象c的x属性是通过调用父类构造函数而具有的。而z属性则是cc类自己在构造函数里定义的。

  • 私有属性的继承问题

在python里,私有数据和方法函数是不能被子类继承的。

  class aa(object):
    def __init__(self, v):
        self.x = v
        self.__pa = 10
        self.__px()
    def __px(self):
        print self.__pa
  class bb(aa):
    pass
  a = aa(12)
  print a.x
  b = bb(13)
  print b.x
  #print b.__pa
  #b.__px()

程序里最后两条注释掉的语句均有错。子类对象(在类外)不能访问父类的私有数据和方法函数。那么在子类的内部能调用父类的私有函数么?父类里的函数是可以直接使用私有函数的,那么我们在子类的函数里调用父类的私有函数看看行不行?

  class aa(object):
    def __init__(self, v):
        self.x = v
        self.__pa = 10
        self.__px()
    def __px(self):
        print self.__pa
  class bb(aa):
    def p(self):
        self.__px()
  a = aa(12)
  print a.x
  b = bb(13)
  print b.x
  b.p()

程序最后一句b.p()调用出错,原因是p函数里调用了__px这个父类的私有函数。看来真的不行!?如果理解Python私有的机制,实际Python没有私有函数,调用出错是因为找不到函数,因为Pyhon将__开始的私有数据和函数的名改变了一下,实现了"隐藏",具体是在__前,前加_类名,所以不知道规则直接调用原来设计的私有函数、数据名肯定出错,但知道了这个隐藏规则,就可以直接调用变化后的函数、数据名,这些可以通过打印对象信息获得print dir(c)。具体来说aa类的私有数据原来设计时为__pa,Python将其变为_aa__pa,私有函数__px变为_aa__px,那么

  class aa(object):
    def __init__(self, v):
        self.x = v
        self.__pa = 10
        self.__px()
    def __px(self):
        print self.__pa
  class bb(aa):
    def p(self):
        super(bb, self)._aa__px()
  a = aa(12)
  print a.x
  b = bb(13)
  print b.x
  b.p()

程序就没有错误。规则知道了,并不一定这样去做,尤其是私有的数据和方法函数大家规定是私有那就按私有的规则来玩吧。

30.2 多继承

Python的类允许可以有多个父类,从左至右有顺序要求,这和后续搜索查找数据和函数有关系。

  class 子类(父类1, 父类2, ...)

多继承时子类的父类间和子类要满足人类的伦理规则

class aa(object):
    def __init__(self, v):
        self.x = v
    def px(self):
        print self.x

class bb(object):
    def __init__(self, v):
        self.y = v
    def py(self):
        print self.y

class cc(aa, bb):
    def __init__(self, v, v1 = 100):
        print "cc"
        super(cc, self).__init__(v1)
        #aa.__init__(self, v1)
        #bb.__init__(self, v1)
        print "ccx"
        self.z = v
    def pz(self):
        print self.z

a = aa(12)
a.px()
b = bb(13)
b.py()
c = cc(14)
print dir(c)
c.pz()

程序的执行结果:

12
13
cc
ccx
['__class__', ..., '__weakref__', 'px', 'py', 'pz', 'x', 'z']
14

子类cc继承aa和bb类,在cc这个子类里调用了super函数(类)主动执行了父类的构造函数,cc有两个父类aa和bb,那这个super执行的是那个父类的构造函数呢?还是两个父类的构造都执行了呢?从print dir(c)语句可以看出cc类的实例对象c里只有x属性和z属性,x属性在cc类的构造函数里没有,出现在aa的构造函数里,所以说明super最终执行的是cc类的第一个父类aa的构造函数。c实例对象里没有bb类里的y属性,若想有y属性必须得执行一下bb类的实例对象构造函数,可以采用如下方式:

aa.__init__(12)
bb.__init__(13)

这样cc类的实例对象c就具有aa和bb的实例对象属性x和y了。从这个例子可以看出,子类想拥有父类对象的属性数据,需要执行父类的构造函数才可以拥有。父类的实例对象的函数如果子类没有定义,那么子类可以使用父类定义的实例对象方法函数。

class aa(object):
    def __init__(self, v):
        self.x = v
    def px(self):
        print "px method of aa", self.x

class bb(object):
    def __init__(self, v):
        self.y = v
    def py(self):
        print "py method of bb", self.y
    def px(self):
        print "px method of bb", self.y
class cc(aa, bb):
    def __init__(self, v, v1 = 100):
        super(cc, self).__init__(v1)
        bb.__init__(self, v1)
        self.z = v
    def pz(self):
        print "pz method of cc", self.z

a = aa(12)
a.px()
b = bb(13)
b.py()
c = cc(14)
print dir(c)

c.px()
c.py()
c.pz()

程序的执行结果:

px method of aa 12
py method of bb 13
['__class__', ..., '__weakref__', 'px', 'py', 'pz', 'x', 'y', 'z']
px method of aa 100
py method of bb 100
pz method of cc 14

从c调用的px、py、pz三个方法函数的执行结果可以看出,cc里没有定义px和py函数,显然这两个函数是从父类继承下来的,px定义在aa类,py定义在bb类里,c对象使用了父类里定义的实例对象方法函数。从程序里还有一个问题就是aa和bb都定义了px方法函数,但cc的实例对象c执行的是aa类的px方法函数?为何不执行bb类的px方法函数呢?这个问题是Python里的搜索树问题,python会依据对象的类的继承关系搜索对象调用的方法函数,(1)如果类自己定义了该调用的方法函数,那就执行自己类里定义的方法函数好了。(2)如果类里没有定义方法函数,看看自己的(第一)父类是否定义了该调用的方法函数,如果有那就执行父类的定义的对象方法函数。(3)如果第一个父类里没有,看看类是不是多向继承,如果是看第二父类里是否有?没有看第三、四...以致所有的父类里是否有该函数的定义,如果能找到且第一次找到,那就执行这个类里定义的方法函数。(4)如果所有的父类里均未出现该方法函数的定义,那么查找第一父类的父类,然后重复步骤(3)查第二父类的父类是否有,以此类推,如果找到便执行该方法函数,如果没找到继续向上一级父类查找,一直查到顶层object如果还是没找到,则报错未定义该方法函数,至此方可截至。按搜索树的规则cc类的实例对象c调用了px方法函数,px没有在cc里定义,那就看其父类里是否有px的定义?aa和bb里都有px的定义,aa是cc类的第一个父类,那就查到aa就截至了,而py函数只有bb类定义,aa类没有py的定义,那么需要看cc类的第二个父类bb里是否有py?有也就到这结束搜索查找过程了。所以c调用px是aa类里的px,c调用py则是bb里的py。

感谢Klang(金浪)智能数据看板klang.org.cn鼎力支持!