经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Python » 查看文章
python语法学习之super(),继承与派生
来源:jb51  时间:2022/5/30 9:37:45  对本文有异议

1 什么是继承?

继承是一种创建新类的方式;

在Python中,新建的类可以继承一个或多个父类,新建的类可称为子类或派生类,父类又可称为基类或超类。

继承可以用来解决类与类之间的代码重用性问题;

  1. class ParentClass1: #定义父类
  2. pass
  3. class ParentClass2: #定义父类
  4. pass
  5. class SubClass1(ParentClass1): #单继承
  6. pass
  7. class SubClass2(ParentClass1,ParentClass2): #多继承
  8. pass

注意:

在python中一个子类可以继承多个父类,在其他语言中,一个子类只能继承一个父类;

python中的继承分为单继承和多继承;

通过类的内置属性__bases__可以查看类继承的所有父类

  1. >>> SubClass2.__bases__
  2. (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

在Python3中只有新式类,即使没有显式地继承object,也会默认继承该类。

2 继承的规则

子类继承父类的成员变量和成员方法:

  • 子类不继承父类的构造方法,能够继承父类的析构方法
  • 子类不能删除父类的成员,但可以重定义父类成员
  • 子类可以增加自己的成员

示例:

  1. # python中子类继承父类成员变量之间的取值逻辑
  2. class Person():
  3. def __init__(self, name, age, sex):
  4. self.name = "jasn"
  5. self.age = '18'
  6. self.sex = sex
  7. def talk(self):
  8. print("i want to speak something to yo!!")
  9. class Chinese(Person):
  10. def __init__(self, name, age, sex, language):
  11. Person.__init__(self, name, age, sex) # 用父类的name,age,sex 覆盖掉子类的属性
  12. self.age = age # 覆盖掉了父类的age,取值为子类实例中传入age参数
  13. self.language = "chinese"
  14. def talk(self):
  15. print("我说的是普通话!!")
  16. Person.talk(self)
  17. obj = Chinese("nancy",'18','male',"普通话")
  18. print(obj.name) # 对应场景A
  19. print(obj.age) # 对应场景B
  20. print(obj.language) # 对应场景C
  21. obj.talk() # 对应场景D
  22. # 总结:
  23. # A:若父类中初始化了成员变量,子类调用父类构造方法未覆盖属性(self.name),则调用子类属性时取值为父类中初始化的成员变量;
  24. # B:若父类中初始化了成员变量,若子类调用父类构造方法覆盖属性(self.age)则取值为子类实例中传入参数
  25. # C:若父类未初始化该成员变量,则无论子类中有无进行对父类构造方法进行属性的覆盖,均取子类实例中传入的参数
  26. # D:对于方法,如果子类有这个方法则直接调用,如果子类没有则去父类查找。父类没有则报错

3 继承原理

对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。

  1. >>> D.mro() # 新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置方法
  2. [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止,而这个MRO列表的构造是通过一个C3线性化算法来实现的。

实际上就是合并所有父类的MRO列表并遵循如下三条准则:

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父

Python 中的 MRO —— 方法搜索顺序

Python中针对类提供了一个内置属性 mro 可以查看方法搜索顺序

MRO 是 method resolution order,主要用于在多继承时判断方法、属性 的调用路径。

  1. print(C.__mro__) #C是多继承后的类名

输出结果:

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

在搜索方法时,是按照 mro 的输出结果 从左至右 的顺序查找的;

如果在当前类中找到方法,就直接执行,不再搜索;如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索。

如果找到最后一个类,还没有找到方法,程序报错。

4 多继承属性查询顺序

(1)多继承结构为菱形结构

如果继承关系为菱形结构,那么经典类与新式类会有不同MRO。

箭头表示搜索顺序

(2)多继承结构为非菱形结构

会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性。

5 查找流程

① 由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去。

② 由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去。

主要知识点:类的__mro__ 属性的用法;

属性查找

有了继承关系,对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找.....

  1. class Foo:
  2. def f1(self):
  3. print('Foo.f1')
  4. def f2(self):
  5. print('Foo.f2')
  6. self.f1()
  7.  
  8. class Bar(Foo):
  9. def f1(self):
  10. print('Bar.f1')
  11. b=Bar()
  12. b.f2()
  13. # 运行结果:
  14. Foo.f2
  15. Bar.f1
  16. # 运行流程分析:
  17. b.f2()会在父类Foo中找到f2,先打印Foo.f2,然后执行到self.f1(),即b.f1(),仍会按照:对象本身->类Bar->父类Foo的顺序依次找下去,在类Bar中找到f1,因而打印结果为Bar.f1

父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的:

  1. class Foo:
  2. def __f1(self): # 变形为_Foo__f1
  3. print('Foo.f1')
  4. def f2(self):
  5. print('Foo.f2')
  6. self.__f1() # 变形为self._Foo__f1,然后回到Bar类中找,没有,再到Foo中找到了
  7. class Bar(Foo):
  8. def __f1(self): # 变形为_Bar__f1
  9. print('Bar.f1')
  10. b=Bar() # Bar类处于执行阶段,_Bar__f1变为__f1
  11. b.f2() # 在父类中找到f2方法,进而调用b._Foo__f1()方法,是在父类中找到的f1方法
  12.  
  13. # 运行结果:
  14. Foo.f2
  15. Foo.f1

6 继承概念的实现

方式主要有2类:

  • 实现继承
  • 接口继承

① 实现继承是指使用基类的属性和方法而无需额外编码的能力。

② 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(子类重构爹类方法)。

在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。

例如:Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类,但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。

7 私有属性私有方法在继承中的表现

父类中的私有方法和私有属性都是不能被子类继承下来的;

测试示例:父类中的私有属性和私有方法是否能被继承下来?

  1. class Perpon:
  2. num = 20
  3. __num1 = 12
  4. def __test1(self):
  5. print('__test1....')
  6. def test2(self):
  7. print('test2...')
  8. class Student(Perpon):
  9. def test(self):
  10. print('num...')
  11. print(self.num)
  12. # print(Student.__num1)
  13. self.test2()
  14. # self.__test1()
  15. student = Student()
  16. student.test()
  17. student.test2()
  18. # student.__test1() # 报错
  19. '''
  20. num...
  21. 20
  22. test2...
  23. test2...
  24. '''

8 派生类

1)在父类的基础上产生子类,产生的子类就叫做派生类

2)父类里没有的方法,在子类中有了,这样的方法就叫做派生方法。

