(5)继承与派生

什么是继承

继承是一种新建类的方式,新建的类称之为子类/派生类,被继承的类称之为父类基类超类

继承描述的是一种遗传的关系,父类的属性可以被子类访问到

为何要继承

解决类与类之间代码冗余的问题

如何用继承

在python中继承的特点:

1. 在python中一个子类可以同时继承多个父类

2. 在python3如果一个类没有指明继承的类,默认继承object,在python2如果一个类没有指明继承的类,不会默认继承object

Python中的类分成两种

新式类

但凡继承了object类的子类,以及该子类的子子类都是新式类

经典类

没有继承object类的子类,以及该子类的子子类都是经典类

在python3全都是新式类

在python2中才去分经典类与新式类

  class Parent1(object): #Python2中类定义传入参数object就是新式类
    pass

继承实例

class Parent1(object): #这是父类 ,传入一个object参数,是为了兼容Python2,所以如果要向下兼容这里必须写
  pass

class Parent2(object): #这是父类
  pass

class Sub1(Parent1):  #这是子类单继承
  pass

class Sub2(Parent1,Parent2): #这是子类,而且是多继承
  pass

PS:如何继承,就是子类把父类当做参数传入下面的代码中

查看继承的方法__bases__

print(Sub1.__bases__)

print(Sub2.__bases__)

查看父类的继承

print(Parent1.__bases__)

print(Parent2.__bases__)

PS:可以看到是一个类object,在python3如果一个类没有指明继承的类,默认继承object

继承实例

class Animal:

    def eat(self):
        print("%s 吃 " %self.name)

    def drink(self):
        print ("%s 喝 " %self.name)

    def shit(self):
        print ("%s 拉 " %self.name)

    def pee(self):
        print ("%s 撒 " %self.name)


class Cat(Animal):

    def __init__(self, name):
        self.name = name
        self.breed = '猫'

    def cry(self):
        print('喵喵叫')

class Dog(Animal):

    def __init__(self, name):
        self.name = name
        self.breed='狗'

    def cry(self):
        print('汪汪叫')


# ######### 执行 #########

c1 = Cat('小白家的小黑猫')
c1.eat()

c2 = Cat('小黑的小白猫')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()

继承的背景下属性查找

在单继承背景下新式类经典类属性查找都一样: 对象->对象的类->父类->父父类...

class Foo:
  # xxx=222
  pass

class Bar(Foo):
  # xxx=111
  pass

obj=Bar()
  # obj.xxx=0
  print(obj.xxx)

PS:查找xxx属性,如果对象里面没有,则去类里面找,类里面没有则去父类找,父类没有则去object里面找然后会报错,这一点新式类和经典类都一样

练习:self.f1是查找了父类的还是自身的
class Foo:
  def f1(self):
    print('Foo.f1')

def f2(self):
  print('Foo.f2')
  self.f1() # obj.f1()


class Bar(Foo):
  def f1(self):
    print('Bar.f1')

obj = Bar()
obj.f2()

首先创建了一个对象调用的是bar类,然后查找f2属性,bar类下没有,则去父类中查找,父类中有f2这个属性,则查找到,然后f2属性下又调用了自身下的属性f1,所以最终查找的是自己的f1属性,而不是父类下的,因为一开始查找f2的时候自身没有,因为继承了父类的属性,所以又去父类中查找,父类中的f2下调用的是自身,因为一开始查找的时候就将自身传入了父类中去查找,所以最后调用的是自己

在子类派生的新方法中重用父类的功能

方式一

class OldboyPeople:
school = 'Oldboy'
def __init__(self,name,age,gender):
self.name=name
self.age=age
self.gender=gender

class OldboyStudent(OldboyPeople):
def choose_course(self):
print('%s is choosing course' %self.name)

class OldboyTeacher(OldboyPeople):
def __init__(self, name, age, gender,level): #如果父类参数满足不了,则可以重新派生定义一个新的,这里就是定义一个新的派生,用内置方法__init__
OldboyPeople.__init__(self,name, age, gender) #这就是子类派生的新方法中重用父类功能,类调用对象就是调用函数,所以少一个参数都不行,这里就是重用父类的功能
self.level=level #这一段是需要自己增加的代码

def score(self,stu,num):
stu.score=num
print('老师[%s]为学生[%s]打了分[%s] ' %(self.name,stu.name,num))

