继承和多态
继承
引入继承
我们有这样一个需求
模仿英雄联盟定义两个英雄类
1.英雄要有昵称、攻击力、生命值属性
2.实例化出两个英雄对象
3.英雄之间可以互殴,被殴打的一方掉血,血量小于0则判断为死亡
那我们实现的代码是这样的
class Gailun:
camp = 'demaxiya' # 定义英雄阵营
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵称
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻击力
def attack(self,enemy): # 攻击方法
enemy.life_value -= self.aggrensivity
class Ruiwen:
camp = 'aioniya'
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵称
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻击力
def attack(self,enemy): # 攻击方法
enemy.life_value -= self.aggrensivity
g1 = Gailun('盖伦',100,50) # 实例化对象 # 昵称:盖伦、生命值:100、攻击力:50
r1 = Ruiwen('瑞文',50,100)
print('瑞文原来生命值:',r1.life_value)
g1.attack(r1)
print('瑞文现在生命值:',r1.life_value)
# 运行结果为
瑞文原来生命值: 50
瑞文现在生命值: 0
我们仔细看代码可以发现,在两个类中有很多的重复代码,初始化方法和attack方法,那么我们学习面向对象就是来较少代码重复量的,有没有办法解决呢?答案是肯定有的
初识继承
继承,指的是类与类之间的关系,是一种xx是xx的关系,继承的功能之一就是用来解决代码重用问题
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可以称为基类或超类,新建的类称为派生类或子类
让我们继续看下刚刚的那个例子,假设没有英雄阵营(等下会讲),Gailun类和Ruiwen类都是英雄,他们都有昵称、生命值、攻击力属性,都有attack攻击方法,所以...Gailun类和Ruiwen类能不能都属于Hero类呢?
class Hero: # 定义一个Hero英雄类----父类
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵称
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻击力
def attack(self,enemy): # 攻击方法
enemy.life_value -= self.aggrensivity
class Gailun(Hero): # 定义类时在类名后面()写上Hero,则代表继承Hero类----子类
pass
class Ruiwen(Hero): # ----子类
pass
g1 = Gailun('盖伦',100,50) # 昵称:盖伦、生命值:100、攻击力:50
r1 = Ruiwen('瑞文',50,100)
print('瑞文原来生命值:',r1.life_value)
g1.attack(r1)
print('瑞文现在生命值:',r1.life_value)
# 运行结果如下:
瑞文原来生命值: 50
瑞文现在生命值: 0
在这段代码中,Gailun类和Ruiwen类全部都是继承了Hero类,所有的属性和方法都是通过继承得到的,所以我们刚刚说继承就是xx是xx的关闭,比如Gailun是英雄、Ruiwen也是英雄,那么我们就可以通过抽象他们直接相似的地方,总结类之间相似的特征得到父类,则父类中的东西子类全部继承,这样就解决了代码重用问题
单继承和多继承
上面我们说过,在python中是支持单继承和多继承的,让我们写一段伪代码来看看:
class ParentsClass1: # ----父类1
pass
class ParentsClass2: # ----父类2
pass
# 1.继承一个父类
class SubClass1(ParentsClass1): # ----继承父类1
pass
# 2.继承多个父类
class SubClass2(ParentsClass1,ParentsClass2): # ----继承父类1和父类2
pass
我们该如何查看继承呢?
print(SubClass1.__bases__)
print(SubClass2.__bases__)
# 运行结果为
(<class '__main__.ParentsClass1'>,) # SubClass1继承ParentsClass1
(<class '__main__.ParentsClass1'>, <class '__main__.ParentsClass2'>)
继承中的属性查找
实例化出一个对象后,访问这个对象的属性是如何查找的呢?首先会从对象本身开始查找,如果没有的话,就去类里面找,如果还没有的话,就去父类中,父类之上可能还会有父类, 找不到才会报错
# 属性查找小练习
class Foo:
def f1(self):
print('from Foo.f1')
def f2(self):
print('From Foo.f2')
class Bar(Foo):
def f2(self):
print('From Bar.f2')
b = Bar()
print(b.__dict__) # 这句话代表去这个对象b里面去查找属性,但是这个对象中并没有__init__方法,所以就没有任何属性,打印结果为空
b.f2()
# 运行结果:
{}
From Bar.f2
# 小练习的修改版
class Foo:
def f1(self):
print('from Foo.f1')
def f2(self):
print('From Foo.f2')
class Bar(Foo):
def f1(self):
print('From Bar.f1')
b = Bar()
b.f2() # 在对象和类中都没有找到这个方法,所以会去类里面找
# 运行结果:
From Foo.f2
派生类
在刚刚的Gailun和Ruiwen中,我们刚刚假设了没有英雄阵营,在理论上,子类不应该一无所有,子类应该有自己的属性
class Hero: # 定义一个Hero英雄类----父类
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵称
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻击力
def attack(self,enemy): # 攻击方法
enemy.life_value -= self.aggrensivity
class Gailun(Hero): # 定义类时在类名后面()写上Hero,则代表继承Hero类----子类
camp = 'demaxiya'
class Ruiwen(Hero): # ----子类
camp = 'aioniya'
g1 = Gailun('盖伦',100,50) # 昵称:盖伦、生命值:100、攻击力:50
r1 = Ruiwen('瑞文',50,100)
print('盖伦来自于:',g1.camp)
print('瑞文来自于:',r1.camp)
# 这样,就把自己的属性给加上去了,看看运行结果
盖伦来自于: demaxiya
瑞文来自于: aioniya
重写父类的方法
由于子类继承了父类,所以会把父类的方法和属性全都继承,但是往往继承下来的方法并不是我们想要的,所以这个时候就需要重写父类的方法
class Hero: # 定义一个Hero英雄类----父类
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵称
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻击力
def attack(self,enemy): # 攻击方法
enemy.life_value -= self.aggrensivity
class Gailun(Hero): # 定义类时在类名后面()写上Hero,则代表继承Hero类----子类
camp = 'demaxiya'
def attack(self,enemy): # 重写了父类中的attack方法
print('This is Gailun Class')
class Ruiwen(Hero): # ----子类
camp = 'aioniya'
g1 = Gailun('盖伦',100,50) # 昵称:盖伦、生命值:100、攻击力:50
r1 = Ruiwen('瑞文',50,100)
g1.attack(r1)
# 运行结果
This is Gailun Class
# 因为我们一直在说属性查找,属性是从当前对象开始查找,因为继承了Hero类,所以有nickname,life_value,aggrensivity属性,然后去类里面找,因为是重写父类的方法, 所以在类中找到了attack方法,就不用再去管父类中的attack方法了
继承实现的原理
python到底是如何实现继承的呢?对于你每定义的一个类,python会计算出一个方法解析顺序MRO列表,这个mro列表就是一个简单的所有基类的线性顺序列表,比如:
class Hero: # 定义一个Hero英雄类----父类
def __init__(self,nickname,life_value,aggrensivity): # 初始化
self.nickname = nickname # 昵称
self.life_value = life_value # 生命值
self.aggrensivity = aggrensivity # 攻击力
def attack(self,enemy): # 攻击方法
enemy.life_value -= self.aggrensivity
class Gailun(Hero): # 定义类时在类名后面()写上Hero,则代表继承Hero类----子类
camp = 'demaxiya'
def attack(self,enemy):
print('This is Gailun Class')
class Ruiwen(Hero): # ----子类
camp = 'aioniya'
g1 = Gailun('盖伦',100,50) # 昵称:盖伦、生命值:100、攻击力:50
r1 = Ruiwen('瑞文',50,100)
print(Gailun.mro())
# 打印结果为
[<class '__main__.Gailun'>, <class '__main__.Hero'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,知道找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的,我们不去研究这个算法的数字原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查(对象比类先检查)
- 多个父类会根据它们在列表中的数据被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
在前面部分我们说到,python既可以支持单继承也可以支持多继承,如果子类继承了多个父类,那么属性的查找方式就有两种:深度优先和广度优先
在讲深度优先和广度优先之前,我们先讲一下新式类和经典类
在python中分为两种类,但是这个概念只是在python2中才会有,在python3中都是新式类。
在python2中,经典类:没有继承object的类,以及它的子类都称为经典类
class Parent:
pass
class Child(Parent):
pass
在python2中,新式类:继承object的类,以及它的子类都称为新式类
class Parent1(object):
pass
class Child1(Parent1):
pass
但是在python3中,没有了经典类和新式类的区分,全部都是新式类,不管有没有继承object
class Foo:
pass
class Bar(Foo):
pass
# 或者是
class Foo(object):
pass
class Bar(Foo):
pass
怎么会讲到新式类和经典类呢? 因为新式类的查找顺序是广度优先,而经典类的查找顺序是深度优先
- 深度优先:
- 广度优先:
举例代码
class A:
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() # 先找到test属性,然后发现test是一个方法,加上括号就可以运行了
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性
# 运行结果
from D # 这里可以知道,继承多个父类,是从第一个父类里面进去查找方法的
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
但是这段代码在python2中运行结果是不一样的,因为python2中父类没有继承object,那么它和它的子类都被称为经典类
import inspect
class A:
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() # 先找到test属性,然后发现test是一个方法,加上括号就可以运行了
print(inspect.getmro(F))
# 运行结果如下
self D
(<class __main__.F at 0x7fdcf68dea78>, <class __main__.D at 0x7fdcf68de9a8>, <class __main__.B at 0x7fdcf68de8d8>, <class __main__.A at 0x7fdcf68de870>, <class __main__.E at 0x7fdcf68dea10>, <class __main__.C at 0x7fdcf68de940>)
这样,我们就看出来深度优先和广度优先的区别了
在子类中调用父类的方法
在子类派生出的新方法中,往往需要重用父类的方法,例如:在父类的初始化方法中,有name,age这两个属性,作为子类,我们继承了父类,有着和父类一样的name,age,但是子类中还想有一个addr属性,这个要如何添加呢?我们用之前的例子举例:
class Hero:
def __init__(self,nickname,life_value,aggrensivity):
self.nickname = nickname
self.life_value = life_value
self.aggrensivity = aggrensivity
def attack(self,enemy):
enemy.life_value -= self.aggrensivity
class Gailun(Hero):
camp = 'demaxiya'
class Ruiwen(Hero):
camp = 'aioniya'
g1 = Gailun('盖伦',100,50)
r1 = Ruiwen('瑞文',50,100)
g1.attack(r1)
print(r1.life_value)
这段代码我们已经完成了,那么我们看一下这个,在父类中有着初始化方法,在子类中同样也有这个方法,那么我问你,继承的作用是什么?继承是解决类与类之间的关系,减少代码重用的问题,但是在这里代码很明显重复了,需要如何修改呢?
第一种方法:指名道姓(不依赖继承)
class Hero:
def __init__(self,nickname,life_value,aggrensivity):
self.nickname = nickname
self.life_value = life_value
self.aggrensivity = aggrensivity
def attack(self,enemy):
enemy.life_value -= self.aggrensivity
class Gailun(Hero):
camp = 'demaxiya'
def attack(self,enemy): # 子类重写父类方法
Hero.attack(self,enemy) # 指名道姓的方法,告诉Hero,这个类的attak我要调用
print('这是Gailun Class')
class Ruiwen(Hero):
camp = 'aioniya'
g1 = Gailun('盖伦',100,50)
r1 = Ruiwen('瑞文',50,100)
g1.attack(r1)
print('瑞文现在生命值:',r1.life_value)
# 运行结果为:
这是Gailun Class
0
第二种方法:super方法(依赖继承)
# 要给盖伦添加一个武器属性
class Hero:
def __init__(self,nickname,life_value,aggrensivity):
self.nickname = nickname
self.life_value = life_value
self.aggrensivity = aggrensivity
def attack(self,enemy):
enemy.life_value -= self.aggrensivity
class Gailun(Hero):
camp = 'demaxiya'
def __init__(self,nickname,life_value,aggrensivity,weapon):
super().__init__(nickname,life_value,aggrensivity)
self.weapon = weapon
def attack(self,enemy):
Hero.attack(self,enemy)
print('这是Gailun Class')
class Ruiwen(Hero):
camp = 'aioniya'
g1 = Gailun('盖伦',100,50,'金箍棒')
r1 = Ruiwen('瑞文',50,100)
print(g1.__dict__)
g1.attack(r1)
print('瑞文现在生命值:',r1.life_value)
# 运行结果为
{'nickname': '盖伦', 'life_value': 100, 'aggrensivity': 50, 'weapon': '金箍棒'}
这是Gailun Class
瑞文现在生命值: 0
这两种方式的区别是:方式一是和继承没有关系的,而方法二是依赖于继承的,并且即使没有直接继承关系,super仍然会按照MRO列表继续往后查找
super()是如何依赖于继承呢?只要有子类继承了多个父类,那么python就会计算出MRO列表,而super就是在MRO列表中去找属性
举例说明
class A:
def test(self):
print('from A.test')
super().test()
class B:
def test(self):
print('from B.test')
class C(A,B):
pass
print(C.__mro__) # 只是打印C的MRO列表,即属性的查找顺序
# 运行结果:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
我们现在实例化一个对象看看:
c = C()
c.test()
# 运行结果
from A.test
from B.test
那如果没有加上super方法呢?
class A:
def test(self):
print('from A.test')
class B:
def test(self):
print('from B.test')
class C(A,B):
pass
c = C()
c.test()
# 运行结果如下:只会打印一个,因为对于这个对象而言,已经找到了test属性了
from A.test
组合
在刚刚讲到继承的时候,我们说过:继承就是解决类与类之间的关系,它是xx是xx的关系,例如:瑞文是英雄,德玛西亚是英雄这些,使用继承的好处就是可以重用代码,那么组合是什么呢?就是xx有xx类的关系,例如:老师有课程类,学生有课程类,把老师类和课程类组合在一起就叫做组合,通过组合,可以把很多类组合在一起
用组合的方式建立了类与组合的类之间的关系,它是一种'有'的关系,比如:教授有生日,教授教云计算基础和linux基础,教授有学生s1,s2,s3
class People: # 定义父类----People
school = 'zhcpt'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People): # 定义子类Teacher继承父类People,但是老师要有等级,要有薪水,就要用到上面的重用父类的方法
def __init__(self,name,age,sex,level,salary):
super().__init__(name,age,sex)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching'%self.name)
class Student(People): # 定义子类Student继承父类People,学生要有上课时间
def __init__(self,name,age,sex,class_time):
super().__init__(name,age,sex)
self.class_time = class_time
def learn(self):
print('%s is learning'%self.name)
# 实例化对象
t1 = Teacher('方明清',35,'male',10,10000)
s1 = Student('肖亚飞',22,'male','08:30:00')
那么老师有课程即老师有课程类如何组合的呢?
class People: # 定义父类----People
school = 'zhcpt'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People): # 定义子类Teacher继承父类People,但是老师要有等级,要有薪水,就要用到上面的重用父类的方法
def __init__(self,name,age,sex,level,salary):
super().__init__(name,age,sex)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching'%self.name)
def tell_info(self):
print('姓名:%s,年龄:%s,性别:%s,等级:%s,薪水:%s'%(self.name,self.age,self.sex,self.level,self.salary))
class Student(People): # 定义子类Student继承父类People,学生要有上课时间
def __init__(self,name,age,sex,class_time):
super().__init__(name,age,sex)
self.class_time = class_time
def learn(self):
print('%s is learning'%self.name)
class Course:
def __init__(self,course_name,course_price,course_period):
self.course_name = course_name
self.course_price = course_price
self.course_period = course_period
def tell_info(self):
print('课程:<%s>\t\t价格:<%s>\t\t学习周期:<%s>'%(self.course_name,self.course_price,self.course_period))
# 实例化对象
t1 = Teacher('方明清',35,'male',10,10000)
t2 = Teacher('李晓明',38,'female',8,8000)
s1 = Student('肖亚飞',22,'male','08:30:00')
# 添加课程对象
c1 = Course('云计算基础',300,'5mouths')
c2 = Course('Linux基础',800,'3mouths')
# 把老师类和课程类组合,老师有课程
t1.course = c1
t2.course = c2
# 打印t1和t2老师的信息和课程
t1.tell_info()
t1.course.tell_info()
print()
t2.tell_info()
t2.course.tell_info()
# 运行结果为
姓名:方明清,年龄:35,性别:male,等级:10,薪水:10000
课程:<云计算基础> 价格:<300> 学习周期:<5mouths>
姓名:李晓明,年龄:38,性别:female,等级:8,薪水:8000
课程:<Linux基础> 价格:<800> 学习周期:<3mouths>
组合2举例
class People: # 定义父类----People
school = 'zhcpt'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People): # 定义子类Teacher继承父类People,但是老师要有等级,要有薪水,就要用到上面的重用父类的方法
def __init__(self,name,age,sex,level,salary):
super().__init__(name,age,sex)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching'%self.name)
def tell_info(self):
print('姓名:%s,年龄:%s,性别:%s,等级:%s,薪水:%s'%(self.name,self.age,self.sex,self.level,self.salary))
class Student(People): # 定义子类Student继承父类People,学生要有上课时间
def __init__(self,name,age,sex,class_time):
super().__init__(name,age,sex)
self.class_time = class_time
def learn(self):
print('%s is learning'%self.name)
class Course:
def __init__(self,course_name,course_price,course_period):
self.course_name = course_name
self.course_price = course_price
self.course_period = course_period
def tell_info(self):
print('课程:<%s>\t\t价格:<%s>\t\t学习周期:<%s>'%(self.course_name,self.course_price,self.course_period))
class data:
def __init__(self,year,mon,day):
self.year = year
self.mon = mon
self.day = day
def tell_info(self):
print('%s-%s-%s'%(self.year,self.mon,self.day))
# 实例化对象
t1 = Teacher('方明清',35,'male',10,10000)
t2 = Teacher('李晓明',38,'female',8,8000)
s1 = Student('肖亚飞',22,'male','08:30:00')
# 实例化生日对象
d1 = data('1996','4','30')
# 学生有生日f
s1.birthday = d1
s1.birthday.tell_info() # 打印s1学生的生日信息
# 运行结果为
1996-4-30
总结:
当类之间有显著不同,并且较小的类是较大的类所需要的组建时,用组合比较好
抽象类与归一化
hi boy,给我开个查询接口。。。此时的接口指的是:自己提供给使用者来调用自己功能的方式\方法\入口,java中的interface使用如下
=================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java
/*
* Java的Interface接口的特征:
* 1)是一组功能的集合,而不是一个功能
* 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作
* 3)接口只定义函数,但不涉及函数实现
* 4)这些功能是相关的,都是动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */
package com.oo.demo;
public interface IAnimal {
public void eat();
public void run();
public void sleep();
public void speak();
}
=================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口
package com.oo.demo;
public class Pig implements IAnimal{ //如下每个函数都需要详细实现
public void eat(){
System.out.println("Pig like to eat grass");
}
public void run(){
System.out.println("Pig run: front legs, back legs");
}
public void sleep(){
System.out.println("Pig sleep 16 hours every day");
}
public void speak(){
System.out.println("Pig can not speak"); }
}
=================第三部分:Person2.java
/*
*实现了IAnimal的“人”,有几点说明一下:
* 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样
* 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */
package com.oo.demo;
public class Person2 implements IAnimal {
public void eat(){
System.out.println("Person like to eat meat");
}
public void run(){
System.out.println("Person run: left leg, right leg");
}
public void sleep(){
System.out.println("Person sleep 8 hours every dat");
}
public void speak(){
System.out.println("Hellow world, I am a person");
}
}
=================第四部分:Tester03.java
package com.oo.demo;
public class Tester03 {
public static void main(String[] args) {
System.out.println("===This is a person===");
IAnimal person = new Person2();
person.eat();
person.run();
person.sleep();
person.speak();
System.out.println("\n===This is a pig===");
IAnimal pig = new Pig();
pig.eat();
pig.run();
pig.sleep();
pig.speak();
}
}
# java中的interface
python中举例说明抽象类和归一化:
class People:
def run(self):
print('Prople is run')
class Dog:
def walk(self):
print('Dog is walk')
class Pig:
def zou(self):
print('Pig is zou')
# 实例化对象
p = People()
d = Dog()
p1 = Pig()
# 调用类的方法
p.run()
d.walk()
p1.zou()
这个是我简单写的代码,实现了人、狗、猪走路的功能,这一共就几行代码相信很容易就能看懂了,那么问题来了,如果我只让你看这么个代码,请你告诉我代表着是什么?
class People:...
class Dog:...
class Pig:...
# 实例化对象
p = People()
d = Dog()
p1 = Pig()
# 调用类的方法
p.run()
d.walk()
p1.zou()
请你现在告诉我p.run()代表着会实现什么功能?没有人会知道的,因为类里面的方法被我隐藏了,好了,现在我们开始讲一下归一化
所谓的归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都是一样的
归一化的好处
- 归一化让使用者无需关心对象的类是什么,只需要知道的是这些对象都具有什么功能就好了,这极大的降低了使用者的难度(人、猪、狗,它们都会走路,都会吃,那么能不能定义一个统一的标准,人走路叫run,狗走路也叫run呢?)
- 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合,在linux中,有这么一句说法:一切皆文件,在linux中,所有的东西都可以当成文件来处理,你无需关系它是网络、内存、磁盘、还是屏幕;再比如:你去考取驾照,没有规定你学完只能开宝马或者奥迪,大家只需要拿到驾驶证,本田能不能开?可以,奔驰能不能开?也可以,开的时候无需关心我开的是哪一辆车,操作手法都是一样的。
抽象类
所谓的抽象类,就是把类与类之间相似的部分拿出来然后得到父类,然后让子类去继承父类,这样做的好处就是:让子类继承父类的同时必须按照父类规定的方法来
抽象类本身就是一个类,属性查找的顺序就是:对象->类->父类->父类的父类...,所以在父类中定义属性在所有的子类中就是公用的
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
import abc
class Animal(metaclass=abc.ABCMeta):
all_type = 'animal'
@abc.abstractmethod
def run(self):
pass
class People(Animal):
def run(self):
print('Prople is run')
class Dog(Animal):
def walk(self):
print('Dog is walk')
class Pig(Animal):
def zou(self):
print('Pig is zou')
# 实例化对象
p = People()
dog1 = Dog()
# 调用类的方法
p.run()
dog1.walk()
# 运行结果
Traceback (most recent call last):
File "D:/py_study/day17-继承开始/test.py", line 30, in <module>
dog1 = Dog()
TypeError: Can't instantiate abstract class Dog with abstract methods run
报错......提示要使用run方法
修改为如下:
import abc
class Animal(metaclass=abc.ABCMeta):
all_type = 'animal'
@abc.abstractmethod
def run(self):
pass
class People(Animal):
def run(self):
print('Prople is run')
class Dog(Animal):
def run(self):
print('Dog is walk')
class Pig(Animal):
def run(self):
print('Pig is zou')
# 实例化对象
p = People()
dog1 = Dog()
pig1 = Pig()
# 调用类的方法
p.run()
dog1.run()
pig1.run()
这样,对于使用者来说:不管你是人、狗、猪,都可以使用run方法,run方法都是走路
注意:
抽象类只能被继承,而不能被实例化,它的功能就是规范子类并不是把父类定义出来直接用,而是间接用的
多态与多态性
多态
同一类事物的多种形态叫做多态,例如:动物有多种形态(人、狗、猪)
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao')
文件有多种形态:文本文件和可执行文件
import abc
class File(metaclass=abc.ABCMeta): #同一类事物:文件
@abc.abstractmethod
def click(self):
pass
class Text(File): #文件的形态之一:文本文件
def click(self):
print('open file')
class ExeFile(File): #文件的形态之二:可执行文件
def click(self):
print('execute file')
多态性
多态性指的是在不考虑实例类型的情况下使用实例,多态性分为静态多态性和动态多态性
动态多态性:如任何类型都可以用运算符+进行计算(等下讲)
静态多态性:如下
peo=People()
dog=Dog()
pig=Pig()
# peo、dog、pig都是动物,只要是动物那么就按照动物的标准使用,就肯定有run方法
# 于是我们便可以不考虑他们三者具体是什么类型,而直接使用
peo.run()
dog.run()
pig.run()
# 更近一步,我们可以定义一个统一的接口来使用,不用考虑对象的类型了
def func(obj):
obj.talk()
静态多态性
其实我们之前已经接触过了,就比如python中的+号,为什么这么说呢?+号不考虑直接使用的类型,就是说它不用考虑两边都是字符串才能用+号,或者两边都是数字才能用+号
车子有很多形态,学开车的时候没有说奥迪怎么开,宝马怎么开,只是学的是一套标准,学的是怎么开车,然后奥迪宝马你都可以开了
使用多态性的好处
- 增加了程序的灵活性,就是用统一的接口去调用,建议在多态的前提上
- 增加了程序的可扩展性,对于使用者来说,接口动都不用动,就直接扩展了一个新的功能
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao')
def func(obj): # 定制一个统一的接口,不需要考虑实例的类型
obj.talk()
peo = People()
func(peo)
然后我们现在新添加一个Cat类
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao')
class Cat(Animal):
def talk(self):
print('say miaomiaomiao')
def func(obj): # 定制一个统一的接口,不需要考虑实例的类型
obj.talk()
peo = People()
func(peo)
cat = Cat()
func(cat)
# 运行结果
say hello
say miaomiaomiao
对于我们来说,新添加了一个Cat类,对于使用者来说,得到了一个cat对象,但是在这个过程当中,代码根本动都没有动,直接把cat对象传递进去,就扩展了一个新的功能
鸭子类型
在python中崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
class File:
def read(self):
pass
def write(self):
pass
class Disk:
def read(self):
print('from is disk read')
def write(self):
print('from is disk write')
class Text:
def read(self):
print('from is text read')
def write(self):
print('from is rext write')
disk = Disk()
text = Text()
disk.read()
disk.write()
text.read()
text.write()
# 在这里给别人的感觉就是大家都用一个接口,不用管你是不是文件,只要你看的像文件,那么就按照文件的读和写去执行,对于使用者来说,大大降低了复杂度,这就是鸭子类型
其实在python中就是这样做的
我们知道的序列类型分为:list|tuple|str,它们三个都是独立的类
l = list([1,2,3])
t = tuple(('a','b'))
s = str('hello')
但是它们三个看起来都像是序列类型,那么就都会有一个len方法,不用考虑是不是列表、字符串,只要是序列类型就可以用__len__
print(l.__len__())
print(t.__len__())
print(s.__len__())
def len(obj): # len是内置方法,所以不需要定义
return obj.__len__()