3)父类里有,子类也有的方法,就叫做方法的重写(就是把父类里的方法重写了)

  1. class Hero:
  2. def __init__(self, nickname,aggressivity,life_value):
  3. self.nickname = nickname
  4. self.aggressivity = aggressivity
  5. self.life_value = life_value
  6. def attack(self, enemy):
  7. print('Hero attack')
  8. class Garen(Hero):
  9. camp = 'Demacia'
  10. def attack(self, enemy): #self=g1,enemy=r1
  11. # self.attack(enemy) #g1.attack(r1),这里相当于无限递归
  12. Hero.attack(self,enemy) # 引用 父类的 attack,对象会去跑 父类的 attack
  13. print('from garen attack') # 再回来这里
  14. def fire(self):
  15. print('%s is firing' % self.nickname)
  16. class Riven(Hero):
  17. camp = 'Noxus'
  18. g1 = Garen('garen', 18, 200)
  19. r1 = Riven('rivren', 18, 200)
  20. g1.attack(r1)
  21. # print(g1.camp)
  22. # print(r1.camp)
  23. # g1.fire()

9 属性的覆盖(派生属性)

子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类);

需要注意的是:一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了(属性的覆盖)。

示例:

  1. # 派生属性: 子类中自己添加的新属性
  2. # 属性的覆盖: 子类和父类有相同属性,调用自己的
  3. class Perpon:
  4. num = 20
  5. def __init__(self, name):
  6. print('person...')
  7. class Student(Perpon):
  8. num = 10 # 把父类中的20覆盖
  9. def __init__(self, name, age): # age 为派生属性
  10. super().__init__(name)
  11. self.name = name
  12. self.age = age
  13. print('student...')
  14. def study(self):
  15. print(super().num)
  16. pass
  17. student = Student('赵四', 23)
  18. print(student.name) # person...
  19. print(student.age)
  20. print(student.num)
  21. student.study()
  22. '''
  23. person...
  24. student...
  25. 赵四
  26. 23
  27. 10
  28. 20
  29. '''

