一、面向对象的三大特性
1.继承
2.多态
3.封装
二、继承
1. 什么是继承
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
子类会“”遗传”父类的属性,从而解决代码重用问题
python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
pass
2. 到底继承了什么:子类继承了父类的所有数据属性和函数属性
继承的本质是父类把自己类的属性引用传递给了子类,子类可以调用父类的属性,但其实它们是不属于子类的
class Dad:
'父类'
money = 10000
def __init__(self,name):
print('父类的初始化方法-->')
self.name = name
def hit_son(self):
print('调用者是%s 调用的是父类的方法' % self.name)
class Son(Dad):
'子类'
pass
s1 = Son('小白龙')
print('继承的爸爸的遗产', s1.money)
s1.hit_son() # 继承了爸爸打儿子的方法
# 上述表现貌似证明了儿子类,继承了父类所有的属性(类的数据属性和函数属性)
print(Dad.__dict__)
print(Son.__dict__)
#打印的结果发现,儿子类的属性字典中没有任何关于父类的属性
#注意了
#继承的本质是父类把自己类的属性引用传递给了儿子,儿子类可以调用父类的属性,但其实它们是不属于儿子的
#因此,在子类中定义的任何数据属性和函数属性都存在于儿子类的属性字典中,调用时优先从自己的属性字典里面查
class Dad:
'父类'
money = 10000
def __init__(self,name):
print('父类的初始化方法-->')
self.name = name
def hit_son(self):
print('调用者是%s 调用的是父类方法' % self.name)
class Son(Dad):
'子类'
def __init__(self,name,age):
self.name = name
self.age = age
def hit_son(self):
print('这是子类调用自己的函数属性')
s1 = Son('小白龙',18)
print(s1.name, s1.age)
s1.hit_son()
print(Dad.__dict__)
print(Son.__dict__)
>>>
父类的初始化方法-->
继承的爸爸的遗产 10000
调用者是小白龙 调用的是父类的方法
{'__module__': '__main__', '__doc__': '父类', 'money': 10000, '__init__': <function Dad.__init__ at 0x000001E8773E97B8>, 'hit_son': <function Dad.hit_son at 0x000001E8773E9950>, '__dict__': <attribute '__dict__' of 'Dad' objects>, '__weakref__': <attribute '__weakref__' of 'Dad' objects>}
{'__module__': '__main__', '__doc__': '子类'}
小白龙 18
这是子类调用自己的函数属性
{'__module__': '__main__', '__doc__': '父类', 'money': 10000, '__init__': <function Dad.__init__ at 0x000001E8773E98C8>, 'hit_son': <function Dad.hit_son at 0x000001E8773E9C80>, '__dict__': <attribute '__dict__' of 'Dad' objects>, '__weakref__': <attribute '__weakref__' of 'Dad' objects>}
{'__module__': '__main__', '__doc__': '子类', '__init__': <function Son.__init__ at 0x000001E8773E9D08>, 'hit_son': <function Son.hit_son at 0x000001E8773E9D90>}
3. 继承和组合的使用分别:
1.当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
例如:描述一个机器人类,机器人这个大类是由很多互不相关的小类组成,如机械胳膊类、腿类、身体类、电池类
2.当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
例如
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
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('%s喵喵叫' % self.name) class Dog(Animal): def __init__(self, name): self.name = name self.breed = '狗' def cry(self): print('%s汪汪叫' % self.name) c = Cat('小白') c.cry() c.eat() d = Dog('小黑') d.cry() d.drink() >>> 小白喵喵叫 小白 吃 小黑汪汪叫 小黑 喝
4. 接口与归一化设计
1. 什么叫接口
* Java的Interface接口的特征:
* 1)是一组功能的集合,而不是一个功能
* 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作
* 3)接口只定义函数,但不涉及函数实现
* 4)这些功能是相关的
2.为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
3.归一化的好处在于:
1. 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
2.1:就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
2.2:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
4.继承的两种用途
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能。
class Interface: #定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
def read(self): #定接口函数read
pass
def write(self): #定义接口函数write
pass
class Txt(Interface): #文本,具体实现read和write
def read(self):
print('文本数据的读取方法')
def write(self):
print('文本数据的读取方法')
class Sata(Interface): #磁盘,具体实现read和write
def read(self):
print('硬盘数据的读取方法')
def write(self):
print('硬盘数据的读取方法')
class Process(Interface):
def read(self):
print('进程数据的读取方法')
def write(self):
print('进程数据的读取方法')
上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,这就需要用到抽象类
5.抽象类
5.1 什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
5.2 为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
5.3. 在python中实现抽象类
import abc
class All(metaclass=abc.ABCMeta):
all_type = 'file'
@abc.abstractmethod # @abc.abstractmethod装饰器 所有继承这个基类的类,都必须有这个read()函数
def read(self): #接口类方法不用实现内部逻辑,也没必要实例化。它只是一个规范而已
'子类必须实现读功能'
pass
@abc.abstractmethod # @abc.abstractmethod装饰器 所有继承这个基类的类,都必须有这个write()函数
def write(self):
'子类必须实现写功能'
pass
class Disk(All):
def read(self): #接口继承要求子类必须有与父类同名的read()函数,没有则会报错
print('硬盘数据的读取方法')
def write(self): #接口继承要求子类必须有与父类同名的write()函数,没有则会报错
print('硬盘数据的读取方法')
# class Txt(All_file):
# pass
#
# t1=Txt() #报错,子类没有定义抽象方法
class Txt(All): #子类继承抽象类,必须定义read和write方法
def read(self):
print('文本数据的读取方法')
def write(self):
print('文本数据的读取方法')
class Sata(All): #子类继承抽象类,必须定义read和write方法
def read(self):
print('硬盘数据的读取方法')
def write(self):
print('硬盘数据的读取方法')
class Process(All): #子类继承抽象类,必须定义read和write方法
def read(self):
print('进程数据的读取方法')
def write(self):
print('进程数据的读取方法')
yingpanwenjian = Disk()
wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#这样大家都是被归一化了,也就是一切皆文件的思想
yingpanwenjian.read()
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()
print(yingpanwenjian.all_type)
print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
>>>
硬盘数据的读取方法
文本数据的读取方法
硬盘数据的读取方法
进程数据的读取方法
file
file
file
file
5.4. 抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
6.继承顺序
在python3中所有的都是新式类
若存在以下继承关系
class A:
def test(self):
print('a')
class B(A):
def test(self):
print('b')
pass
class C(A):
def test(self):
print('c')
class D(B):
# def test(self):
# print('d')
pass
class E(B):
def test(self):
print('e')
pass
class F(D, E):
def test(self):
print('f')
pass
f = F()
f.test() #新式类继承顺序(python3都是新式类) F->D->E->B->A (找到基础类还未找到此方法则会报错)
#经典类继承顺序 F->D->B->A->E->C (找到基类未找到此方法不一定报错,继续往回找)
print(F.__mro__)
>>>
f
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
6.3 继承原理(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.如果对下一个类存在两个合法的选择,选择第一个父类
7.子类中调用父类的方法
方法一:指名道姓,即父类名.父类方法()
class Vehicle:
Country = 'China'
def __init__(self, name, speed, load, power):
self.name = name
self.speed = speed
self.load = load
self.power = power
def run(self):
print('开动啦')
class Subway(Vehicle):
def __init__(self, name, speed, load, power, line):
# self.name = name
# self.speed = speed
# self.load = load
# self.power = power
Vehicle.__init__(self, name, speed, load, power) 基类若是改名,则需要修改逻辑,可拓展性差
self.line = line
def show_info(self):
print(self.name, self.line)
def run(self):
Vehicle.run(self) # 调用父类方法
print('%s %s 线,开动啦' %(self.name, self.line))
l1 = Subway('长沙地铁','30m/s',100000,'电','2号')
l1.show_info()
l1.run()
>>>
长沙地铁 2号
开动啦
长沙地铁 2号 线,开动啦
方法二:super()
class Vehicle:
Country = 'China'
def __init__(self, name, speed, load, power):
self.name = name
self.speed = speed
self.load = load
self.power = power
def run(self):
print('开动啦')
class Subway(Vehicle):
def __init__(self, name, speed, load, power, line):
# self.name = name
# self.speed = speed
# self.load = load
# self.power = power
super().__init__(name, speed, load, power)
self.line = line
def show_info(self):
print(self.name, self.line)
def run(self):
super().run()
print('%s %s 线,开动啦' %(self.name, self.line))
l1 = Subway('长沙地铁','30m/s',100000,'电','2号')
l1.show_info()
l1.run()
>>>
长沙地铁 2号
开动啦
长沙地铁 2号 线,开动啦
强调:二者使用哪一种都可以,但最好不要混合使用
指名道姓与super()的区别
#指名道姓
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() #A.__init__被重复调用
'''
D的构造方法
B的构造方法
A的构造方法
C的构造方法
A的构造方法
'''
#使用super()
class A:
def __init__(self):
print('A的构造方法')
class B(A):
def __init__(self):
print('B的构造方法')
super().__init__()
class C(A):
def __init__(self):
print('C的构造方法')
super().__init__()
class D(B,C):
def __init__(self):
print('D的构造方法')
super().__init__()
f1=D() #super()会基于mro列表,往后找
'''
D的构造方法
B的构造方法
C的构造方法
A的构造方法
'''
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)