欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程语言 > python >内容正文

python

(第七集——第一章)python面向对象

发布时间:2023/12/29 python 58 豆豆
生活随笔 收集整理的这篇文章主要介绍了 (第七集——第一章)python面向对象 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

程序设计分类


面向过程

面向过程好比精心设计好一条流水线,是一种机械式的思维方式。

# 优点: 复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单) # 缺点: 一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。# 应用场景: 一旦完成很少改变的场景,例子有Linux內核,git,以及Apache HTTP Server等

面向对象

对象是特征与技能的结合体,面向对象更加注重对现实世界的模拟,
优点:
程序的扩展性强。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:
编程的复杂度远高于面向过程
无法像面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果.
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方.

类与对象


类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,类是一系列对象相似的特征与技能的结合体。
类就是将数据与操作数据的方法组合到一起,实现数据与功能是统一.

程序中,保证先定义类,后产生对象: 1. 在程序中特征用变量标识,技能用函数标识 2. 因而类中最常见的无非是:变量和函数的定义 # 类的范例class OldboyStudent:def __init__(self, name, age, sex): # 定义构造方法,无返回值self.name = nameself.age = ageself.sex = sexschool = 'oldboy'def learn(self):print('is learning')def eat(self):print('is eating')def sleep(self):print('is sleeping')##################################################### # 使用OldboyStudent类: s1 = OldboyStudent('tom', 1, '男') # 实例化类得到s1对象,同时执行类中的构造方法__init__() print(s1.__dict__) # {'name': 'tom', 'age': 1, 'sex': '男'} 查看用来存放类所有变量的字典 print(s1.__dir__()) # 查看用来存放类的变量和函数的名称空间 print(s1.__module__) # __main__ 在当前类执行时的模块名称,当被其它模块引入时,模块名为引入该模块的那个模块名 print(s1.__class__) # <class '__main__.OldboyStudent'> 查看对象对应的类名 print(s1.__getattribute__('school')) # 类的反射,通过字符串获得对象中该属性的属性值 # del s1.age # 等价于s1.__dict__.pop('age'), 从名称空间字典中删除该属性 # print(s1.__dict__) # s1.age = 22 # 为age属性赋值 # print(s1.age) # 访问age属性 s1.learn() # 调用learn方法 s1.eat() # 调用eat方法

属性查找


类的属性

# 数据属性:类的数据属性是所有对象共享的 print(id(s1.school)) # 4377347328 print(id(s2.school)) # 4377347328# id是python的实现机制,并不能真实反映内存地址, 如果有内存地址,还是以内存地址为准 # 函数属性:类的函数属性是绑定给对象用的 print(OldboyStudent.learn) # <function OldboyStudent.learn at 0x1021329d8> print(s1.learn) # <bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>> print(s2.learn) # <bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>> # 类的函数属性是绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样,类的函数属性是绑定给对象使用的

备注:在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类…最后都找不到就抛出异常

绑定到对象的方法的特殊之处