10 父类属性(方法)的重用

指名道姓的重用

  1. class A:
  2. def __init__(self):
  3. print('A的构造方法')
  4. class B(A):
  5. def __init__(self):
  6. print('B的构造方法')
  7. A.__init__(self)
  8. class C(A):
  9. def __init__(self):
  10. print('C的构造方法')
  11. A.__init__(self)
  12. class D(B,C):
  13. def __init__(self):
  14. print('D的构造方法')
  15. B.__init__(self) # 先找到B,B调用A,等这个线性任务处理完之后,在继续下一行代码
  16. C.__init__(self) # 先找到C,C里面也调用A的方法
  17. pass
  18. f1=D() #A.__init__被重复调用
  19. '''
  20. D的构造方法
  21. B的构造方法
  22. A的构造方法
  23. C的构造方法
  24. A的构造方法
  25. '''

Super()方法重用

  1. class A:
  2. def __init__(self):
  3. print('A的构造方法')
  4. class B(A):
  5. def __init__(self):
  6. print('B的构造方法')
  7. super(B,self).__init__()
  8. class C(A):
  9. def __init__(self):
  10. print('C的构造方法')
  11. super(C,self).__init__()
  12. class D(B,C):
  13. def __init__(self):
  14. print('D的构造方法')
  15. super().__init__() # super(D,self).__init__()
  16. f1=D() #super()会基于mro列表,往后找
  17. '''
  18. D的构造方法
  19. B的构造方法
  20. C的构造方法
  21. A的构造方法
  22. '''
  23. # super() 语法
  24. # super(type[, object-or-type]) type 当前类,object-or-type 为实例化对象,一般默认为self,不过该参数在python3中默认

super()是一个特殊的类,调用super得到一个对象,该对象指向父类的名称空间。

派生与继承解决问题:子类重用父类的属性,并派生出新的属性。

注意:使用哪一种都可以,但不能两种方式混合使用!

11 继承派生机制的作用

可以将一些共用的功能加在基类中,实现代码的共享;

在不改变基类的基础上改变原有的功能;

练习:

list类里只有append向末尾加一个元素的方法,但没有向列表头部添加元素的方法,试想能否为列表在不改变原有功能的基础上添加一个inster_head(x)方法,此方法能在列表的前部添加元素?

  1. class Mylist(list):
  2. def insert_head(self,x):
  3. # self.reverse()
  4. # self.append(x)
  5. # self.reverse()
  6. self.insert(0,x) #直接在最开始插入x
  7. myl = Mylist(range(3,6))
  8. print(myl) #[3.4.5]
  9. myl.insert_head(2)
  10. print(myl) #[2,3,4,5]
  11. myl.append(6)
  12. print(myl) #[2,3,4,5,6]

12 Super()

super(cls,obj)返回被绑定超类的实例(要求obj必须为cls类型的实例)

super() 返回被绑定超类的实例,等同于:super(class,实例方法的第一个参数,必须在方法内调用)

格式:

  1. 父类类名.方法名称(self) 或者 super().方法名称()或者super(本类类名,对象名)

作用:借助super()返回的实例间接调用父类的覆盖方法;

示例:

  1. #此示例示意用super函数间接调用父类的
  2. class A:
  3. def work(self):
  4. print('A.work被调用')
  5. class B(A):
  6. '''B类继承子A类'''
  7. def work(self):
  8. print('B.work被调用')
  9. def super_work(self):
  10. #调用b类自己的work方法
  11. self.work()
  12. #调用父类的work
  13. super(B,self).work()
  14. super().work() #此种调用方式只能在实例方法内调用
  15. b = B()
  16. # b.work() #B.work被调用!!
  17. # super(B,b).work() #A.work被调用
  18. b.super_work()
  19. # super_work() #出错

调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且继承顺序严格遵循mro继承序列;

