一、多态
定义
一切皆对象,不同的对象可以调用相同的方法,实现的过程不一样。但是该方法必须有意义,符合子类实例化后的对象的实际情况,也就是一定会被子类或者子类的实例调用
预热:
l1=[1,2,3]
str1="ljytest"
len(l1)的结果等于l1.__len__()的结果
len(str1)的结果等于str1.__len__()的结果
str类和list类都由type产生,而str1和l1是由不同的类产生的,但是都能调用__len__()方法,这就是一种多态。
其实len(l1)本质是调用__len__()方法实现的
下面再来举一个生活中的例子:
class H2O: #水、冰、水蒸气都是由氢元素和氧元素,模仿list和str的元类type def __init__(self,name,temperature): self.name=name self.temperature=temperature def translation(self): if self.temperature < 0: print(‘[%s]温度太低结冰了‘ %self.name) elif self.temperature > 0 and self.temperature < 100: print(‘[%s]液化成水‘ %self.name) elif self.temperature > 100: print(‘[%s]温度太高变成了水蒸气‘ %self.name) class Water(H2O): pass class Ice(H2O): pass class Steam(H2O): pass #实例化对象 w1=Water(‘水‘,25) i1=Ice(‘冰‘,-20) s1=Steam(‘蒸汽‘,3000) #Part1 #三个不同的对象执行相同的方法,得到不同的结果,调用方法的形式和效果就跟l.__len__()、str1.__len__()一样 w1.translation() i1.translation() s1.translation()
class H2O: #水、冰、水蒸气都是由氢元素和氧元素,模仿list和str的元类type def __init__(self,name,temperature): self.name=name self.temperature=temperature def translation(self): if self.temperature < 0: print(‘[%s]温度太低结冰了‘ %self.name) elif self.temperature > 0 and self.temperature < 100: print(‘[%s]液化成水‘ %self.name) elif self.temperature > 100: print(‘[%s]温度太高变成了水蒸气‘ %self.name) class Water(H2O): pass class Ice(H2O): pass class Steam(H2O): pass #实例化对象 w1=Water(‘水‘,25) i1=Ice(‘冰‘,-20) s1=Steam(‘蒸汽‘,3000) #Part2 #如何去实现len(l)、len(str)这种效果?在外部定义一个函数 def func(obj): obj.translation() func(w1) #本质在运行w1.translation() func(i1) #本质在运行i1.translation()
多态是由继承的产生的,它是继承的一种体现方式。而类的继承有两层意义,分别是"改变"和"扩展",多态就是类的这两层意义的一个具体的实现机制,即不同的对象可以调用相同
的方法,实现的过程不一样,python中的标准类型就是多态的概念的一个很好的示范。
继承给子类的方法,子类必须能用起来,子类怎么用这些方法,就是通过多态。如果你的父类实现了一个毫无意义的方法(如在HH2O类里面实现一个aaa方法,然后print("aaa")),
子类根本都不会去调用的,又谈何实现过程不一样?所以父类定义的方法,必须要考虑到子类是否会去调用。
记住:不能为了继承而继承
二、封装
什么是封装?"装"很容易理解,就是一个能装东西的容器,在类中就是属性字典,而"封"就是隐藏的意思,你把麻袋封口了,别人就不知道里面装的是啥
封装的三个层次
第一个层次:作为一个使用者,我只管调用,不关心该方法如何实现
test.py文件中调用fengzhuang.py文件的People类
fengzhuang.py
class People: star=‘earth‘ def __init__(self,id,name,age,salary): self.id=id self.name=name self.age=age self.salary=salary def get_id(self): print(‘我是私有方法啊,我找到的id是[%s]‘ %self.id)
test.py
#test.py文件 from fengzhuang import People p1=People(‘123123123123‘,‘alex‘,‘18‘,100000000) p1.get_id() #能调用 print(p1.star) #能调用
第二个层次:下划线开头的属性
单下划线
#单下划线 class People: _star=‘earth‘ def __init__(self,id,name,age,salary): self.id=id self.name=name self.age=age self.salary=salary def get_id(self): print(‘我是私有方法啊,我找到的id是[%s]‘ %self.id) p1=People(‘442235‘,‘alex‘,‘18‘,100000000) print(p1._star) #能访问,我擦又说是私有属性外部访问不了?注意,这个只是python约定,并没有真正限制。只是作为使用者,你不应该调用这种带单下划线的属性。
双下划线
#双下划线 class People: __star=‘earth‘ def __init__(self,id,name,age,salary): self.id=id self.name=name self.age=age self.salary=salary def get_id(self): print(‘我是私有方法啊,我找到的id是[%s]‘ %self.id) p1=People(‘442235‘,‘alex‘,‘18‘,100000000) print(p1.__star) #访问报错 print(People.__dict__)#字典里面该属性名字为"_People__star" print(p1._People__star) #双下划__开头的属性会被python重命名,变成:_类名__变量名
第三个层次:明确区分内外
class People: __star=‘earth111111111111‘ def __init__(self,id,name,age,salary): print(‘----->‘,self.__star) self.id=id self.name=name self.age=age self.salary=salary def get_id(self): print(‘我是私有方法啊,我找到的id是[%s]‘ %self.id) #访问函数 def get_star(self): print(self.__star) p1=People(‘123123123123‘,‘alex‘,‘18‘,100000000) print(People.__dict__) print(p1.__star) #如果你知道属性字典里面该属性叫什么名字,你当然可以这样调用,可如果你不知道呢? p1.get_star() #调用类提供的访问函数
关于第三点我觉得有必要说明一下
为了让外部能够访问内部私有属性,我们在内部定义一个接口函数供外部调用去访问这些私有属性。
既然可以通过接口函数去访问私有属性,那么有些人可能就会把所有属性都会设置成私有属性。比如,一间房子的长宽高,这种本来就经常调用的属性,如果你一开始将它设置成私有属性,那别人调用长宽高去计算你房子的面积和体积时,你就必须给别人开一个接口函数。每开一个接口函数就相当于多暴露一层,到时候你的程序就会千仓百孔。当然工资这种事情,也不方便公开讨论的,所以你可以设置成私有属性,如果别人要调用,那就只能给他一个接口,这种是符合实际的。
所以,我感觉python没有严格限制外部访问私有属性是怕有些人滥用,不能为了封装而封装,你必须要符合实际情况。
三、面向对象相关概念
面向对象的好处:
1.通过封装明确内外。上帝造物,但是你并知道上帝是怎么造物的
2.通过继承+多态在语言层面支持了归一化设计
注意:不用面向对象也可以做封装、归一化设计
概念总结:
抽象:指对现实世界问题和实体的本质表现,行为和特征建模
现实:对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装:对数据/信息进行隐藏,在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。
接口:本质是函数,提供接口和访问函数以便外部访问内部的数据属性
组合:类与类之间没有太多共性,甚至没有共性,但是这些类合起来能构成一个完整的系统,彼此是“有一个”的关系
派生:描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义
继承:述了子类属性从祖先类继承这样一种方式
泛化(只继承:表示所有子类与其父类及祖先类有一样的特点
特化(子类定义与父类重名的属性):描述所有子类自定义的、让它与其祖先类不同的属性
多态:指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性:指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类冰、水蒸气、都继承于水,它们都有一个同名的方法就是。变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
四、反射
实现反射的四个函数:hasattr、getattr、setattr、delattr,均适用于实例和类
hasattr:判断object中有没有一个name字符串对应的方法或属性
hasattr(obj,str)
setattr:设置object的字符串key的值为value。相当于obj.key=value,用于修改或者新增属性,可设置方法。
setattr(obj,key,value)
getattr:获取object中一个叫name的字符串对应的方法或属性,返回对象的某个属性,函数则返回函数地址,没有则报错,相当于对象.属性
getattr(obj,str,[default])
delattr:删除object中一个叫name的字符串对应的方法或属性,相当于 del obj.属性名
delattr(obj.str)
class FatBoss: feture="sly" #sly是狡猾的意思 def __init__(self,name,weight): self.name=name self.weight=weight def hire_people(self): print("[%s]的团队正在招人,傻逼才去" %self.name) def drink_wine(self): print("[%s]叫你去喝酒,傻逼才去" %self.name) print(hasattr(FatBoss,‘feture‘)) #打印FatBoss是否有"feture"属性 b1=FatBoss("little_tiger","200kg") print(b1.name) #相当于print(b1.__dict__[‘name‘]) print(b1.__dict__) #{‘name‘: ‘Dhz‘, ‘weight‘: ‘200kg‘} #hasattr print(hasattr(b1,"name")) #返回True print(hasattr(b1,"hire_people")) #返回True print(hasattr(FatBoss,"hire_people")) #返回True print(hasattr(b1,‘selasdfasdfsadfasdfasdfasdfasdl_hourse‘)) #返回False #getattr print(getattr(b1,"name")) #little_tiger print(getattr(b1,"drink_wine")) #返回<bound method FatBoss.drink_wine of <__main__.FatBoss instance at 0x025B0C88>> func=getattr(b1,"drink_wine") func() #[little_tiger]叫你去喝酒,傻逼才去 #setattr setattr(b1,"BigSB",True) #设置实例的属性 setattr(b1,"name","sb") #修改实例的属性 setattr(FatBoss,"feture","fat") #修改类的属性 print(b1.__dict__)#{‘BigSB‘: True, ‘name‘: ‘sb‘, ‘weight‘: ‘200kg‘} print(FatBoss.__dict__)#‘feture‘: ‘fat‘ setattr(b1,"func",lambda x:x+1) setattr(b1,‘func1‘,lambda self:self.name+‘sb‘) print(b1.func) #返回lambda函数内存地址 print(b1.func(10)) #返回11 print(b1.func1(b1))#完成字符串拼接,返回sbsb #delattr delattr(b1,"name") print(b1.__dict__) #没有了name
反射好处一:事先定义好接口,接口只有在被完成后才会真正执行,实现即插即用(一种‘后期绑定’)。也就是说你可以事先把主要的逻辑写好(只定义接口),到后期再去实现接口的功能
比如说,现在你和杨老细要一起开发一个FTP客户端,杨老细负责客户端的上传下载功能,你负责其他功能。但是杨老细没有写完代码就请假去嫖了,此时你又赶进度
你不可能也停下来等他回来写好逻辑你再写吧?这时就要用到反射了。
杨老细的代码
class FtpClient: ‘ftp客户端,但是还么有实现具体的功能‘ def __init__(self,addr): print(‘正在连接服务器[%s]‘ %addr) self.addr=addr def put(self): print(‘正在上传文件‘)
你的代码
from ftp_client import FtpClient f1=FtpClient(‘1.1.1.1‘) # f1.put() 如果FtpClient里面已经写好该方法,那么调用的话就不会报错,可如果还没写好,难道你这边的代码也要因此而耽搁吗? # 所以,如果你事先知道put方法没写好,那么你可以在你的程序里面用反射进行判断,如果还没写好,那就跳过这段代码,先实现其他逻辑 # 等到put方法写好了,反射判断为True,就会执行,只要你事先写好了hasattr的逻辑 if hasattr(f1,‘put‘): func_get=getattr(f1,‘put‘) func_get() else: print(‘其他的逻辑‘)
反射的好处二:动态导入模块
如果你导入的模块是字符串咋办?如下例子:m1是包名,t是m1下的模块,t模块里面有test1函数,_test2函数是私有属性
module_t=__import__(‘m1.t‘) print(module_t)#你想导入的是模块t,但是导入的结果是最顶端的模块m module_t.t.test1() #只导入了m1包,所以,还是要module_t.t.test()这样调用 from m1.t import * test1()#能运行 _test2() #不能运行 from m1.t import test1,_test2 test1()#能运行 _test2()#能运行 import importlib m=importlib.import_module(‘m1.t‘)#该方法能导入你只想导入的模块,python官方推荐 print(m)#导入的是t m.test1() #能运行 m._test2() #能运行
五、__getattr__、__setattr__、__delattr__
类内置的方法,你不定义,系统默认就有,你定义了,就用你的
__getattr__:调用对象不存在的属性时才触发执行,比较常用
__delattr__:删除一个对象属性就会触发,可用于控制属性删除,不常用
__setattr__:设置属性时触发,可用于控制使用者设置的属性类型,不常用。比如,要求使用者设置的属性必须是字符串类型,不能是其他类型
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print(‘执行__getattr__‘) f1=Foo(10) print(f1.y) print(getattr(f1,‘y‘)) #你以为这会跟len(str)--->str.__len__()的效果一样,但getattr(f1,‘y‘)的效果并不等同于f1.__getattr__(‘y‘) f1.sssssssssssssssssssssssssssssssssssss #触发了__getattr__ class Foo: x=1 def __init__(self,y): self.y=y def __setattr__(self, key, value): print(‘__setattr__执行‘) # self.key=value 实例化调用self.y=y就会触发__setattr__,而__setattr__下面又是self.key=value,又再次触发__setattr__,如此递归下去... self.__dict__[key]=value #正确的姿势 f1=Foo(10) print(f1.__dict__)#设置进去了 f1.z=2 print(f1.__dict__) #设置进去了 class Foo: x=1 def __init__(self,y): self.y=y def __delattr__(self, item): print(‘删除操作__delattr__‘) del self.item 这是无限递归,因为del self.item会触发__delattr__,如此循环往复,直到溢出 f1=Foo(10) del f1.y #触发了__delattr__ del f1.x #触发了__delattr__
__getattr__、__setattr__、__delattr__的简单应用:
class Foo: def __init__(self,name): self.name=name def __getattr__(self, item): print("__getsttr__") def __setattr__(self,k,v): print("执行__setattr__",k,v) #k是name,v是ljy if type(v) is str: print("开始设置") self.__dict__[k]=v.upper() else: print("必须是字符串类型") def __delattr__(self, item): if (type(self.__dict__[item])) is str: self.__dict__.pop(item) else: print(‘不允许删除属性%s‘ %item) f1=Foo("ljy") #触发__setattr__ f1.age=18 #触发__setattr__,但是数字不能设置 print(f1.__dict__) del f1.name #触发__delattr__ print(f1.__dict__) #删除了name
二次加工标准类型数据
class List(list): def append(self,p_object): if type(p_object) is str: # self.append(p_object)你自己没有append方法 super().append(p_object) #继承父类的append方法 else: print("只能添加字符串类型") def show_middle(self): mid_index=int(len(self)/2) return self[mid_index] l1=list("hello") print(l1) l2=List("hello") print(l2) print(l2.show_middle())#返回l l2.append(111111) #追加不成功 l2.append("SB") #追加成功 print(l2)
六、个人定制
二次加工包装标准类型,通过继承和派生的的概念,定制自己的数据类型。
class List(list): def append(self, p_object): #p_object是追加的字符串 if type(p_object) is str: # self.append(p_object)用自己的会无限递归 super().append(p_object) #list.append(self,p_object),调自己的会出现无限递归,那我就用方法 else: print(‘只能添加字符串类型‘) def show_midlle(self): mid_index=int(len(self)/2)#取得中间元素的下标 return self[mid_index] # l2=list(‘hell oworld‘) # print(l2,type(l2)) l1=List(‘helloworld‘) # print(l1,type(l1)) # print(l1.show_midlle()) l1.append(1111111111111111111111) l1.append(‘SB‘) print(l1)
授权
授权的关键点就是覆盖原有的__getattr__方法,授权不是通过继承来做的
import time class FileHandle: def __init__(self,filename,mode=‘r‘,encoding=‘utf-8‘): # self.filename=filename self.file=open(filename,mode,encoding=encoding) #这里用真正的open打开一个文件,赋值给一个self.file,相当于平时的f=open(...) self.mode=mode self.encoding=encoding def __getattr__(self, item): # print(item,type(item)) item是传进来的属性名(字符串类型),在这里是read # self.file.read,self.file具有文件操作的一切方法 return getattr(self.file,item) f1=FileHandle(‘a.txt‘,‘w+‘) print(f1.file) #open的一个文件句柄 print(f1.__dict__)#file属性对应的是一个文件句柄 print(‘==>‘,f1.read) #触发__getattr__,为什么会触发__getattr__?因为f1的字典里面没有read方法,FileHandle类里面也没有,那就肯定触发__getattr__ # 我们的目的就是想要触发__getattr__,利用__getattr__完成授权 print(f1.write) #写方法也如此,最后肯定触发__getattr__,找到里面的self.file print(f1.write("11111\\n")) f1.seek(0) #道理同上 print(f1.read()) #self.file=open(filename,mode,encoding=encoding)就相当于sys_f=open("b.txt","w+"),print(getattr(sys_f,"read")),sys_f这个文件句柄里面有read方法 自定制一个write方法,每一行的开头都加时间 class FileHandle: def __init__(self,filename,mode=‘r‘,encoding=‘utf-8‘): # self.filename=filename self.file=open(filename,mode,encoding=encoding) #这里用真正的open打开一个文件,赋值给一个self.file self.mode=mode self.encoding=encoding def write(self,line): print(‘------------>‘,line) t=time.strftime(‘%Y-%m-%d %X‘) self.file.write(‘%s %s‘ %(t,line)) def __getattr__(self, item): # print(item,type(item)) # self.file.read return getattr(self.file,item) f1=FileHandle(‘a.txt‘,‘w+‘) f1.write(‘1111111111111111\\n‘) #不触发__getattr__,因为类里面有了 f1.write(‘cpu负载过高\\n‘) f1.write(‘内存剩余不足\\n‘) f1.write(‘硬盘剩余不足\\n‘) f1.seek(0) print(‘--->‘,f1.read())