面向对象三大特性:继承,多态,封装
1.继承:类的继承与父子继承关系一样,父类称为基类,子类可以继承父类的所有属性
继承也分为单继承与多继承:单继承就是继承一个父类,多继承就是继承多个父类
class ParentClass pass class ParentClass2: pass class SubClass1(ParentClass): #单继承 pass class SubClass2(ParentClass,ParentClass2): #多继承 pass
什么时候用继承?
1.当类之间有显著的不同,并且较小的类是较大的类的所需要的组件时,用组合比较好
2.当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
派生:继承了父类后派生自己的属性
继承的含义:
1.继承基类的方法(减少代码重用),但还是要尽量少用,因为代码间的解耦性不强
2.声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法
接口继承:父类定义一个接口而不定义具体实现,子类自己具体实现
#一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
解密继承原理
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
#查看F类继承了那些类 >>> 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.如果对下一个类存在两个合法的选择,选择第一个父类
子类调用父类的方法
先看下面这个例子
class Vehicle: def __init__(self,name,speed): self.name = name self.speed = speed def run(self): print('开动') class subway(Vehicle): def __init__(self,name,speed,lines): Vehicle.__init__(self,name,speed) self .lines = lines p1 = subway('AA',4,5) print(p1.__dict__)
以上方法有个缺点,就是当你以后要改父类名的时候以下的相关逻辑都要改父类名,可扩展性差
介绍一个super用法,他会把你继承的父类自动传进其参数,好处就是不用写父类名,当父类名被改不会影响子逻辑,直接改父类继承名就好。好处之2就是不用传self
class Vehicle: def __init__(self,name,speed): self.name = name self.speed = speed def run(self): print('开动') class subway(Vehicle): def __init__(self,name,speed,lines): super().__init__(name,speed) #super(__class__,self).__init__(name,speed) self .lines = lines p1 = subway('AA',4,5) print(p1.__dict__)
2.多态
Python本身是多态的,多态指的是一类事物有多种形态,而不同的类实例的对象可以调用同个方法
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需要考虑他们具体的类
Python中标准类型就是多态概念的一个很好示范
len(s1) = s1.__len___() len(l) = l.__len__()
其实面向对象三大特性中并无多态,他也只是继承衍生出来的
如下面例子,你只需要确定一个大类,当这个大类可以分为有多个不同形态的类的时候,多态就派上用场了,由此我们也就不必须确定哪些小类。
class H2O: def __init__(self,tmp): self.tmp = tmp def turn_water(self): print('water') def turn_ice(self): print('ice') class Water(H2O): pass class Ice(H2O): pass def func(x): if x.tmp == 50: x.turn_water() if x.tmp<50: x.turn_ice() w1 =Water(50) i1= Ice(1) func(w1) func(i1)
3.封装
封装:本质就是区分内外,内部可用,外部用不到
装:定义类的数据属性和函数属性
封:隐藏
第一层面封装:类本身就是一种封装,类下面封装了数据属性与函数属性,这些都是外部调用不到的
第二层面封装:类中定义私有的,只在类中内部使用,外部不可访问 具体方法由单下划线与双下划线实现
单下划线封装
如下面还未实行第二层面上封装的例子:People实例化的对象可以访问start
class People: start = 'earth' def __init__(self): pass x1 = People() print(x1.start)
但如果在start前面加一个单下划线(代码区分内外,外部不可调用的约定)
class People: _start = 'earth' def __init__(self): pass x1 = People() print(x1.start) #报错
但既然是约定,就可以打破。
class People: _start = 'earth' def __init__(self): pass x1 = People() print(x1._start) #earth 访问成功
双下划线封装
既然知道了单下划线的封装原理,那对于双下划线我们也来试试看看能否打破约定
class People: __start = 'earth' def __init__(self): pass x1 = People() print(x1.__start) #报错
其实双下划线实际上就是重命名__start = _People__start
class People: __start = 'earth' def __init__(self): pass x1 = People() print(People.__dict__) # '_People__start': 'earth' print(x1._People__start) #earth
既然这种封装会被打破约定那为什么还要用他呢?答:给不知道的人用(虽然有点无耻)
既然定义了属性,那就要给人用,所以我们需要定义一个接口函数,让用户去使用他。不然定义的属性就没意义了啊
class People: __start = 'earth' def __init__(self): pass def use(self): return self.__start #封装,只在内部使用,所以我们在这里直接写__start就行了 x1 = People() print(x1.use())
由此可见封装区分内外也区分了类与实例的,所以在类内部使用__start的时候不用写_People__start
封装就是应该充分考虑好内跟外,给用户设计好接口使用
总结一下面向对象编程的好处:
1.封装:明确内外,调用者不明白内部逻辑
2.继承+多态:归一化设计