#改写 class OldboyStudent:school='oldboy'def __init__(self,name,age,sex):self.name=nameself.age=ageself.sex=sexdef learn(self):print('%s is learning' %self.name) #新增self.namedef eat(self):print('%s is eating' %self.name)def sleep(self):print('%s is sleeping' %self.name)s1=OldboyStudent('李坦克','男',18) s2=OldboyStudent('王大炮','女',38) s3=OldboyStudent('牛榴弹','男',78) 类中定义的函数(没有被任何装饰器装饰的)是类的函数属性, 类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数: OldboyStudent.learn(s1) #李坦克 is learning OldboyStudent.learn(s2) #王大炮 is learning OldboyStudent.learn(s3) #牛榴弹 is learning 类中定义的函数(没有被任何装饰器装饰的), 其实主要是给对象使用的,而且是绑定到对象的, 虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用, 谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理) s1.learn() #等同于OldboyStudent.learn(s1) s2.learn() #等同于OldboyStudent.learn(s2) s3.learn() #等同于OldboyStudent.learn(s3)注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数selfself可以是任意名字,但是约定俗成地写出self 类即类型:提示:python的class术语与c++有一定区别,与 Modula-3更像。python中一切皆为对象,且python3中类与类型是一个概念,类型就是类.#类型dict就是类dict >>> list <class 'list'>#实例化的到3个对象l1,l2,l3 >>> l1=list() >>> l2=list() >>> l3=list()#三个对象都有绑定方法append,是相同的功能,但内存地址不同 >>> l1.append <built-in method append of list object at 0x10b482b48> >>> l2.append <built-in method append of list object at 0x10b482b88> >>> l3.append <built-in method append of list object at 0x10b482bc8>#操作绑定方法l1.append(3),就是在往l1添加3,绝对不会将3添加到l2或l3 >>> l1.append(3) >>> l1 [3] >>> l2 [] >>> l3 []#调用类list.append(l3,111)等同于l3.append(111) >>> list.append(l3,111) #l3.append(111) >>> l3 [111]

对象之间的交互


class Garen: #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄;camp='Demacia' #所有玩家的英雄(盖伦)的阵营都是Demacia;def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻击力58...;self.nickname=nickname #为自己的盖伦起个别名;self.aggressivity=aggressivity #英雄都有自己的攻击力;self.life_value=life_value #英雄都有自己的生命值;def attack(self,enemy): #普通攻击技能,enemy是敌人;enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。 class Riven:camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus;def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54;self.nickname=nickname #为自己的锐雯起个别名;self.aggressivity=aggressivity #英雄都有自己的攻击力;self.life_value=life_value #英雄都有自己的生命值;def attack(self,enemy): #普通攻击技能,enemy是敌人;enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。

继承与派生


继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。子类会“”遗传”父类的属性,从而解决代码重用问题。

python中类的继承分为:单继承和多继承 class ParentClass1: #定义父类passclass ParentClass2: #定义父类passclass SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClasspassclass SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类pass 查看继承: >>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>) 经典类与新式类: 只有在python2中才分新式类和经典类,python3中统一都是新式类 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类 如: >>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)

继承与抽象(先抽象再继承)

抽象: 继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承 抽象分成两个层次:1.将奥巴马和梅西这俩对象比较像的部分抽取成类; 2.将人,猪,狗这三个类比较像的部分抽取成父类。 (抽象最主要的作用是划分类别) 继承: 基于抽象的结果,通过编程语言去实现它,是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类。

继承与重用性


在开发程序的过程中,如果我们定义了一个类A,然后又新建立另外一个类B,但类B的大部分内容与类A的相同时,可以不用重新写一个类B,采用类的继承的概念。 通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用 class Hero:def __init__(self,nickname,aggressivity,life_value):self.nickname=nicknameself.aggressivity=aggressivityself.life_value=life_valuedef move_forward(self): print('%s move forward' %self.nickname)def move_backward(self):print('%s move backward' %self.nickname)def move_left(self):print('%s move forward' %self.nickname)def move_right(self):print('%s move forward' %self.nickname)def attack(self,enemy):enemy.life_value-=self.aggressivityclass Garen(Hero): # 继承Hero类passclass Riven(Hero): # 继承Hero类passg1=Garen('草丛伦',100,300) r1=Riven('锐雯雯',57,200)print(g1.life_value) r1.attack(g1) # 调用父类中的方法 print(g1.life_value)''' 运行结果 243 ''' class Foo:def f1(self):print('Foo.f1')def f2(self):print('Foo.f2')self.f1() # 因为self表示子类Bar的对象,所以会调用子类Bar类中的f1函数class Bar(Foo):def f1(self):print('Foo.f1')b=Bar() b.f2() ''' 打印结果: Foo.f2 Foo.f1 '''

派生


