一、 继承顺序
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class A( object ): def test( self ): print ( 'from A' ) class B(A): def test( self ): print ( 'from B' ) class C(A): def test( self ): print ( 'from C' ) class D(B): def test( self ): print ( 'from D' ) class E(C): def test( self ): print ( 'from E' ) class F(D,E): # def test(self): # print('from F') pass f1 = F() f1.test() print (F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类 |
二、 继承原理(python如何实现的继承)
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
1 2 |
>>> F.mro() #等同于F.__mro__ [< class '__main__.F' >, < class '__main__.D' >, < class '__main__.B' >, < class '__main__.E' >, < class '__main__.C' >, < class '__main__.A' >,<br> < class 'object' >] |
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止
合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
三、子类中调用父类方法(super()方法)
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法
方法一 父类名.父类方法()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class People: def __init__( self ,name,sex,age): self .name = name self .sex = sex self .age = age def walk( self ): print ( '%s is walking' % self .name) class Chinese(People): country = 'China' def __init__( self ,name,sex,age,language = 'Chinses' ): People.__init__( self ,name,sex,age) self .language = language def walk( self ,x): pass c = Chinese( 'xiaojing' , 'male' , 20 ) print (c.name,c.sex,c.age,c.language) |
方法二 super()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class People: def __init__( self ,name,sex,age): self .name = name self .age = age self .sex = sex def walk( self ): print ( '%s is walking' % self .name) class Chinese(People): country = 'China' def __init__( self ,name,sex,age,language = 'Chinese' ): super ().__init__(name,sex,age) #super() 绑定 调用父类方法 self .language = language def walk( self ,x): super ().walk() print ( "---->子类的x" ,x) c = Chinese( 'EGG' , 'male' , '20' ) c.walk( 123 ) |
不用super引发的惨案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#每个类中都继承了且重写了父类的方法 class A: def __init__( self ): print ( 'A的构造方法' ) class B(A): def __init__( self ): print ( 'B的构造方法' ) A.__init__( self ) class C(A): def __init__( self ): print ( 'C的构造方法' ) A.__init__( self ) class D(B,C): def __init__( self ): print ( 'D的构造方法' ) B.__init__( self ) C.__init__( self ) pass f1 = D() print (D.__mro__) #python2中没有这个属性 |
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#每个类中都继承了且重写了父类的方法 class A: def __init__( self ): print ( 'A的构造方法' ) class B(A): def __init__( self ): print ( 'B的构造方法' ) super (B, self ).__init__() class C(A): def __init__( self ): print ( 'C的构造方法' ) super (C, self ).__init__() class D(B,C): def __init__( self ): print ( 'D的构造方法' ) super (D, self ).__init__() f1 = D() print (D.__mro__) #python2中没有这个属性 |
一、什么是继承:
继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类成为基类或超累,新建的类成为派生类或子类
1.1 继承分为:单继承和多继承
1 2 3 4 5 6 7 8 9 10 11 |
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是parentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分割开多个继承的类 pass |
1.2 查看继承
>>> SubClass1.__bases__
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
1 2 3 4 |
>>> ParentClass1.__bases__ (< class 'object' >,) >>> ParentClass2.__bases__ (< class 'object' >,) |
1 为什么要封装
封装数据的主要原因是:保护隐私
封装方法的主要原因是:隔离复杂度
2 封装分为两个层面
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
1 2 3 4 |
>>> r1.nickname '草丛伦' >>> Riven.camp 'Noxus' |
注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
1 2 3 4 5 6 7 8 |
class A: __N = 0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__( self ): self .__X = 10 #变形为self._A__X def __foo( self ): #变形为_A__foo print ( 'from A' ) def bar( self ): self .__foo() #只有在类内部才可以通过__foo的形式访问到. |
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
2.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
1 2 3 4 5 6 7 |
>>> a = A() >>> a._A__N 0 >>> a._A__X 10 >>> A._A__N 0 |
2.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#正常情况 >>> class A: ... def fa( self ): ... print ( 'from A' ) ... def test( self ): ... self .fa() ... >>> class B(A): ... def fa( self ): ... print ( 'from B' ) ... >>> b = B() >>> b.test() from B |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#把fa定义成私有的,即__fa >>> class A: ... def __fa( self ): #在定义时就变形为_A__fa ... print ( 'from A' ) ... def test( self ): ... self .__fa() #只会与自己所在的类为准,即调用_A__fa ... >>> class B(A): ... def __fa( self ): ... print ( 'from B' ) ... >>> b = B() >>> b.test() from A |
1.3 继承与抽象(先抽象在继承)
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Animal: def __init__( self ,name,age): self .name = name self .age = age def walk( self ): def say( self ): print ( '%s is saying' % self .name) class People(Animal): pass class Pig(Animal): pass class Dog(Animal): pass p1 = People( "obama" , 50 ) print (p1.name) print (p1.age) p1.walk() #先找walk名字 找相应的值,去父类找 p1.say() |
1 2 3 4 |
obama 50 obama is walking obama is saying |
1.4 继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class Hero: def __init__( self ,nickname,aggressivity,life_value): self .nickname = nickname self .aggressivity = aggressivity self .life_value = life_value def move_forward( self ): print ( '%s move forward' % self .nickname) def move_backward( self ): print ( '%s move backward' % self .nickname) def move_left( self ): print ( '%s move forward' % self .nickname) def move_right( self ): print ( '%s move forward' % self .nickname) def attack( self ,enemy): enemy.life_value - = self .aggressivity class Garen(Hero): pass class Riven(Hero): pass g1 = Garen( '草丛伦' , 100 , 300 ) r1 = Riven( '锐雯雯' , 57 , 200 ) print (g1.life_value) r1.attack(g1) print (g1.life_value) ''' 运行结果 300 243 ''' |
1.5 继承与派生
1、派生,父类里面没有的,在子类里面定义独有的,派生出新的东西。
2、派生出父类里面重名的东西,再找先找用子类自己的构造方法。
例子:父类里面没有的,在子类里面定义独有的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Hero: def __init__( self ,nicname,aggressivity,life_value): self .nicname = nicname self .aggressivity = aggressivity self .life_value = life_value def attack( self ,enemy): enemy.life_value - = self .aggressivity class Garen(Hero): camp = 'Demacia' class Riven(Hero): camp = 'Noxus' g1 = Garen( 'garen' , 18 , 200 ) r1 = Riven( 'riven' , 18 , 200 ) print (g1.camp) print (r1.camp) |
输出结果为:
父类里面重名的东西,再找先找用子类自己的构造方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Hero: def __init__( self ,nicname,aggressivity,life_value): self .nicname = nicname self .aggressivity = aggressivity self .life_value = life_value def attack( self ,enemy): print ( 'Hero attack' ) enemy.life_value - = self .aggressivity class Garen(Hero): camp = 'Demacia' def attack( self ,enemy): Hero.attack( self ,enemy) print ( 'from garen attack' ) def fire( self ): print ( '%s is firing' % self .nicname) class Riven(Hero): camp = 'Noxus' g1 = Garen( 'garen' , 18 , 200 ) r1 = Riven( 'riven' , 18 , 200 ) print (g1.camp) g1.attack(r1) print (g1.camp) |
输出结果为:
1 2 3 |
Hero attack from garen attack Demacia |
延续第二种:在子类里面添加新的独有的功能,如:“script”garen独有的,但是garen的身份在父类里,可以重用父类的功能
在子类中,重用父类的函数,父类名.父类函数(参数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Hero: def __init__( self ,nicname,aggressivity,life_value): self .nicname = nicname self .aggressivity = aggressivity self .life_value = life_value def attack( self ,enemy): print ( 'Hero attack' ) enemy.life_value - = self .aggressivity class Garen(Hero): camp = 'Demacia' def __init__( self ,nicname,aggressivity,life_value,script): Hero.__init__( self ,nicname,aggressivity,life_value) self .script = script def attack( self ,enemy): Hero.attack( self ,enemy) print ( 'from garen attack' ) g1 = Garen( 'garen' , 18 , 2 , '人在塔在' ) print (g1.script) |
输出结果为:
2、组合
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合对比继承来说,也是用来重用代码,但是组合描述的是一种“有的关系”
2.1 组合的方式:
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如老师学生有python课程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Teacher: def __init__( self ,name,sex,course): self .name = name self .sex = sex self .course = course class Student: def __init__( self ,name,sex,course): self .name = name self .sex = sex self .course = course class Course: def __init__( self ,name,price,period): self .name = name self .price = price self .period = period Course_obj = Course( 'python' , '15800' , '7m' ) t1 = Teacher( 'allen' , 'male' ,Course_obj) s1 = Student( 'susan' , 'male' ,Course_obj) print (s1.course.name) print (t1.course.name) |
输出结果为:
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同
2.2 继承的方式:
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>>> class Teacher: ... def __init__( self ,name,gender): ... self .name = name ... self .gender = gender ... def teach( self ): ... print ( 'teaching' ) ... >>> >>> class Professor(Teacher): ... pass ... >>> p1 = Professor( 'egon' , 'male' ) >>> p1.teach() teaching |