示例:

  1. class Father1:
  2. x =10
  3. pass
  4. class Father2:
  5. x = 20
  6. pass
  7. #多继承的情况下,从左到右
  8. class Sub(Father1,Father2):
  9. def __init__(self): #注意__int__不是__init__
  10. print(super().__delattr__)
  11. print(Sub.mro()) # [<class '__main__.Sub'>, <class '__main__.Father1'>, <class '__main__.Father2'>, <class 'object'>]
  12. obj = Sub()
  13. print(object) #<class 'object'>

mro():会把当前类的继承关系列出来,严格按照mro列表的顺序往后查找

  1. class A: #默认继承object
  2. def test(self):
  3. print('from A.test')
  4. super().test()
  5. class B:
  6. def test(self):
  7. print('from B.test')
  8. class C(A, B):
  9. pass
  10. c = C()
  11. #检查super的继承顺序
  12. #mro(): 会把当前类的继承关系列出来。
  13. print(C.mro()) #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
  14. c.test() #from A.test
  15. #from B.test

使用super调用父类中的方法,注意分析程序的执行顺序。

  1. class Parent(object):
  2. def __init__(self, name, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
  3. print('parent的init开始被调用')
  4. self.name = name
  5. print('parent的init结束被调用')
  6. class Son1(Parent):
  7. def __init__(self, name, age, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
  8. print('Son1的init开始被调用')
  9. self.age = age
  10. super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
  11. print('Son1的init结束被调用')
  12. class Son2(Parent):
  13. def __init__(self, name, gender, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
  14. print('Son2的init开始被调用')
  15. self.gender = gender
  16. super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
  17. print('Son2的init结束被调用')
  18. class Grandson(Son1, Son2):
  19. def __init__(self, name, age, gender):
  20. print('Grandson的init开始被调用')
  21. # 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
  22. # 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
  23. # super(Grandson, self).__init__(name, age, gender) 效果和下面的一样
  24. super().__init__(name, age, gender)
  25. print('Grandson的init结束被调用')
  26. print(Grandson.__mro__) #搜索顺序
  27. gs = Grandson('grandson', 12, '男')
  28. print('姓名:', gs.name)
  29. print('年龄:', gs.age)
  30. print('性别:', gs.gender)
  31. '''结果如下:
  32. (<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
  33. Grandson的init开始被调用
  34. Son1的init开始被调用
  35. Son2的init开始被调用
  36. parent的init开始被调用
  37. parent的init结束被调用
  38. Son2的init结束被调用
  39. Son1的init结束被调用
  40. Grandson的init结束被调用
  41. 姓名:grandson
  42. 年龄:12
  43. 性别:男
  44. '''

注意:在上面模块中,当在子类中通过super调用父类方法时,parent被执行了1次。

super调用过程:上面gs初始化时,先执行grandson中init方法, 其中的init有super调用,每执行到一次super时,都会从__mro__方法元组中顺序查找搜索。

所以先调用son1的init方法,在son1中又有super调用,这个时候就就根据__mro__表去调用son2的init,然后在son2中又有super调用,这个就根据mro表又去调用parent中的init,直到调用object中的init。

所以上面的打印结果如此,要仔细分析执行过程。

重点提示:

  • 1)super().__init__相对于类名.init,在单继承上用法基本无差
  • 2)但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果。
  • 3)多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错。
  • 4)单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错。
  • 5)多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法。

这也是为何多继承需要全部传参的一个原因

练习一

子类调用自己的方法的时候同时调用父类的方法

  1. class Dog(Animal):
  2. def sleep(self):
  3. # 方法一 父类.方法名(对象)
  4. # Animal.eat(self)
  5. # 方法二 super(子类,对象名).方法名()
  6. super(Dog,dog).sleep()
  7. print('去狗窝')
  8. def look_door(self):
  9. print('看门狗')
  10. dog = Dog('哈巴狗',23)
  11. dog.sleep()
  12. '''
  13. 睡觉
  14. 去狗窝
  15. '''

练习二

在类的外部调用super()

  1. # 类外部调用super()
  2. dog = Dog('哈巴狗', 23)
  3. super(Dog, dog).sleep() # super(子类,对象名).方法名()
  4. dog.sleep()
  5. '''
  6. 睡觉
  7. 去狗窝
  8. '''

到此这篇关于python语法学习之super(),继承与派生的文章就介绍到这了,更多相关python 继承与派生内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号