当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。 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 比基尼'''

组合与重用性

软件重用除继承外还有组合,指的是在一个类中以另外一个类的对象作为数据属性,称为类的组合:>>> 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.继承的方式: 通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人2.组合的方式: 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日, 教授教python和linux课程,教授有学生s1、s2、s3...当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好: class People:def __init__(self,name,age,sex):self.name=nameself.age=ageself.sex=sexclass Course:def __init__(self,name,period,price):self.name=nameself.period=periodself.price=pricedef tell_info(self):print('<%s %s %s>' %(self.name,self.period,self.price))class Teacher(People):def __init__(self,name,age,sex,job_title):People.__init__(self,name,age,sex)self.job_title=job_titleself.course=[]self.students=[]class Student(People):def __init__(self,name,age,sex):People.__init__(self,name,age,sex)self.course=[]t1=Teacher('egon',18,'male','沙河霸道金牌讲师') s1=Student('牛榴弹',18,'female')python=Course('python','3mons',3000.0) linux=Course('linux','3mons',3000.0)#为老师egon和学生s1添加课程 t1.course.append(python) # 将python对象添加到老师 course列表属性中 t1.course.append(linux) # 将linux对象添加到 老师course列表属性中 s1.course.append(python) # 将python对象添加到 学生course列表属性中#为老师egon添加学生s1 t1.students.append(s1)#使用 for obj in t1.course: # t1.course属性保存的是Cource类的对象obj.tell_info() # obj此时代表的是Cource类的对象,"""结果:<python 3mons 3000.0><linux 3mons 3000.0>"""

接口与归一化设计

接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数。

  • 归一化设计:
    只要是基于同一个接口实现的类,所有这些类产生的对象在使用时,在用法上都一样。

  • 好处:

    1、让使用者无需关心对象的类是什么,只要知道这些对象都具备某些功能,降低使用者的使用难度。
    2、 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合。
    (如:有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,
    他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,
    还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样)

  • 模仿interface接口

模仿interface: python中无interface的关键字,可借助第三方模块模仿接口的概念,twisted的twisted\internet\interface.py里使用zope.interface 也可采用继承实现:继承的两种用途: 1、继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。 2:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。def read(self): #定接口函数readpassdef write(self): #定义接口函数writepassclass Txt(Interface): # 继承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('进程数据的读取方法')

抽象类

python模仿Interface代码只是看起来像接口,并未起到接口的作用,子类完全可不实现接口 ,这就用到了抽象类

抽象类:
抽象类是一个特殊的类,只能被继承,不能被实例化,采用内置模块实现

类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
从设计角度去看,如果类是从现实对象抽象而来的,抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法.

python中实现抽象类: #_*_coding:utf-8_*_ __author__ = 'Linhaifeng' import abc #利用abc模块实现抽象类class All_file(metaclass=abc.ABCMeta): # 继承abc类all_type='file' # 定义数据属性@abc.abstractmethod #定义抽象方法,无需实现功能def read(self):'子类必须定义读功能'pass@abc.abstractmethod #定义抽象方法,无需实现功能def write(self):'子类必须定义写功能'pass# class Txt(All_file): # 继承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)

抽象类与接口:
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。抽象类是一个介于类和接口之间的概念,同时具备类和接口的部分特性,可以用来实现归一化设计.

继承实现的原理

Python中子类可以同时继承多个父类,如A(B,C,D),
如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先

当类是经典类时,按深度优先方式查找下去。 当类是新式类时,按广度优先方式查找下去。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__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性#新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类

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.如果对下一个类存在两个合法的选择,选择第一个父类

子类中调用父类的方法

方法一:指名道姓,即父类名.父类方法() #_*_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.line=linedef run(self):print('地铁%s号线欢迎您' %self.line)Vehicle.run(self) # 调用父类中的run方法,此时不会报错,因为上面调用了父类中的构造方法,内存中有该类,传入的参数为Subway对象line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run() # 调用子类中的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() # 调用父类中的方法,将Subway的对象作为参数传入class Mobike(Vehicle):#摩拜单车passline13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run() # 两者区别: 强调:二者使用哪一种都可以,但最好不要混合使用 1、即使没有直接继承关系,super仍然会按照mro继续往后查找 #指名道姓:父类名.父类方法() 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列表)

多态与多态性


多态指的是一类事物有多种形态

动物有多种形态:人,狗,猪:import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物@abc.abstractmethoddef talk(self): # 抽象类方法定义passclass 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.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方法obj.talk() 多态性的好处: 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) ''' Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’python程序员通常根据这种行为来编写程序。 例如,如果想编写现有对象的自定义版本,可以继承该对象也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法 #二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 class TxtFile:def read(self):passdef write(self):passclass DiskFile:def read(self):passdef write(self):pass2:其实大家一直在享受着多态性带来的好处,比如Python的序列类型有多种形态:字符串,列表,元组,多态性体现如下 #str,list,tuple都是序列类型 s=str('hello') l=list([1,2,3]) t=tuple((4,5,6))#我们可以在不考虑三者类型的前提下使用s,l,t s.__len__() l.__len__() t.__len__()len(s) len(l) len(t)

