首页 > 代码库 > python篇1.12.2---继承,多态与多态性,封装
python篇1.12.2---继承,多态与多态性,封装
1.什么是继承
继承是一种创建 新的类的 方式。
在python中,新的类可继承自一个或多个父类,原始的类称为 基类或超类,新建的类 称为 派生类或子类。
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #多继承,用逗号分开 pass
查看继承
print(SubClass1.__bases__) print(SubClass2.__bases__) #输出结果 (<class ‘__main__.ParentClass1‘>,) (<class ‘__main__.ParentClass1‘>, <class ‘__main__.ParentClass2‘>)
提示:若没有指定的基类,Python的类会默认 继承object类。object是python类的 基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__ (<class ‘object‘>,) >>> ParentClass2.__bases__ (<class ‘object‘>,)
2.继承与抽象(先抽象在继承)
抽象即抽取 相似或比较像 的部分。
抽象分成两个层次:
1.将 奥巴马和梅西 这俩对象比较想的部分抽取成 类
2.将人,猪,狗 这三个类比较像的部分抽取成 父类
抽取最主要的作用是 划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它, 肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结果。
抽象只是分析和设计的过程中,一个动作或者一种技巧,通过抽象可以得到类
3.继承与重用性
==========================第一部分 例如 猫可以:喵喵叫、吃、喝、拉、撒 狗可以:汪汪叫、吃、喝、拉、撒 如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下: #猫和狗有大量相同的内容 class 猫: def 喵喵叫(self): print ‘喵喵叫‘ def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something class 狗: def 汪汪叫(self): print ‘喵喵叫‘ def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something ==========================第二部分 上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现: 动物:吃、喝、拉、撒 猫:喵喵叫(猫继承动物的功能) 狗:汪汪叫(狗继承动物的功能) 伪代码如下: class 动物: def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 猫(动物): def 喵喵叫(self): print ‘喵喵叫‘ # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 狗(动物): def 汪汪叫(self): print ‘喵喵叫‘ ==========================第三部分 #继承的代码实现 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()
在开发程序过程中,如果我们定义了一个类A,然后又想新建立另一个类B,但是类B的大部分内容与类A的相同时,我们不可能从头开始写一个类B,这就用到了 类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
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(‘{} move forward‘.format(self.nickname)) def move_backward(self): print(‘{} move backward‘.format(self.nickname)) def move_left(self): print(‘{} move left‘.format(self.nickname)) def move_right(self): print(‘{} move right‘.format(self.nickname)) def attack(self, enemy: object) -> object: 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
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.
注意:像g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Riven(Hero): camp=‘Noxus‘ def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类 print(‘from riven‘) def fly(self): #在自己这里定义新的 print(‘%s is flying‘ %self.nickname)
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值
class Riven(Hero): camp=‘Noxus‘ def __init__(self,nickname,aggressivity,life_value,skin): Hero.__init__(self,nickname,aggressivity,life_value) #调用父类功能 self.skin=skin #新属性 def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类 Hero.attack(self,enemy) #调用功能 print(‘from riven‘) def fly(self): #在自己这里定义新的 print(‘%s is flying‘ %self.nickname) r1=Riven(‘锐雯雯‘,57,200,‘比基尼‘) r1.fly() print(r1.skin) ‘‘‘ 运行结果 锐雯雯 is flying 比基尼 ‘‘‘
4.组合与重用性
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
其实早在以前我们就体会了组合的用法,比如一个英雄有一个装备
>>> class Equip: #武器装备类 ... def fire(self): ... print(‘release Fire skill‘) ... >>> class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类 ... camp=‘Noxus‘ ... def __init__(self,nickname): ... self.nickname=nickname ... self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性 ... >>> r1=Riven(‘锐雯雯‘) >>> r1.equip.fire() #可以使用组合的类产生的对象所持有的方法 release Fire skill
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
1.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种‘是‘的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师
>>> 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
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程
class BirthDate: def __init__(self,year,month,day): self.year=year self.month=month self.day=day class Couse: def __init__(self,name,price,period): self.name=name self.price=price self.period=period class Teacher: def __init__(self,name,gender): self.name=name self.gender=gender def teach(self): print(‘teaching‘) class Professor(Teacher): def __init__(self,name,gender,birth,course): Teacher.__init__(self,name,gender) self.birth=birth self.course=course p1=Professor(‘egon‘,‘male‘, BirthDate(‘1995‘,‘1‘,‘27‘), Couse(‘python‘,‘28000‘,‘4 months‘)) print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period) ‘‘‘ 运行结果: 1995 1 27 python 28000 4 months
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
5.接口与归一化设计
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
6.
7.继承实现的原理(继承顺序)
1 继承顺序
##例子
例子1.Python3(都是新式类)代码:
class A(object): def test(self): print(‘from A‘) class B(A): #寻找的 顶点,若没有,转向右边 # def test(self): # print(‘from B‘) pass class C(A): def test(self): print(‘from C‘) #结束左边的寻找,之后是右边的 # pass class D(B): # def test(self): # print(‘from D‘) pass class E(C): # def test(self): # print(‘from E‘) pass 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中才分新式类与经典类
例子2.
#新式类的继承,在查找属性时遵循:广度优先 class X(object): # def test(self): # print(‘from X‘) pass class Y(object): def test(self): print(‘from Y‘) # pass class B(X): # def test(self): # print(‘from B‘) pass class C(Y): # def test(self): # print(‘from C‘) pass class D(B): # def test(self): # print(‘from D‘) pass class E(C): # def test(self): # print(‘from E‘) pass class F(D,E): # def test(self): # print(‘from F‘) pass f1=F() f1.test() #F--->D---->B--->X---> E---->C---->Y---->obje
2.继承原理(python如何实现的继承)
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如:
>>> F.mro() #等同于F.__mro__ [<class ‘__main__.F‘>, <class ‘__main__.D‘>, <class ‘__main__.B‘>, <class ‘__main__.E‘>, <class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘object‘>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
8.子类中调用的父类方法
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法.
使用super():
#在Python3中 class People: def __init__(self,name,sex,age): self.name = name self.sex = sex self.age = age def walk(self): print(‘{} is walking ‘.format(self.name)) class Chinese(People): country = ‘China‘ def __init__(self,name,sex,age,language=‘Chinese‘): # self.name=name # self.sex=sex # self.age=age # People.__init__(self,name,sex,age) super().__init__(name,sex,age) ##代替 上面四个注释的内容 self.language = language def walk(self,x): super().walk() print(‘子类的x‘,x) c = Chinese(‘egon‘,‘male‘,18) c.walk(333) ###运行结果 egon is walking 子类的x 333
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
多态与多态性
很多人喜欢将二者混为一谈,然后百思不得其解,其实只要分开看,就会很明朗
多态
多态指的是一类事物有多种形态。
动物分为人类,猪类 (在定义角度)
1. 序列类型有多种形态:字符串,列表,元组。 (处在 定义角度)
2. 动物有多种形态:人类,狗类,猪类 (处在 定义角度)
#多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度) class Animal: def run(self): raise AttributeError(‘子类必须实现这个方法‘) class People(Animal): def run(self): print(‘人正在走‘) class Pig(Animal): def run(self): print(‘pig is walking‘) class Dog(Animal): def run(self): print(‘dog is running‘) peo1 = People() pig1 = Pig() d1 = Dog() peo1.run() pig1.run() d1.run() ###运行结果 人正在走 pig is walking dog is running
多态性
一 什么是多态性(请务必注意注意注意:多态与多态性是两种概念。)
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算
动态多态性:如下
1.
2。
def func(obj): #围绕此的应用 obj.run()
#多态性:一种调用方式,不同的执行效果(多态性) class Animal: def run(self): raise AttributeError(‘子类必须实现这个方法‘) class People(Animal): def run(self): print(‘人正在走‘) class Pig(Animal): def run(self): print(‘pig is walking‘) class Dog(Animal): def run(self): print(‘dog is running‘) peo1 = People() pig1 = Pig() d1 = Dog() def func(obj): #obj这个参数没有类型限制,可以传入不同类型的值 obj.run() #调用的逻辑都一样,执行的结果却不一样 func(peo1) func(pig1) func(d1) ##执行结果 人正在走 pig is walking dog is running
综上我们也可以说,多态性是‘一个接口(函数func),多种实现(如obj.run())’
二 为什么要用多态性(多态性的好处)
其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
>>> class Cat(Animal): #属于动物的另外一种形态:猫 ... def talk(self): ... print(‘say miao‘) ... >>> def func(animal): #对于使用者来说,自己的代码根本无需改动 ... animal.talk() ... >>> cat1=Cat() #实例出一只猫 >>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能 say miao ‘‘‘ 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1) ‘‘‘
封装
从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,然后把麻袋封上口子。但其实这种理解相当片面
首先我们要了解
1.要封装什么
你钱包有多少钱(数据的封装)
你的性取向(数据的封装)
你撒尿的具体功能是怎么实现的(方法的封装)
2.为什么要封装
封装数据的主要原因是:保护隐私(作为男人的你,脸上就写着:我喜欢男人,你害怕么?)
封装方法的主要原因是:隔离复杂度(快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了,比如你不必知道你自己的尿是怎么流出来的,你直接掏出自己的接口就能用尿这个功能)
你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。还有你的头把你的脑子封装到了脑壳里,然后提供了眼睛这个接口....
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),就是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
3.封装分为两个层面
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
>>> r1.nickname ‘草丛伦‘ >>> Riven.camp ‘Noxus‘
注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
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,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
python篇1.12.2---继承,多态与多态性,封装