欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。关注公粽号 《机器和智能》 回复关键词 “python项目实战” 即可获取美哆商城视频资源!
博主介绍:
CSDN博客专家,CSDN优质创作者,CSDN实力新星,CSDN内容合伙人;
阿里云社区专家博主;
华为云社区云享专家;
51CTO社区入驻博主,掘金社区入驻博主,支付宝社区入驻博主,博客园博主。
python面向对象 —— 继承、多态、封装
- python中的继承
- 在python中模仿Java的Interface
- 抽象类 – 接口继承
- 继承顺序
- python中的继承原理(python3)
- 子类中调用父类方法 – super方法
- python中的多态
- python中的封装
专栏:《python从入门到实战》
python中的继承
class Parent1:passclass Parent2:passclass sub(Parent1): #单继承passclass sub2(Parent1, Parent2): #多继承pass
子类继承了父类所有的属性,如果子类属性和父类属性同名,那么在使用的时候,子类会使用自己的属性,因为搜索的顺序是先找自己的,自己没有再去找父类的,如果自己有就用自己的。注意,子类没有覆盖父类的属性,父类的属性还是父类的,子不过子类使用同名属性的时候会先找到自己的属性。如果用父类去使用这个属性,那么父类得到的是自己的值。比如父类有一个属性flag = 10,子类也定义了一个flag = 11,那么son.flag = 11,father.flag = 10,各是各的,不存在值被覆盖这种情况。
继承的两层含义:
- 继承基类的方法,并且做出自己的改变或者扩展(代码重用)。子类与父类耦合,应尽量避免使用。
- 声明某个子列兼容于某基类,定义一个接口类,子类继承接口类并实现接口类中定义的方法。(继承的真正应用)
实际使用中,继承的第一重含义往往会使得子类与基类出现强耦合,实际意义不大甚至有害。继承的第二层含义非常重要,也被称为接口继承。接口继承实质上是要求做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者不需要关心具体细节,可以一视同仁的处理实现了特定接口的所有对象,即归一化。归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合,就好像Linux中一切皆文件(泛文件概念),所有东西都作为文件处理,不必关心它具体是内存、磁盘还是网络或者屏幕(但是底层设计者需要区分字符设备、块设备等等),在python中,一切皆对象。
继承与组合的区别:
- 继承建立了基类与派生类之间的关系,它是一种‘是’的关系;
- 组合建立了类与组合类之间的关系,它是一种‘有’的关系;
在python中模仿Java的Interface
class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。def read(self): #定接口函数readpassdef write(self): #定义接口函数writepassclass Txt(Interface): #文本,具体实现read和writedef read(self):print('文本数据的读取方法')def write(self):print('文本数据的读取方法')class Sata(Interface): #磁盘,具体实现read和writedef read(self):print('硬盘数据的读取方法')def write(self):print('硬盘数据的读取方法')class Process(Interface):def read(self):print('进程数据的读取方法')def write(self):print('进程数据的读取方法')
抽象类 – 接口继承
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化,且子类必须实现抽象方法。
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计 。
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
#一切皆文件
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中的类可以继承多个类,Java和C#只能继承一个类。如果python中的类继承了多个类,那么寻找方法有两种,广度优先和深度优先。
如果类是经典类,在多继承的情况下,会按照深度优先的方式查找。
如果类是新式类,在多继承的情况下,会按照广度优先的方式查找。
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__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性
#也就是说 python2中经典类没有这个方法,新式类有
#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类
python中的继承原理(python3)
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'>]
#新式类都继承于object类 – python3都是新式类
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
新式类与经典类:新式类都继承了object类,经典类没有继承object。只有python2区分新式类与经典类,python3不区分新式类与经典类。
子类中调用父类方法 – super方法
显示调用:父类名.父类方法()
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'class Vehicle: #定义交通工具类Country='China'def __init__(self,name,speed,load,power):self.name=nameself.speed=speedself.load=loadself.power=powerdef run(self):print('开动啦...')class Subway(Vehicle): #地铁def __init__(self,name,speed,load,power,line):Vehicle.__init__(self,name,speed,load,power)#只有实例化和对象调类方法的时候,self才会自动传值,这里需要手动传self.line=linedef run(self):print('地铁%s号线欢迎您' %self.line)Vehicle.run(self)line13=Subway('中国地铁','180m/s','1000人/箱','电',13)
line13.run()
super()方法
class Vehicle: #定义交通工具类Country='China'def __init__(self,name,speed,load,power):self.name=nameself.speed=speedself.load=loadself.power=powerdef run(self):print('开动啦...')class Subway(Vehicle): #地铁def __init__(self,name,speed,load,power,line):#super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)super().__init__(name,speed,load,power)self.line=linedef run(self):print('地铁%s号线欢迎您' %self.line)super(Subway,self).run()class Mobike(Vehicle):#摩拜单车passline13=Subway('中国地铁','180m/s','1000人/箱','电',13)
line13.run()
super.父类方法() – 无需写父类名,无需传参self
即使没有直接继承关系,super仍然会按照mro继续往后查找
#A没有继承B,但是A内super会基于C.mro()继续往后找
class A:def test(self):super().test()
class B:def test(self):print('from B')
class C(A,B):passc=C()
c.test() #打印结果:from Bprint(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
指名道姓的显式调用与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(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() #super()会基于mro列表,往后找
'''
D的构造方法
B的构造方法
C的构造方法
A的构造方法
'''
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
#A没有继承B,但是A内super会基于C.mro()继续往后找
class A:def test(self):print('A---->test')super().aaa()
class B:def test(self):print('B---->test')def aaa(self):print('B---->aaa')class C(A,B):def aaa(self):print('C----->aaa')c=C()
c.test() #打印结果:
'''
A---->test
B---->aaa
'''print(C.mro()) # C作为方法调用(即c.test())的发起者,方法调用过程中涉及的属性查找都参考C.mro()。父子关系按照mro列表为准,千万不要从代码层面看父子。例如
# c.test(),发起者C.mro()列表如下,从列表中可以看出,B类就是A类他爹,而代码层面二者并无继承关系
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
python中的多态
len([1, 2, 3])
len(‘hello’)
len((1, 3))
# ---- 这就是多态
l = [1, 3, 4]
len(l) l.__line__()
import abc
class File(metaclass=abc.ABCMeta): #同一类事物:文件@abc.abstractmethoddef click(self):passclass 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都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
obj.talk()
"""
这样就模仿了一个类似于len的多态
我们使用len的时候,传入一个对象len(obj),实际上就相当于调用obj的__len__方法,这里也一样,func(obj),就相当于调用obj的talk方法,这就是多态。
"""
- 多态动态绑定
- 多态性:静态多态性、动态多态性
- 多态是体现在运行时的。
python中的封装
python中约定,单下划线_开头的是私有的,不应该在外部使用,但是实际上这只是一种约定,在外部调用也是可以的,python并没有限制真正的私有。
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
#其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形
#类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:class A:__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__Ndef __init__(self):self.__X=10 #变形为self._A__Xdef __foo(self): #变形为_A__fooprint('from A')def bar(self):self.__foo() #只有在类内部才可以通过__foo的形式访问到.#A.__N #err 访问不到,因为__N已经被改名了,改成了_A__N
#A._A__N #是可以访问到的
#这种,在外部是无法通过__x这个名字访问到。
这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:__类名__属性,然后就可以访问了,如a._A__N,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形。在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的。
封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口(对外提供访问内部私有属性的接口),让外部能够间接地用到我们隐藏起来的属性,那这么做的意义何在???
1:封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
class Teacher:def __init__(self,name,age):# self.__name=name# self.__age=ageself.set_info(name,age)def tell_info(self):print('姓名:%s,年龄:%s' %(self.__name,self.__age))def set_info(self,name,age):if not isinstance(name,str):raise TypeError('姓名必须是字符串类型')if not isinstance(age,int):raise TypeError('年龄必须是整型')self.__name=nameself.__age=aget=Teacher('egon',18)
t.tell_info()t.set_info('egon',19)
t.tell_info()
2:封装方法:目的是隔离复杂度
封装方法举例:
- 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
- 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性class ATM:def __card(self):print('插卡')def __auth(self):print('用户认证')def __input(self):print('输入取款金额')def __print_bill(self):print('打印账单')def __take_money(self):print('取款')def withdraw(self):self.__card()self.__auth()self.__input()self.__print_bill()self.__take_money()a=ATM()
a.withdraw()
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的。其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket, sys._home, sys._clear_type_cache)这些都是私有的,原则上是供内部调用的,作为外部的你,也是可以用的, python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__。