封装


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._A__N是可以访问到的, # 但是在外部是无法通过__x这个名字访问到。 注意:1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。 2.变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形注意:1.python的封装机制并没有真正限制从外部直接访问属性, 当知道类名和属性名就可拼出名字:_类名__属性进行访问, 如a._A__N,该操作并非严格意义上的限制外部访问,只是一种语法意义上的变形,主要用来限制外部的直接访问。 2.变形的过程只在类的定义时发生1次,在定义后的赋值操作,不会变形 3.在继承中,父类如果不想让子类重写自己的方法,可将方法定义为私有#正常情况 >>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() from B#把fa定义成私有的,即__fa>>> class A: ... def __fa(self): #在定义时就变形为_A__fa ... print('from A') ... def test(self): ... self.__fa() #只有通过在自己类中对__fa()方法进行调用,即调用_A__fa ... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test() # 调用父类中的test方法中封装的__fa()方法,传入的参数为b对象 from A

封装不是单纯意义的隐藏

1:封装数据:将数据隐藏不是目的。隐藏后对外提供操作该数据的接口,可在接口附加上对该数据操作的限制, 以此完成对数据属性操作的严格控制: class Teacher:def __init__(self,name,age):# self.__name=name # 封装属性# self.__age=age # 封装属性self.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=name # 验证通过,对封装的属性进行赋值self.__age=aget=Teacher('egon',18) t.tell_info()t.set_info('egon',19) t.tell_info() # 2:封装方法:目的是隔离复杂度封装方法举例: 1. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!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() 3: 了解 python并不会真的阻止访问私有属性,模块也遵循这种约定, 如果模块名以单下划线开头,那from module import *时不能被导入, 但采用from module import _private_module依然可以导入。 调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache), 这都是私有,原则上是供内部调用的,作为外部调用时也可以用。python要像与其他编程语言一样,严格控制属性的访问权限, 需借助内置方法如__getattr__

封装与扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。
这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码; 而外部使用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。 这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。#类的设计者 class Room:def __init__(self,name,owner,width,length,high):self.name=nameself.owner=ownerself.__width=width # 封装属性self.__length=lengthself.__high=highdef tell_area(self): # 对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积return self.__width * self.__length#使用者 >>> r1=Room('卧室','egon',20,20,20) >>> r1.tell_area() #使用者调用接口tell_area#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 class Room:def __init__(self,name,owner,width,length,high):self.name=nameself.owner=ownerself.__width=widthself.__length=lengthself.__high=highdef tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了return self.__width * self.__length * self.__high#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 >>> r1.tell_area()

总结

以上是生活随笔为你收集整理的(第七集——第一章)python面向对象的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。