一、多态
什么是多态:
类的继承有两层意义:1.改变 2.扩展
多态就是类的这两层意义的一个具体的实现机。 即:调用不同类实例化的对象,下的相同的方法,实现的过程不一样
python中的标准类型就是多态概念的一个很好的示范如(str.__len__(),list.__len__(),tuple.__len__可以len(str),len(list),len(tuple))
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' class H2O: def __init__(self,name,temperature): self.name=name self.temperature=temperature def turn_ice(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) def func(obj): obj.turn_ice() func(w1) func(i1) func(s1) w2=Water('水',101) func(w2) 多态
多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
多态表明了动态(又名,运行时)绑定的存在,允计重载及运行时类型确定和验证。 运行时候的绑定状态。str1和l都绑定了一个方法,都继承了同一个父类
#len(object)====>object.__len()__ 所有的数据类型都是由:type来的。和水、冰斗继承水的例子是一样的。 str1='sdfdf' print(len(str1)) print(str1.__len__()) #本质上就是调用str1下面的__len__方法! l=['s','d','f','d','f'] print(len(l)) print(l.__len__()) #本质上调用l下面的len方法 tu=('s','d','f','d','f') print(len(tu)) print(tu.__len__()) #多个类:str1、l、tu同时访问他们下面都有的共同的len方法。(共同的属性和动作来访问,而不用考虑他们具体的类) 5 5 5 5 5 5
举例:
水是一个类
不同温度,水被实例化成了不同的状态:冰,水蒸气,雾(然而很多人就理解到这一步就任务此乃多态,错,fuck!,多态是运行时绑定的存在)
(多态体现在由同一个类实例化出的多个对象,这些对象执行相同的方法时,执行的过程和结果是不一样的)
冰,水蒸气,雾,有一个共同的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的两个过程,虽然调用的方法都一样
class H2o: def __init__(self,name,temperature): self.name=name self.temperature=temperature def turn_ice(self): if self.temperature<0: print('%s温度变成了兵' %self.name) if self.temperature>0 and self.temperature<100: print('%s温度变成了水' %self.name) if 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) w1.turn_ice() #和l.__len__()、tu.__len__()、str1.__len__() i1.turn_ice() s1.turn_ice()
def func(object): object.turn_ice() func(w1) #len(l)、len(str1)、len(tu) func(i1) func(s1) w2=Water('水',101) func(w2)
正本清源六:有人说,面向对象三大特性,封装,多态,继承--->小傻逼
多态实际上是依附于继承的两种含义的:“改变”和“扩展”本身就意味着必须有机制去自动选用你改变/扩展过的版本,故无多态,则两种含义就不可能实现。
所以,多态实质上是继承的实现细节;那么让多态与封装、继承这两个概念并列,显然是不符合逻辑的.
1、继承实现代码的重用(turn_ice)
2、有一个机制把重用的代码能有效的利用起来,而不是只是定义而不用。---这个机制就是多态。
二、封装
封装是啥,抛开面向对象,你单去想什么是装,装就是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,什么是封,封就是把麻袋封上口子。
在面向对象中这个麻袋就是你的类或者对象,类或者对象这俩麻袋内部装了数据属性和函数属性.那么对于类和对象来说‘封’的概念从何而来,其实封的概念代表隐藏
在学完了面向对象的类和对象相关的知识后,大家都知道了如何把属性装进类或者对象中,那么如何完成封的效果呢?
第一个层面的封装:类就是麻袋,这本身就是一种封装.定义过程就是装的过程!装到属性字典里面:
例:略
第二个层面的封装:类中定义私有的,只在类的内部使用,外部无法访问
———隐藏,可以通过特定的约定隐藏自己的数据属性和函数属性
python不依赖语言特性去实现第二层面的封装,而是通过遵循一定的数据属性和函数属性的命名约定来达到封的效果
约定一:任何以单下划线开头的名字都应该是内部的,私有的
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' 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) #我们明明约定好了的,只要属性前加一个单下划线,那他就属于内部的属性,不能被外部调用了啊,为何还能调用??? print(People._star) p1=People('3706861900121221212','alex',28,10) print(p1._age,p1._salary) p1._get_id()
瞬间你就蒙蔽了,私有的怎么还能被访问私有的怎么还能被访问,我上去就是一巴掌,这只是一种约定
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点
约定二:双下划线开头的名字
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' 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) print(People.__dict__)#__star存到类的属性字典中被重命名为_People__star p1=People('333333','alex',18,10) print(p1.__dict__)#__age存到类的属性字典中被重命名为_People__age print(People.star) #好像不能访问了喔! print(p1.age)# 好像也不能访问了喔!
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' 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) print(People.__dict__)#__star存到类的属性字典中被重命名为_People__star p1=People('333333','alex',18,10) print(p1.__dict__)#__age存到类的属性字典中被重命名为_People__age # print(People.star) #好像不能访问了喔! # print(p1.age)# 好像也不能访问了喔! print(People._People__star) #我曹,又能访问了why? print(p1._People__age)#我曹,又能访问了why? 诡异的事情发生了
约定二到底有何卵用?-》双下滑线开头的属性在继承给子类时,子类是无法覆盖的(原理也是基于python自动做了双下滑线开头的名字的重命名工作)
第三个层面的封装:
明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用(这才是真正的封装,具体实现,会在面向对象进阶中讲)
利用接口函数访问内部函数。后期出现属性频繁被用的情况下,可以定义接口函数。用来访问(1、需要通过内部出具计算得出的。2、就是内部数据)。最后就成了:千疮百孔!
class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #此时我们想求的是面积 return self.__width * self.__length *self.__high def tell_width(self): #访问__width的数据 return self.__width r1=Room('卫生间','alex',100,100,10000) # arear=r1.__width * r1.__length print(r1.tell_area())
总结:
上面提到有两种不同的编码约定(单下划线和双下划线 )来命名私有属性,那么问 题就来了:到底哪种方式好呢?大多数而言,你应该让你的非公共名称以单下划线开 头。但是,如果你清楚你的代码会涉及到子类,并且有些内部属性应该在子类中隐藏 起来,那么才考虑使用双下划线方案。 但是无论哪种方案,其实python都没有从根本上限制你的访问。
然后你就懵逼了,你这不跟我扯犊子呢么,讲了半天得出的结论是:不可能完成真正的封。没错,这不是我的错,也不是龟叔的错
正本清源七:封装最简单了,装是定义属性,封是“把不想让别人看到、以后可能修改的东西用隐藏起来---》小傻逼
在其他语言中私有的属性在外部就是不能被访问的,在python中则相反面向对象有封装,多态,继承三大特性没错,这只是面向对象所支持的特性,一些人错误的认为,面向对象编程兼备的三大特性,那一定就是好的,于是他们随意把属性定义成私有的,却又在需求变更时发现自己的定义不合理,又不得不通过某种方式把私有的属性公开,像这种(我们用python来模拟其他语言中滥用封装,发现不对劲后开始在类中到处挖洞)
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' class People: __star='earth' def __init__(self,id,name,age,salary): self.id=id self.name=name self.__age=age self._salary=salary def tell_star(self): print('%s 来自于星球[%s]' %(self.name,self.__star)) p1=People('12','alex',18,12) p1.tell_star()
上述的这种例子是,不动脑子就把一个属性做成了私有的属性,私有属性外部是无法被访问的,后来的某天你发现,这个属性其实是应该被放开的,于是你的解决方法是定义一个访问函数,在内部去调用私有属性,的方法完美的解决了这个问题,没错,访问函数确实是一种好东西,但是你这个问题的出在你滥用封装,后来利用访问函数去帮你填坑。
python并不严格限制外部对私有属性的访问,龟叔之所以这么设计,我估计一个重要原因就是见多了上面这种论调的人于是干脆把我们的python做成非严格意义的封装,以避免你滥用封装。
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者;而外部调用者也可以知道自己不可以碰哪里。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #此时我们想求的是面积 return self.__width * self.__length r1=Room('卫生间','alex',100,100,10000) area=r1.tell_area() print(area)
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #此时我们想求的是体积,内部逻辑变了,而外部调用感知不到,仍然使用该方法 return self.__width * self.__length * self.__high r1=Room('卫生间','alex',100,100,10000) area=r1.tell_area() print(area)
三、自省和反射:
1 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
2 python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
判断object中有没有一个name字符串对应的方法或属性
def getattr(object, name, default=None): # known special case of getattr """ getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case. """ pass getattr(object, name, default=None)
def setattr(x, y, v): # real signature unknown; restored from __doc__ """ Sets the named attribute on the given object to the specified value. setattr(x, 'y', v) is equivalent to ``x.y = v'' """ pass
def delattr(x, y): # real signature unknown; restored from __doc__ """ Deletes the named attribute from the given object. delattr(x, 'y') is equivalent to ``del x.y'' """ pass
2.1、hasattr(object,name)
attr:属性,判断是否有这个属性。检测name是否在 object里面。(对象,字符串)
你在调用方法或者属性,都在找自己的属性字典,而字典的key是一个字符串。所以name必须是一个字符串类型。
class BlackMedium: feature='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('【%s】正在卖房子,傻逼才买呢' %self.name) def rent_house(self): print('【%s】' %self.name) b1=BlackMedium('万成置地','天露园') #b1.name #相当于b1.__dic__['name'] print(hasattr(b1,'name')) print(hasattr(b1,'sell_house')) print(hasattr(BlackMedium,'sell_house'))
2.2、getattr(obj,name,default=none) 查找
class BlackMedium: feature='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('【%s】正在卖房子,傻逼才买呢' %self.name) def rent_house(self): print('【%s】' %self.name) b1=BlackMedium('万成置地','天露园') #b1.name #相当于b1.__dic__['name'] print(getattr(b1,'name')) 相当于:b1.name print(getattr(b1,'sell_house','没有这个属性')) #print(getattr(b1,'sell_house1'))#找不到报错!没写默认参数。相当于:b1.sell_house print(getattr(b1,'sell_house1','没有这个属性')) #运行结果:没有这个属性。默认参数:找不到情况下返回的值。 和这
2.3、setattr()
class BlackMedium: feature='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('【%s】正在卖房子,傻逼才买呢' %self.name) def rent_house(self): print('【%s】' %self.name) b1=BlackMedium('万成置地','天露园') setattr(b1,'sb',True) #相当于:b1.sb=value print(b1.__dict__) #{'name': '万成置地', 'sb': True, 'addr': '天露园'} 运行结果
setattr(b1,'func',lambda x:x+1)
2.4、delattr()
class BlackMedium: feature='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('【%s】正在卖房子,傻逼才买呢' %self.name) def rent_house(self): print('【%s】' %self.name) b1=BlackMedium('万成置地','天露园') delattr(b1,'name') #相当于:del b1.name
总结:
class BlackMedium: feature='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼' %self.name) def rent_house(self): print('%s 黑中介租房子啦,傻逼才租呢' %self.name) b1=BlackMedium('万成置地','回龙观天露园') #检测是否含有某属性 print(hasattr(b1,'name')) print(hasattr(b1,'sell_house')) #获取属性 n=getattr(b1,'name') print(n) func=getattr(b1,'rent_house') func() # getattr(b1,'aaaaaaaa') #报错 print(getattr(b1,'aaaaaaaa','不存在啊')) #设置属性 setattr(b1,'sb',True) setattr(b1,'show_name',lambda self:self.name+'sb') print(b1.__dict__) print(b1.show_name(b1)) #删除属性 delattr(b1,'addr') delattr(b1,'show_name') delattr(b1,'show_name111')#不存在,则报错 print(b1.__dict__)
class Foo(object): staticField = "old boy" def __init__(self): self.name = 'wupeiqi' def func(self): return 'func' @staticmethod def bar(): return 'bar' print getattr(Foo, 'staticField') print getattr(Foo, 'func') print getattr(Foo, 'bar')
#!/usr/bin/env python # -*- coding:utf-8 -*- import sys def s1(): print 's1' def s2(): print 's2' this_module = sys.modules[__name__] hasattr(this_module, 's1') getattr(this_module, 's2')
导入其他模块,利用反射查找该模块是否存在某个方法
#!/usr/bin/env python # -*- coding:utf-8 -*- def test(): print('from the test')
#!/usr/bin/env python # -*- coding:utf-8 -*- """ 程序目录: module_test.py index.py 当前文件: index.py """ import module_test as obj #obj.test() print(hasattr(obj,'test')) getattr(obj,'test')()
3 为什么用反射之反射的好处
好处一:实现可插拔机制
有俩程序员,一个alex,一个是egon,alex在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,alex想到了反射,使用了反射机制alex可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现alex想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
class FtpClient: 'ftp客户端,但是还么有实现具体的功能' def __init__(self,addr): print('正在连接服务器[%s]' %addr) self.addr=addr
#from module import FtpClient f1=FtpClient('192.168.1.1') if hasattr(f1,'get'): func_get=getattr(f1,'get') func_get() else: print('---->不存在此方法') print('处理其他的逻辑')
好处二:动态导入模块(基于反射当前模块成员)(欠)
九 面向对象的优点
从编程进化论我们得知,面向对象是一种更高等级的结构化编程方式,它的好处就两点
1:通过封装明确了内外,你作为类的缔造者,你是上帝,上帝造物的逻辑你无需知道(你知道了你tm也成上帝了),上帝想让你知道的你才能知道,这样就明确了划分了等级,物就是调用者,上帝就是物的创造者
2:通过继承+多态在语言层面支持了归一化设计
注意:不用面向对象语言(即不用class),一样可以做归一化(如老掉牙的泛文件概念、游戏行业的一切皆精灵),一样可以封装(通过定义模块和接口),只是用面向对象语言可以直接用语言元素显式声明这些而已;而用了面向对象语言,满篇都是class,并不等于就有了归一化的设计。甚至,因为被这些花哨的东西迷惑,反而更加不知道什么才是设计。
十 python中关于OOP的常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
多态表明了动态(又名,运行时)绑定的存在,允计重载及运行时类型确定和验证。
举例:
水是一个类
不同温度,水被实例化成了不同的状态:冰,水蒸气,雾(然而很多人就理解到这一步就任务此乃多态,错,fuck!,多态是运行时绑定的存在)
(多态体现在由同一个类实例化出的多个对象,这些对象执行相同的方法时,执行的过程和结果是不一样的)
冰,水蒸气,雾,有一个共同的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的两个过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__