#面向对象的三大特性
1 继承(上一章的内容)
2 多态
python本身就是多态的
3 封装
# 多态
#不同类的实例化对象,调用同一个方法(执行的逻辑不同),而不用考虑他们具体的类,例如:
字符对象s和列表对象l都调用了同一个__len__的方法(都继承于他们的父类即str和list),却不需要考虑他们属于不同类
s = str(111) #相当于str这个类将数字1传入,实例化出来一个字符串对象l = list('abc') #相当于list这个类将'abc'传入,实例化出来一个列表对象len(s) #相当于字符对象s调用了字符类下的一个__len__的方法,即s.__len__()print(s.__len__())len(l) #同理print(l.__len__()) print(dir(str)) #里面有__len__ print(dir(list)) #里面有__len__
#其实len函数相当于
def len(obj) : obj.__len__()
#多态反应的是一个执行时候的状态
#再举一个例子加深理解
class H20 : def __init__(self,temperature): self.temperature = temperature def trans(self): if self.temperature < 0 : print('freezing') if self.temperature > 0 and self.temperature < 100 : print('flowing') if self.temperature > 100 : print('boiling')class liquid(H20): passclass solid(H20) : passs1 = liquid(110)s2 = liquid(20)s1.trans() #都调用了同一种方法,却属于不同类s2.trans()def trans(obj) : #其实这里定义了这个函数,才叫真正的多态。当你不调用时,没差别;而当你调用这个函数时,不管是什么类实例化出来的对象,都能执行同一套方法。 obj.trans()trans(s1)trans(s2)
到此,我们也就明白到底什么是多态了:不同子类继承了父类的共同的函数属性,并且不同子类实例化出来的不同对象都调用父类的函数属性,这种动态的过程,就叫做多态。其实多态的基础就是就是继承,就是一种继承的体现方式。
换句话说,继承解决的是代码重用的问题,而把这个优势体现出来的方式就叫做多态,即父类定义的函数,要考虑到多个子类的继承问题(需要是有意义的)
#封装
#封装是一种思想(本质就是要区别内外,内部就是类里面,外部就是调用者;外部调用者无需知道内部逻辑),装就是把东西都放到一个袋子里,封就是把袋子封口,将东西隐藏起来,也就是定义一个类,往里面定义数据属性和函数属性,这就叫装(装到了__dict__里)
#那么什么是封呢?
(类中定义私有的,只想让其在类内部被访问,外部不需访问,但需要给外部定义一个接口函数,用于外部访问内部)
1 在一个py文件里定义的类,由其他py文件调用,其实就已经有隐藏的含义了,调用者并不知道其内部逻辑,只需要用就行
2 通过python约定俗成的命名方法来命名,达到隐藏的效果:
3 为封装到内部的逻辑提供一个外部访问的接口
# _开头的变量如 _abc 代表这个属性不该被外部调用者调用
class A : _b = '你不该在外部调用这个数据属性' def __init__(self): passa = A()print(a._b) #还是能调用到这个_b,因为这只是文字上的约束,python并没有内部逻辑帮你阻止调用 #但你看到_开头的就要明白,这个属性是不该被你调用的
#__开头的变量如__abc (__abc__这样末尾有__的不算),python会给你自动重命名为_类名__abc
class A : __b = '你调用不到我,会报错' def __init__(self): print('但我可以在这里调',self.__b) def C(self): #接口函数(访问函数) print(self.__b,'内部就能调用')a = A()# print(a.__b) #调用不到__b,会报错 ,因为以及被python自动重命名为_A__b了,可以在__dict__中找到print(A.__dict__)print(a._A__b)a.C() #外部调用者通过调用接口函数,访问到了在内部被封装的属性
#注意事项:
要把什么变量做成私有的需要深思熟虑(不然后期只能通过不断的添加接口函数来弥补,在一个项目里把私有的变量名再改回去是不现实的)
#来几个真正封装的例子
class cal_area : def __init__(self,name,width,length): self.name = name self.__width = width self.__length = length def result(self): print('%s的面积为%s平方米' %(self.name,self.__length*self.__width)) def port(self): return self.__width,self.__lengths = cal_area('home',100,100)s.result()print(s.port()) #没法直接调用内部私有的__width和__length,只能通过新写的接口函数(也叫访问函数)来调用
#反射(又称自省)
#主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。
四个可以实现反射的函数(适用于类和对象):
hasattr(object,name):判断对象中有没有一个叫做name的方法或属性(数据属性或函数属性),
或者换句话说,能不能调用到name的方法或属性,返回bool值. 注意name是个字符串!
a = 'abc'print(hasattr(a,'__len__')) #可以传入实例化对象print(hasattr(str,'__len__')) #也可以传入一个类class A: def __init__(self,name): self.__name = name def __test(self): passprint(hasattr(A,'__test')) #hasattr检测的是能被调用的方法或属性,这里做了封装所以没找到
getattr(object,name,default=None):获取对象或者类中能被调用的方法或者属性
a = 'abc'b = getattr(a,'__len__') #在实例化对象a中,获取到叫做'__len__'的属性,并且可以调用.(没有则会报错)d = getattr(a,'adflkj','如果找不到,得到这里的值') #这样写找不到也不会报错e = getattr(a,'adsf',default=None)print(b())print(d)c = getattr(str,'__len__')print(c(a))#其实getattr(object,name)就等于object.name
setattr(object,key,value):给对象或类设置方法或属性
class A: def __init__(self,name): self.name = name def test(self): passa = A('abc')setattr(a,'name0','ssy')print(a.__dict__)#相当于直接a.name = 'alex'print(a.__dict__)#也可传入函数属性def test(self) : print(self.name)setattr(A,'test',test)a.test()
delattr(object,key):删除对象或类的属性或方法,相当于 del A.key
class A: def __init__(self,name): self.name = name def test(self): passa = A('abc')delattr(a,'name')#相当于直接del a.nameprint(a.__dict__)
#反射的好处:
当多人开发一个项目时候,你需要调用别人负责实现的的类,但是对方没实现,甚至都没定义这时候怎么办?
难道要等到他实现了通知你,你才开始写代码么?这时候就可以用hasattr来进行一个判断
from XXX import Aa = A('abc') #调用别人的类来实例化if hasattr(a,'你要调用的方法') : b = getattr(a,'你要调用的方法') b() #运行调用的方法#如果不存在,就接着下面的逻辑
这样,当你调用的类不存在时,就跳过;存在的时候即调用;你这段调用的代码其实就已经写好了,不论这个类有没有被实现。
换句话说就是你不用等别人实现这个类,再回过头来写这个调用逻辑了。
#动态导入
#以一个字符串的名字的形式导入模块
#动态导入,可以导入字符串名字的模块。但是无论你调用多少层,总是获取最顶层a = __import__('module_test.bin') #导入相当于执行了一遍文件.print(a) #获取的不是你导入的最低层bin,而是顶层module_testa.bin.test() #所以得这么才能调用到,但是为了能调用到,__import__里还是得写到最低层,即'module_test.bin'#导入的过程相当于执行了一次文件,下面拿以前的导入方式来距离from module_test import binbin.test() #bin下定义的函数得通过bin这个类调用,不能直接test()#或者直接把模块里的所有东西导入到当前,那就可以直接test()调用了from module_test.bin import * # * 跟linux里的通配符一个意思test()#但是这种用*的方法没法导入_开头的函数_test() #没找到这个函数#不过python没有在真正逻辑层面上限制你调用,你可以这么调_testfrom module_test.bin import test,_test_test()#或者用__import__调用也可以a.bin._test()#另一种更好的方法动态导入import importliba = importlib.import_module('module_test.bin')a.test()a._test()
#为什么这里要讲动态导入呢?因为动态导入就是基于反射做的。而模块其实就是一个类
比如 importlib.import_module('module_test.bin'),相当于从importlib这个类里找到一个import_module方法;
这个方法能从当前类(文件夹就是一个类)下找到module_test这个类,再从中找到bin这个类
这也就解释了为什么pycharm创建文件夹的时候,会生成一个__init__的文件,并且自动执行,其实它就是用来实例化出对象(文件夹里的文件)的
# __getattr__ 、 __setattr__ 、 __delattr__
(注意:形如 __XXX__ 都是类的内置方法,当不自己设置__XXX__的时候用内置的,设置跟他名字一样的__XXX__则用你设置的)
(常用)#__getattr__ : 当对象或类调用不存在的方法时,会自动运行类下的__getattr__方法 (如果不像下面自己设置__getattr__则调用内置的结果是输出报错信息到屏幕)
(不常用)#__delattr__: 类或对象在删除属性时,会自动运行__delattr__
(不常用)#__setattr__: 类或对象在设置属性时,会自动运行__setattr__
class Foo : name = 'abc' def __init__(self,x): self.x = x def __getattr__(self, item): print('__getattr__被执行') def __delattr__(self, item): print("__delattr__被执行") def __setattr__(self, key, value): print("__setattr__被执行") # self.key = value #不能这么设置属性,因为这里一设置又会触发__setattr__的执行,然后再设置,无限循环 self.__dict__[key] = value #这样设置才对a = Foo(1)a.adfdaf #类或对象调用不存在的属性时,会自动运行__getattr__del a.name #类或对象在删除属性时,会自动运行__delattr__a.name1 = 'cba' #触发__setattr__