stu1=OldboyStudent('王大炮',18,'male') #初始化学生的信息
print(stu1.__dict__) #查看有没有初始化成功

tea1=OldboyTeacher('Egon',18,'male',10) #初始化老师的信息,如果有重用父类功能,这里也要传入相应的参数
print(tea1.__dict__) #查看有没有初始化成功

方式二

super(自己的类名,self)得到一个特殊对象,该对象专门用来引用父类的属性,严格依赖于继承,完全参照mro列表

class OldboyPeople:
school = 'Oldboy'
def __init__(self,name,age,gender):
self.name=name
self.age=age
self.gender=gender

class OldboyStudent(OldboyPeople):
def choose_course(self):
print('%s is choosing course' %self.name)

class OldboyTeacher(OldboyPeople):
def __init__(self, name, age, gender,level):
super(OldboyTeacher,self).__init__(name,age,gender) #Python2中要这样写
super().__init__(name,age,gender) #Python3中可以这样简写
self.level=level

def score(self,stu,num):
stu.score=num
print('老师[%s]为学生[%s]打了分[%s] ' %(self.name,stu.name,num))

stu1=OldboyStudent('王大炮',18,'male')
print(stu1.__dict__)

tea1=OldboyTeacher('Egon',18,'male',10)
print(tea1.__dict__)

print(stu1.school)
print(tea1.school)

PS:重写父类OldboyPeople.__init__(self,name, age, gender)和super(OldboyTeacher,self).__init__(name,age,gender)两种方式看喜好使用,但是不要混着用 

在多继承背景下(非菱形查找),如果一个子类继承了多个分支的父类,多个分支最终没有汇聚到一个非object类上

新式类经典类属性查找都一样

非菱形下属性查找都一样

class E:
def test(self):
print('E')
pass

class F:
def test(self):
print('F')
pass

class B(E):
def test(self):
print('B')
pass

class C(F):
def test(self):
print('C')
pass

class D:
def test(self):
print('D')
pass

class A(B,C,D):
def test(self):
print('A')
pass

obj=A()
obj.test()

PS:由图可知,A继承了BCD,B继承了E,C继承了F,在查找的时候如果B里面没有则去E里查找,如果E没有则去C,如果C没有则去F,如果F没有则去D,D没有就报错(经典类没有object,只有新式类才有)

如果一个子类继承了多个分支的父类,多个分支最终汇聚到一个非object类上,称之为菱形继承

在菱形继承的背景下,新式类与经典的属性查找有所不同

 经典类和新式类继承下的查找

class G:
def test(self):
print('G')

class E(G):
def test(self):
print('E')

class F(G):
def test(self):
print('F')

class B(E):
def test(self):
print('B')

class C(F):
def test(self):
print('C')

class D(G):
def test(self):
print('D')

class A(B, C, D):
def test(self):
print('A')

obj = A()
print(B.mro()) #只有新式类才有的内置方法,查看属性查找的顺序
print(A.mro())
obj.test()

经典类: 深度优先查找,A->B->E->G->C->F->D

PS:这里的深度就是所谓的一条道走到底,就是从A先一直走到G直到没查询到,然后再走其他的继承直到找到或者没找到

新式类: 广度优先查找,A->B->E->C->F->D->G->object

PS:以哪一个类作为起始查找属性,就会以该类的mro列表为基准来进行属性查找

大前提: 我们是由C为起始引发的属性查找,所以super会参照C的mro列表来进行属性查找

注意: A没有继承B,但是A内super会基于C.mro()继续往后找

class A:
   def test(self):
   super().test()
class B:
def test(self):
print('from B')

class C(A,B):
pass

c=C()
c.test()
print(C.mro()) #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

print(A.mro()) #如果用A作为其实触发属性查找就会报错,参照mro字典
obj=A()
obj.test()

PS:super是按照从哪一个类作为起始找的属性,就是参照这个起始类的mro进行查找,super就是重新或者重新的语法

PS:为什么class A里面的super().test()最后会查找到B,因为查询是因为C作为基准引发的,所以就算A和B没有继承关系,C引发了查找,在A里面没有查找到,A里面由引发了一次属性查找,简单理解就是C引发了查找,A里面没有,然后A又引发了一次查找,作为基准就是C没在A里面找到,然后就去B里面找了

原文地址:https://www.cnblogs.com/shizhengquan/p/10091823.html