08-面向对象----
一 面向?qū)ο蟮某绦蛟O(shè)計(jì)的由來
請(qǐng)參考:http://www.cnblogs.com/linhaifeng/articles/6428835.html
二 什么是面向?qū)ο蟮某绦蛟O(shè)計(jì)及為什么要有它
面向過程的程序設(shè)計(jì)的核心是過程,過程即解決問題的步驟,面向過程的設(shè)計(jì)就好比精心設(shè)計(jì)好一條流水線,考慮周全什么時(shí)候處理什么東西。
優(yōu)點(diǎn)是:極大的降低了程序的復(fù)雜度
缺點(diǎn)是:一套流水線或者流程就是用來解決一個(gè)問題,生產(chǎn)汽水的流水線無(wú)法生產(chǎn)汽車,即便是能,也得是大改,改一個(gè)組件,牽一發(fā)而動(dòng)全身。
應(yīng)用場(chǎng)景:一旦完成基本很少改變的場(chǎng)景,著名的例子有Linux內(nèi)核,git,以及Apache HTTP Server等。
面向?qū)ο蟮某绦蛟O(shè)計(jì)的核心是對(duì)象,要理解對(duì)象為何物,必須把自己當(dāng)成上帝,上帝眼里世間存在的萬(wàn)物皆為對(duì)象,不存在的也可以創(chuàng)造出來。面向?qū)ο蟮某绦蛟O(shè)計(jì)好比如來設(shè)計(jì)西游記,如來要解決的問題是把經(jīng)書傳給東土大唐,如來想了想解決這個(gè)問題需要四個(gè)人:唐僧,沙和尚,豬八戒,孫悟空,每個(gè)人都有各自的特征和技能(這就是對(duì)象的概念,特征和技能分別對(duì)應(yīng)對(duì)象的數(shù)據(jù)屬性和方法屬性),然而這并不好玩,于是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經(jīng)路上被搞死,又安排了一群神仙保駕護(hù)航,這些都是對(duì)象。然后取經(jīng)開始,師徒四人與妖魔鬼怪神仙交互著直到最后取得真經(jīng)。如來根本不會(huì)管師徒四人按照什么流程去取。
面向?qū)ο蟮某绦蛟O(shè)計(jì)的
優(yōu)點(diǎn)是:解決了程序的擴(kuò)展性。對(duì)某一個(gè)對(duì)象單獨(dú)修改,會(huì)立刻反映到整個(gè)體系中,如對(duì)游戲中一個(gè)人物參數(shù)的特征和技能修改都很容易。
缺點(diǎn):可控性差,無(wú)法向面向過程的程序設(shè)計(jì)流水線式的可以很精準(zhǔn)的預(yù)測(cè)問題的處理流程與結(jié)果,面向?qū)ο蟮某绦蛞坏╅_始就由對(duì)象之間的交互解決問題,即便是上帝也無(wú)法預(yù)測(cè)最終結(jié)果。于是我們經(jīng)常看到一個(gè)游戲人某一參數(shù)的修改極有可能導(dǎo)致陰霸的技能出現(xiàn),一刀砍死3個(gè)人,這個(gè)游戲就失去平衡。
應(yīng)用場(chǎng)景:需求經(jīng)常變化的軟件,一般需求的變化都集中在用戶層,互聯(lián)網(wǎng)應(yīng)用,企業(yè)內(nèi)部軟件,游戲等都是面向?qū)ο蟮某绦蛟O(shè)計(jì)大顯身手的好地方
面向?qū)ο蟮某绦蛟O(shè)計(jì)并不是全部。對(duì)于一個(gè)軟件質(zhì)量來說,面向?qū)ο蟮某绦蛟O(shè)計(jì)只是用來解決擴(kuò)展性。
三 類和對(duì)象
提示:python的class術(shù)語(yǔ)與c++有一定區(qū)別,與 Modula-3更像。
python中一切皆為對(duì)象,且python3統(tǒng)一了類與類型的概念,類型就是類,所以,不管你信不信,你已經(jīng)使用了很長(zhǎng)時(shí)間的類了
1 >>> dict #類型dict就是類dict 2 <class 'dict'> 3 >>> d=dict(name='egon') #實(shí)例化 4 >>> d.pop('name') #向d發(fā)一條消息,執(zhí)行d的方法pop 5 'egon'基于面向?qū)ο笤O(shè)計(jì)一個(gè)款游戲:英雄聯(lián)盟,每個(gè)玩家選一個(gè)英雄,每個(gè)英雄都有自己的特征和和技能,特征即數(shù)據(jù)屬性,技能即方法屬性,特征與技能的結(jié)合體就一個(gè)對(duì)象。
從一組對(duì)象中提取相似的部分就是類,類也是特征與技能的結(jié)合體,特征即數(shù)據(jù)并且是所有對(duì)象共享的數(shù)據(jù),技能即函數(shù)屬性并且是所有對(duì)象共享的函數(shù)屬性。
garen_hero.Q()稱為向garen_hero這個(gè)對(duì)象發(fā)送了一條消息,讓他去執(zhí)行Q這個(gè)函數(shù),完成一個(gè)功能,類似的有:
garen_hero.W()
garen_hero.E()
garen_hero.R()
一個(gè)英雄可以攻擊另外一個(gè)英雄,這就是對(duì)象之間的交互
garen_hero.attack(Riven)
?
?
3.1 類相關(guān)知識(shí)
3.1.1 初識(shí)類
在python中聲明函數(shù)與聲明類很相似
聲明函數(shù)
1 def functionName(args): 2 '函數(shù)文檔字符串' 3 函數(shù)體 1 ''' 2 class 類名: 3 '類的文檔字符串' 4 類體 5 ''' 6 7 #我們創(chuàng)建一個(gè)類 8 class Data: 9 pass 1 大前提: 2 1.只有在python2中才分新式類和經(jīng)典類,python3中統(tǒng)一都是新式類 3 2.新式類和經(jīng)典類聲明的最大不同在于,所有新式類必須繼承至少一個(gè)父類 4 3.所有類甭管是否顯式聲明父類,都有一個(gè)默認(rèn)繼承object父類(講繼承時(shí)會(huì)講,先記住) 5 在python2中的區(qū)分 6 經(jīng)典類: 7 class 類名: 8 pass 9 10 經(jīng)典類: 11 class 類名(父類): 12 pass 13 14 在python3中,上述兩種定義方式全都是新式類 在本節(jié)開頭介紹得出結(jié)論,類是數(shù)據(jù)與函數(shù)的結(jié)合,二者稱為類的屬性 class Garen: #定義英雄蓋倫的類,不同的玩家可以用它實(shí)例出自己英雄;camp='Demacia' #所有玩家的英雄(蓋倫)的陣營(yíng)都是Demacia;def attack(self,enemy): #普通攻擊技能,enemy是敵人;enemy.life_value-=self.aggressivity #根據(jù)自己的攻擊力,攻擊敵人就減掉敵人的生命值。3.1.2 類有兩種作用:屬性引用和實(shí)例化
3.1.2.1 屬性引用(類名.屬性)
>>> Garen.camp #引用類的數(shù)據(jù)屬性,該屬性與所有對(duì)象/實(shí)例共享 'Demacia' >>> Garen.attack #引用類的函數(shù)屬性,該屬性也共享 <function Garen.attack at 0x101356510> >>> Garen.name='Garen' #增加屬性 >>> del Garen.name #刪除屬性3.1.2.2 實(shí)例化(__init__與self)
類名加括號(hào)就是實(shí)例化,會(huì)自動(dòng)觸發(fā)__init__函數(shù)的運(yùn)行,可以用它來為每個(gè)實(shí)例定制自己的特征
class Garen: #定義英雄蓋倫的類,不同的玩家可以用它實(shí)例出自己英雄;camp='Demacia' #所有玩家的英雄(蓋倫)的陣營(yíng)都是Demacia;def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻擊力58...;self.nickname=nickname #為自己的蓋倫起個(gè)別名;self.aggressivity=aggressivity #英雄都有自己的攻擊力;self.life_value=life_value #英雄都有自己的生命值;def attack(self,enemy): #普通攻擊技能,enemy是敵人;enemy.life_value-=self.aggressivity #根據(jù)自己的攻擊力,攻擊敵人就減掉敵人的生命值。實(shí)例化:類名+括號(hào)
>>> g1=Garen('草叢倫') #就是在執(zhí)行Garen.__init__(g1,'草叢倫'),然后執(zhí)行__init__內(nèi)的代碼g1.nickname=‘草叢倫’等self的作用是在實(shí)例化時(shí)自動(dòng)將對(duì)象/實(shí)例本身傳給__init__的第一個(gè)參數(shù),self可以是任意名字,但是瞎幾把寫別人就看不懂了。
這種自動(dòng)傳遞的機(jī)制還體現(xiàn)在g1.attack的調(diào)用上,后續(xù)會(huì)介紹。
一:我們定義的類的屬性到底存到哪里了?有兩種方式查看 dir(類名):查出的是一個(gè)名字列表 類名.__dict__:查出的是一個(gè)字典,key為屬性名,value為屬性值二:特殊的類屬性 類名.__name__# 類的名字(字符串) 類名.__doc__# 類的文檔字符串 類名.__base__# 類的第一個(gè)父類(在講繼承時(shí)會(huì)講) 類名.__bases__# 類所有父類構(gòu)成的元組(在講繼承時(shí)會(huì)講) 類名.__dict__# 類的字典屬性 類名.__module__# 類定義所在的模塊 類名.__class__# 實(shí)例對(duì)應(yīng)的類(僅新式類中)3.2 對(duì)象相關(guān)知識(shí)
對(duì)象是關(guān)于類而實(shí)際存在的一個(gè)例子,即實(shí)例
>>> g1=Garen('草叢倫') #類實(shí)例化得到g1這個(gè)實(shí)例,基于該實(shí)例我們講解實(shí)例相關(guān)知識(shí)3.2.1 對(duì)象/實(shí)例只有一種作用:屬性引用
#對(duì)象/實(shí)例本身其實(shí)只有數(shù)據(jù)屬性 >>> g1.nickname '草叢倫' >>> g1.aggressivity 58 >>> g1.life_value 455 ''' 查看實(shí)例屬性 同樣是dir和內(nèi)置__dict__兩種方式 特殊實(shí)例屬性 __class__ __dict__ .... '''對(duì)象/實(shí)例本身只有數(shù)據(jù)屬性,但是python的class機(jī)制會(huì)將類的函數(shù)綁定到對(duì)象上,稱為對(duì)象的方法,或者叫綁定方法
>>> g1.attack #對(duì)象的綁定方法 <bound method Garen.attack of <__main__.Garen object at 0x101348dd8>>>>> Garen.attack #對(duì)象的綁定方法attack本質(zhì)就是調(diào)用類的函數(shù)attack的功能,二者是一種綁定關(guān)系 <function Garen.attack at 0x101356620>對(duì)象的綁定方法的特別之處在于:obj.func()會(huì)把obj傳給func的第一個(gè)參數(shù)。
3.3 對(duì)象之間的交互
我們可以仿照garen類再創(chuàng)建一個(gè)Riven類
class Riven:camp='Noxus' #所有玩家的英雄(銳雯)的陣營(yíng)都是Noxus;def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻擊力54;self.nickname=nickname #為自己的銳雯起個(gè)別名;self.aggressivity=aggressivity #英雄都有自己的攻擊力;self.life_value=life_value #英雄都有自己的生命值;def attack(self,enemy): #普通攻擊技能,enemy是敵人;enemy.life_value-=self.aggressivity #根據(jù)自己的攻擊力,攻擊敵人就減掉敵人的生命值。實(shí)例出一個(gè)Riven來
>>> r1=Riven('銳雯雯')交互:銳雯雯攻擊草叢倫,反之一樣
>>> g1.life_value 455 >>> r1.attack(g1) >>> g1.life_value 4013.4 類名稱空間與對(duì)象/實(shí)例名稱空間
創(chuàng)建一個(gè)類就會(huì)創(chuàng)建一個(gè)類的名稱空間,用來存儲(chǔ)類中定義的所有名字,這些名字稱為類的屬性
而類有兩種屬性:數(shù)據(jù)屬性和函數(shù)屬性
其中類的數(shù)據(jù)屬性是共享給所有對(duì)象的
>>> id(r1.camp) #本質(zhì)就是在引用類的camp屬性,二者id一樣 4315241024 >>> id(Riven.camp) 4315241024而類的函數(shù)屬性是綁定到所有對(duì)象的:
>>> id(r1.attack) 4302501512 >>> id(Riven.attack) 4315244200''' r1.attack就是在執(zhí)行Riven.attack的功能,python的class機(jī)制會(huì)將Riven的函數(shù)屬性attack綁定給r1,r1相當(dāng)于拿到了一個(gè)指針,指向Riven類的attack功能除此之外r1.attack()會(huì)將r1傳給attack的第一個(gè)參數(shù) '''創(chuàng)建一個(gè)對(duì)象/實(shí)例就會(huì)創(chuàng)建一個(gè)對(duì)象/實(shí)例的名稱空間,存放對(duì)象/實(shí)例的名字,稱為對(duì)象/實(shí)例的屬性
在obj.name會(huì)先從obj自己的名稱空間里找name,找不到則去類中找,類也找不到就找父類...最后都找不到就拋出異常?
3.5 小結(jié)
瑞文
class Riven:camp='Noxus'def __init__(self,nickname,aggressivity=54,life_value=414,money=1001,armor=3):self.nickname=nicknameself.aggressivity=aggressivityself.life_value=life_valueself.money=moneyself.armor=armordef attack(self,enemy):damage_value=self.aggressivity-enemy.armorenemy.life_value-=damage_value蓋倫
class Garen:camp='Demacia'def __init__(self,nickname,aggressivity=58,life_value=455,money=100,armor=10):self.nickname=nicknameself.aggressivity=aggressivityself.life_value=life_valueself.money=moneyself.armor=armordef attack(self,enemy):damage_value=self.aggressivity-enemy.armorenemy.life_value-=damage_value定義裝備:
class BlackCleaver:def __init__(self,price=475,aggrev=9,life_value=100):self.price=priceself.aggrev=aggrevself.life_value=life_valuedef update(self,obj):obj.money-=self.price #減錢obj.aggressivity+=self.aggrev #加攻擊obj.life_value+=self.life_value #加生命值def fire(self,obj): #這是該裝備的主動(dòng)技能,噴火,燒死對(duì)方obj.life_value-=1000 #假設(shè)火燒的攻擊力是1000測(cè)試交互
r1=Riven('草叢倫') g1=Garen('蓋文') b1=BlackCleaver()print(r1.aggressivity,r1.life_value,r1.money) #r1的攻擊力,生命值,護(hù)甲if r1.money > b1.price:r1.b1=b1b1.update(r1)print(r1.aggressivity,r1.life_value,r1.money) #r1的攻擊力,生命值,護(hù)甲print(g1.life_value) r1.attack(g1) #普通攻擊 print(g1.life_value) r1.b1.fire(g1) #用裝備攻擊 print(g1.life_value) #g1的生命值小于0就死了按照這種思路一點(diǎn)一點(diǎn)的設(shè)計(jì)類和對(duì)象,最終你完全可以實(shí)現(xiàn)一個(gè)對(duì)戰(zhàn)類游戲。
四 繼承與派生
4.1 什么是繼承
繼承是一種創(chuàng)建新的類的方式,在python中,新建的類可以繼承自一個(gè)或者多個(gè)父類,原始類稱為基類或超類,新建的類稱為派生類或子類。
python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類passclass ParentClass2: #定義父類passclass SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClasspassclass SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號(hào)分隔開多個(gè)繼承的類pass查看繼承
>>> SubClass1.__bases__ (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)提示:如果沒有指定基類,python的類會(huì)默認(rèn)繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實(shí)現(xiàn)。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)4.2 繼承與抽象(先抽象再繼承)
抽象即抽取類似或者說比較像的部分。
抽象分成兩個(gè)層次:?
1.將奧巴馬和梅西這倆對(duì)象比較像的部分抽取成類;?
2.將人,豬,狗這三個(gè)類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關(guān)注點(diǎn),降低復(fù)雜度)
繼承:是基于抽象的結(jié)果,通過編程語(yǔ)言去實(shí)現(xiàn)它,肯定是先經(jīng)歷抽象這個(gè)過程,才能通過繼承的方式去表達(dá)出抽象的結(jié)構(gòu)。
抽象只是分析和設(shè)計(jì)的過程中,一個(gè)動(dòng)作或者說一種技巧,通過抽象可以得到類
4.3 繼承與重用性
==========================第一部分 例如貓可以:喵喵叫、吃、喝、拉、撒狗可以:汪汪叫、吃、喝、拉、撒如果我們要分別為貓和狗創(chuàng)建一個(gè)類,那么就需要為 貓 和 狗 實(shí)現(xiàn)他們所有的功能,偽代碼如下:#貓和狗有大量相同的內(nèi)容 class 貓:def 喵喵叫(self):print '喵喵叫'def 吃(self):# do somethingdef 喝(self):# do somethingdef 拉(self):# do somethingdef 撒(self):# do somethingclass 狗:def 汪汪叫(self):print '喵喵叫'def 吃(self):# do somethingdef 喝(self):# do somethingdef 拉(self):# do somethingdef 撒(self):# do something==========================第二部分 上述代碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實(shí)現(xiàn):動(dòng)物:吃、喝、拉、撒貓:喵喵叫(貓繼承動(dòng)物的功能)狗:汪汪叫(狗繼承動(dòng)物的功能)偽代碼如下: class 動(dòng)物:def 吃(self):# do somethingdef 喝(self):# do somethingdef 拉(self):# do somethingdef 撒(self):# do something# 在類后面括號(hào)中寫入另外一個(gè)類名,表示當(dāng)前類繼承另外一個(gè)類 class 貓(動(dòng)物):def 喵喵叫(self):print '喵喵叫'# 在類后面括號(hào)中寫入另外一個(gè)類名,表示當(dāng)前類繼承另外一個(gè)類 class 狗(動(dòng)物):def 汪汪叫(self):print '喵喵叫'==========================第三部分 #繼承的代碼實(shí)現(xiàn) class Animal:def eat(self):print("%s 吃 " %self.name)def drink(self):print ("%s 喝 " %self.name)def shit(self):print ("%s 拉 " %self.name)def pee(self):print ("%s 撒 " %self.name)class Cat(Animal):def __init__(self, name):self.name = nameself.breed = '貓'def cry(self):print('喵喵叫')class Dog(Animal):def __init__(self, name):self.name = nameself.breed='狗'def cry(self):print('汪汪叫')# ######### 執(zhí)行 #########c1 = Cat('小白家的小黑貓') c1.eat()c2 = Cat('小黑的小白貓') c2.drink()d1 = Dog('胖子家的小瘦狗') d1.eat() view code在開發(fā)程序的過程中,如果我們定義了一個(gè)類A,然后又想新建立另外一個(gè)類B,但是類B的大部分內(nèi)容與類A的相同時(shí)
我們不可能從頭開始寫一個(gè)類B,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會(huì)‘遺傳’A的所有屬性(數(shù)據(jù)屬性和函數(shù)屬性),實(shí)現(xiàn)代碼重用
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.aggressivity class Garen(Hero):passclass Riven(Hero):passg1=Garen('草叢倫',100,300) r1=Riven('銳雯雯',57,200)print(g1.life_value) r1.attack(g1) print(g1.life_value)''' 運(yùn)行結(jié)果 300 243 ''' view code提示:用已經(jīng)有的類建立一個(gè)新的類,這樣就重用了已經(jīng)有的軟件中的一部分設(shè)置大部分,大大生了編程工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標(biāo)準(zhǔn)庫(kù),來定制新的數(shù)據(jù)類型,這樣就是大大縮短了軟件開發(fā)周期,對(duì)大型軟件開發(fā)來說,意義重大.
注意:像g1.life_value之類的屬性引用,會(huì)先從實(shí)例中找life_value然后去類中找,然后再去父類中找...直到最頂級(jí)的父類。
當(dāng)然子類也可以添加自己新的屬性或者在自己這里重新定義這些屬性(不會(huì)影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那么調(diào)用新增的屬性時(shí),就以自己為準(zhǔn)了。
class Riven(Hero):camp='Noxus'def attack(self,enemy): #在自己這里定義新的attack,不再使用父類的attack,且不會(huì)影響父類print('from riven')def fly(self): #在自己這里定義新的print('%s is flying' %self.nickname) view code在子類中,新建的重名的函數(shù)屬性,在編輯函數(shù)內(nèi)功能的時(shí)候,有可能需要重用父類中重名的那個(gè)函數(shù)功能,應(yīng)該是用調(diào)用普通函數(shù)的方式,即:類名.func(),此時(shí)就與調(diào)用普通函數(shù)無(wú)異了,因此即便是self參數(shù)也要為其傳值
class Riven(Hero):camp='Noxus'def __init__(self,nickname,aggressivity,life_value,skin):Hero.__init__(self,nickname,aggressivity,life_value) #調(diào)用父類功能self.skin=skin #新屬性def attack(self,enemy): #在自己這里定義新的attack,不再使用父類的attack,且不會(huì)影響父類Hero.attack(self,enemy) #調(diào)用功能print('from riven')def fly(self): #在自己這里定義新的print('%s is flying' %self.nickname)r1=Riven('銳雯雯',57,200,'比基尼') r1.fly() print(r1.skin)''' 運(yùn)行結(jié)果 銳雯雯 is flying 比基尼''' view code4.4 組合與重用性
軟件重用的重要方式除了繼承之外還有另外一種方式,即:組合
組合指的是,在一個(gè)類中以另外一個(gè)類的對(duì)象作為數(shù)據(jù)屬性,稱為類的組合
其實(shí)早在3.5小節(jié)中我們就體會(huì)了組合的用法,比如一個(gè)英雄有一個(gè)裝備
>>> class Equip: #武器裝備類 ... def fire(self): ... print('release Fire skill') ... >>> class Riven: #英雄Riven的類,一個(gè)英雄需要有裝備,因而需要組合Equip類 ... camp='Noxus' ... def __init__(self,nickname): ... self.nickname=nickname ... self.equip=Equip() #用Equip類產(chǎn)生一個(gè)裝備,賦值給實(shí)例的equip屬性 ... >>> r1=Riven('銳雯雯') >>> r1.equip.fire() #可以使用組合的類產(chǎn)生的對(duì)象所持有的方法 release Fire skill view code組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場(chǎng)景皆不同,
1.繼承的方式
通過繼承建立了派生類與基類之間的關(guān)系,它是一種'是'的關(guān)系,比如白馬是馬,人是動(dòng)物。
當(dāng)類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師
>>> class Teacher: ... def __init__(self,name,gender): ... self.name=name ... self.gender=gender ... def teach(self): ... print('teaching') ... >>> >>> class Professor(Teacher): ... pass ... >>> p1=Professor('egon','male') >>> p1.teach() teaching view code2.組合的方式
用組合的方式建立了類與組合的類之間的關(guān)系,它是一種‘有’的關(guān)系,比如教授有生日,教授教python課程
class BirthDate:def __init__(self,year,month,day):self.year=yearself.month=monthself.day=dayclass Couse:def __init__(self,name,price,period):self.name=nameself.price=priceself.period=periodclass Teacher:def __init__(self,name,gender):self.name=nameself.gender=genderdef teach(self):print('teaching') class Professor(Teacher):def __init__(self,name,gender,birth,course):Teacher.__init__(self,name,gender)self.birth=birthself.course=coursep1=Professor('egon','male',BirthDate('1995','1','27'),Couse('python','28000','4 months'))print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period) ''' 運(yùn)行結(jié)果: 1995 1 27 python 28000 4 months ''' view code當(dāng)類之間有顯著不同,并且較小的類是較大的類所需要的組件時(shí),用組合比較好
4.5 接口與歸一化設(shè)計(jì)
1.什么是接口
=================第一部分:Java 語(yǔ)言中的接口很好的展現(xiàn)了接口的含義: IAnimal.java /* * Java的Interface很好的體現(xiàn)了我們前面分析的接口的特征: * 1)是一組功能的集合,而不是一個(gè)功能 * 2)接口的功能用于交互,所有的功能都是public,即別的對(duì)象可操作 * 3)接口只定義函數(shù),但不涉及函數(shù)實(shí)現(xiàn) * 4)這些功能是相關(guān)的,都是動(dòng)物相關(guān)的功能,但光合作用就不適宜放到IAnimal里面了 */package com.oo.demo; public interface IAnimal {public void eat();public void run();public void sleep();public void speak(); }=================第二部分:Pig.java:豬”的類設(shè)計(jì),實(shí)現(xiàn)了IAnnimal接口 package com.oo.demo; public class Pig implements IAnimal{ //如下每個(gè)函數(shù)都需要詳細(xì)實(shí)現(xiàn)public void eat(){System.out.println("Pig like to eat grass");}public void run(){System.out.println("Pig run: front legs, back legs");}public void sleep(){System.out.println("Pig sleep 16 hours every day");}public void speak(){System.out.println("Pig can not speak"); } }=================第三部分:Person2.java /* *實(shí)現(xiàn)了IAnimal的“人”,有幾點(diǎn)說明一下: * 1)同樣都實(shí)現(xiàn)了IAnimal的接口,但“人”和“豬”的實(shí)現(xiàn)不一樣,為了避免太多代碼導(dǎo)致影響閱讀,這里的代碼簡(jiǎn)化成一行,但輸出的內(nèi)容不一樣,實(shí)際項(xiàng)目中同一接口的同一功能點(diǎn),不同的類實(shí)現(xiàn)完全不一樣 * 2)這里同樣是“人”這個(gè)類,但和前面介紹類時(shí)給的類“Person”完全不一樣,這是因?yàn)橥瑯拥倪壿嫺拍?在不同的應(yīng)用場(chǎng)景下,具備的屬性和功能是完全不一樣的 */package com.oo.demo; public class Person2 implements IAnimal {public void eat(){System.out.println("Person like to eat meat");}public void run(){System.out.println("Person run: left leg, right leg");}public void sleep(){System.out.println("Person sleep 8 hours every dat");}public void speak(){System.out.println("Hellow world, I am a person");} }=================第四部分:Tester03.java package com.oo.demo;public class Tester03 {public static void main(String[] args) {System.out.println("===This is a person===");IAnimal person = new Person2();person.eat();person.run();person.sleep();person.speak();System.out.println("\n===This is a pig===");IAnimal pig = new Pig();pig.eat();pig.run();pig.sleep();pig.speak();} } view code繼承有兩種用途:
一:繼承基類的方法,并且做出自己的改變或者擴(kuò)展(代碼重用)
二:聲明某個(gè)子類兼容于某基類,定義一個(gè)接口類Interface,接口類中定義了一些接口名(就是函數(shù)名)且并未實(shí)現(xiàn)接口的功能,子類繼承接口類,并且實(shí)現(xiàn)接口中的功能
class Interface:#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關(guān)鍵字來定義一個(gè)接口。def read(self): #定接口函數(shù)readpassdef write(self): #定義接口函數(shù)writepassclass Txt(Interface): #文本,具體實(shí)現(xiàn)read和writedef read(self):print('文本數(shù)據(jù)的讀取方法')def write(self):print('文本數(shù)據(jù)的讀取方法')class Sata(Interface): #磁盤,具體實(shí)現(xiàn)read和writedef read(self):print('硬盤數(shù)據(jù)的讀取方法')def write(self):print('硬盤數(shù)據(jù)的讀取方法')class Process(All_file):def read(self):print('進(jìn)程數(shù)據(jù)的讀取方法')def write(self):print('進(jìn)程數(shù)據(jù)的讀取方法') view code實(shí)踐中,繼承的第一種含義意義并不很大,甚至常常是有害的。因?yàn)樗沟米宇惻c基類出現(xiàn)強(qiáng)耦合。
繼承的第二種含義非常重要。它又叫“接口繼承”。
接口繼承實(shí)質(zhì)上是要求“做出一個(gè)良好的抽象,這個(gè)抽象規(guī)定了一個(gè)兼容接口,使得外部調(diào)用者無(wú)需關(guān)心具體細(xì)節(jié),可一視同仁的處理實(shí)現(xiàn)了特定接口的所有對(duì)象”——這在程序設(shè)計(jì)上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區(qū)分的處理所有接口兼容的對(duì)象集合——就好象linux的泛文件概念一樣,所有東西都可以當(dāng)文件處理,不必關(guān)心它是內(nèi)存、磁盤、網(wǎng)絡(luò)還是屏幕(當(dāng)然,對(duì)底層設(shè)計(jì)者,當(dāng)然也可以區(qū)分出“字符設(shè)備”和“塊設(shè)備”,然后做出針對(duì)性的設(shè)計(jì):細(xì)致到什么程度,視需求而定)。
在python中根本就沒有一個(gè)叫做interface的關(guān)鍵字,上面的代碼只是看起來像接口,其實(shí)并沒有起到接口的作用,子類完全可以不用去實(shí)現(xiàn)接口 ,如果非要去模仿接口的概念,可以借助第三方模塊:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py里使用zope.interface
文檔https://zopeinterface.readthedocs.io/en/latest/
2. 為何要用接口
接口提取了一群類共同的函數(shù),可以把接口當(dāng)做一個(gè)函數(shù)的集合。
然后讓子類去實(shí)現(xiàn)接口中的函數(shù)。
這么做的意義在于歸一化,什么叫歸一化,就是只要是基于同一個(gè)接口實(shí)現(xiàn)的類,那么所有的這些類產(chǎn)生的對(duì)象在使用時(shí),從用法上來說都一樣。
歸一化,讓使用者無(wú)需關(guān)心對(duì)象的類是什么,只需要的知道這些對(duì)象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
比如:我們定義一個(gè)動(dòng)物接口,接口里定義了有跑、吃、呼吸等接口函數(shù),這樣老鼠的類去實(shí)現(xiàn)了該接口,松鼠的類也去實(shí)現(xiàn)了該接口,由二者分別產(chǎn)生一只老鼠和一只松鼠送到你面前,即便是你分別不到底哪只是什么鼠你肯定知道他倆都會(huì)跑,都會(huì)吃,都能呼吸。
再比如:我們有一個(gè)汽車接口,里面定義了汽車所有的功能,然后由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實(shí)現(xiàn)了汽車接口,這樣就好辦了,大家只需要學(xué)會(huì)了怎么開汽車,那么無(wú)論是本田,還是奧迪,還是大眾我們都會(huì)開了,開的時(shí)候根本無(wú)需關(guān)心我開的是哪一類車,操作手法(函數(shù)調(diào)用)都一樣
4.6 抽象類
1 什么是抽象類
??? 與java一樣,python也有抽象類的概念但是同樣需要借助模塊實(shí)現(xiàn),抽象類是一個(gè)特殊的類,它的特殊之處在于只能被繼承,不能被實(shí)例化
2 為什么要有抽象類
??? 如果說類是從一堆對(duì)象中抽取相同的內(nèi)容而來的,那么抽象類就是從一堆類中抽取相同的內(nèi)容而來的,內(nèi)容包括數(shù)據(jù)屬性和函數(shù)屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內(nèi)容就是水果這個(gè)抽象的類,你吃水果時(shí),要么是吃一個(gè)具體的香蕉,要么是吃一個(gè)具體的桃子。。。。。。你永遠(yuǎn)無(wú)法吃到一個(gè)叫做水果的東西。
??? 從設(shè)計(jì)角度去看,如果類是從現(xiàn)實(shí)對(duì)象抽象而來的,那么抽象類就是基于類抽象而來的。
從實(shí)現(xiàn)角度來看,抽象類與普通類的不同之處在于:抽象類中只能有抽象方法(沒有實(shí)現(xiàn)功能),該類不能被實(shí)例化,只能被繼承,且子類必須實(shí)現(xiàn)抽象方法。這一點(diǎn)與接口有點(diǎn)類似,但其實(shí)是不同的,即將揭曉答案
3. 在python中實(shí)現(xiàn)抽象類
#_*_coding:utf-8_*_ #一切皆文件 import abc #利用abc模塊實(shí)現(xiàn)抽象類class All_file(metaclass=abc.ABCMeta):all_type='file'@abc.abstractmethod #定義抽象方法,無(wú)需實(shí)現(xiàn)功能def read(self):'子類必須定義讀功能'pass@abc.abstractmethod #定義抽象方法,無(wú)需實(shí)現(xiàn)功能def write(self):'子類必須定義寫功能'pass# class Txt(All_file): # pass # # t1=Txt() #報(bào)錯(cuò),子類沒有定義抽象方法class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法def read(self):print('文本數(shù)據(jù)的讀取方法')def write(self):print('文本數(shù)據(jù)的讀取方法')class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法def read(self):print('硬盤數(shù)據(jù)的讀取方法')def write(self):print('硬盤數(shù)據(jù)的讀取方法')class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法def read(self):print('進(jìn)程數(shù)據(jù)的讀取方法')def write(self):print('進(jìn)程數(shù)據(jù)的讀取方法')wenbenwenjian=Txt()yingpanwenjian=Sata()jinchengwenjian=Process()#這樣大家都是被歸一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read()print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type) view code4. 抽象類與接口
抽象類的本質(zhì)還是類,指的是一組類的相似性,包括數(shù)據(jù)屬性(如all_type)和函數(shù)屬性(如read、write),而接口只強(qiáng)調(diào)函數(shù)屬性的相似性。
抽象類是一個(gè)介于類和接口直接的一個(gè)概念,同時(shí)具備類和接口的部分特性,可以用來實(shí)現(xiàn)歸一化設(shè)計(jì)
4.7 繼承實(shí)現(xiàn)的原理(繼承順序)
1 繼承順序
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__) #只有新式才有這個(gè)屬性可以查看線性列表,經(jīng)典類沒有這個(gè)屬性#新式類繼承順序:F->D->B->E->C->A #經(jīng)典類繼承順序:F->D->B->A->E->C #python3中統(tǒng)一都是新式類 #pyhon2中才分新式類與經(jīng)典類 view code2 繼承原理(python如何實(shí)現(xiàn)的繼承)
python到底是如何實(shí)現(xiàn)繼承的,對(duì)于你定義的每一個(gè)類,python會(huì)計(jì)算出一個(gè)方法解析順序(MRO)列表,這個(gè)MRO列表就是一個(gè)簡(jiǎn)單的所有基類的線性順序列表,例如
>>> F.mro() #等同于F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] view code為了實(shí)現(xiàn)繼承,python會(huì)在MRO列表上從左到右開始查找基類,直到找到第一個(gè)匹配這個(gè)屬性的類為止。
而這個(gè)MRO列表的構(gòu)造是通過一個(gè)C3線性化算法來實(shí)現(xiàn)的。我們不去深究這個(gè)算法的數(shù)學(xué)原理,它實(shí)際上就是合并所有父類的MRO列表并遵循如下三條準(zhǔn)則:
1.子類會(huì)先于父類被檢查
2.多個(gè)父類會(huì)根據(jù)它們?cè)诹斜碇械捻樞虮粰z查
3.如果對(duì)下一個(gè)類存在兩個(gè)合法的選擇,選擇第一個(gè)父類
4.8 子類中調(diào)用父類方法
子類繼承了父類的方法,然后想進(jìn)行修改,注意了是基于原有的基礎(chǔ)上修改,那么就需要在子類中調(diào)用父類的方法
方法一:父類名.父類方法()
#_*_coding:utf-8_*_ class Vehicle: #定義交通工具類Country='China'def __init__(self,name,speed,load,power):self.name=nameself.speed=speedself.load=loadself.power=powerdef run(self):print('開動(dòng)啦...')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號(hào)線歡迎您' %self.line)Vehicle.run(self)line13=Subway('中國(guó)地鐵','180m/s','1000人/箱','電',13) line13.run() view code方法二:super()
class Vehicle: #定義交通工具類Country='China'def __init__(self,name,speed,load,power):self.name=nameself.speed=speedself.load=loadself.power=powerdef run(self):print('開動(dòng)啦...')class Subway(Vehicle): #地鐵def __init__(self,name,speed,load,power,line):#super(Subway,self) 就相當(dāng)于實(shí)例本身 在python3中super()等同于super(Subway,self)super().__init__(name,speed,load,power)self.line=linedef run(self):print('地鐵%s號(hào)線歡迎您' %self.line)super(Subway,self).run()class Mobike(Vehicle):#摩拜單車passline13=Subway('中國(guó)地鐵','180m/s','1000人/箱','電',13) line13.run() view code不用super引發(fā)的慘案
#每個(gè)類中都繼承了且重寫了父類的方法 class A:def __init__(self):print('A的構(gòu)造方法') class B(A):def __init__(self):print('B的構(gòu)造方法')A.__init__(self)class C(A):def __init__(self):print('C的構(gòu)造方法')A.__init__(self)class D(B,C):def __init__(self):print('D的構(gòu)造方法')B.__init__(self)C.__init__(self)pass f1=D()print(D.__mro__) #python2中沒有這個(gè)屬性 view code當(dāng)你使用super()函數(shù)時(shí),Python會(huì)在MRO列表上繼續(xù)搜索下一個(gè)類。只要每個(gè)重定義的方法統(tǒng)一使用super()并只調(diào)用它一次,那么控制流最終會(huì)遍歷完整個(gè)MRO列表,每個(gè)方法也只會(huì)被調(diào)用一次
(注意注意注意:使用super調(diào)用的所有屬性,都是從MRO列表當(dāng)前的位置往后找,千萬(wàn)不要通過看代碼去找繼承關(guān)系,一定要看MRO列表)
#每個(gè)類中都繼承了且重寫了父類的方法 class A:def __init__(self):print('A的構(gòu)造方法') class B(A):def __init__(self):print('B的構(gòu)造方法')super(B,self).__init__()class C(A):def __init__(self):print('C的構(gòu)造方法')super(C,self).__init__()class D(B,C):def __init__(self):print('D的構(gòu)造方法')super(D,self).__init__()f1=D()print(D.__mro__) #python2中沒有這個(gè)屬性 view code五 多態(tài)與多態(tài)性
5.1 多態(tài)
多態(tài)指的是一類事物有多種形態(tài),(一個(gè)抽象類有多個(gè)子類,因而多態(tài)的概念依賴于繼承)
1. 序列類型有多種形態(tài):字符串,列表,元組。
2. 動(dòng)物有多種形態(tài):人,狗,豬
import abc class Animal(metaclass=abc.ABCMeta): #同一類事物:動(dòng)物@abc.abstractmethoddef talk(self):passclass People(Animal): #動(dòng)物的形態(tài)之一:人def talk(self):print('say hello')class Dog(Animal): #動(dòng)物的形態(tài)之二:狗def talk(self):print('say wangwang')class Pig(Animal): #動(dòng)物的形態(tài)之三:豬def talk(self):print('say aoao') view code3. 文件有多種形態(tài):文件文件,可執(zhí)行文件
import abc class File(metaclass=abc.ABCMeta): #同一類事物:文件@abc.abstractmethoddef click(self):passclass Text(File): #文件的形態(tài)之一:文本文件def click(self):print('open file')class ExeFile(File): #文件的形態(tài)之二:可執(zhí)行文件def click(self):print('execute file') view code5.2 多態(tài)性
一 什么是多態(tài)性(請(qǐng)務(wù)必注意注意注意:多態(tài)與多態(tài)性是兩種概念。)
多態(tài)性是指具有不同功能的函數(shù)可以使用相同的函數(shù)名,這樣就可以用一個(gè)函數(shù)名調(diào)用不同內(nèi)容的函數(shù)。
在面向?qū)ο蠓椒ㄖ幸话闶沁@樣表述多態(tài)性:向不同的對(duì)象發(fā)送同一條消息,不同的對(duì)象在接收時(shí)會(huì)產(chǎn)生不同的行為(即方法)。也就是說,每個(gè)對(duì)象可以用自己的方式去響應(yīng)共同的消息。所謂消息,就是調(diào)用函數(shù),不同的行為就是指不同的實(shí)現(xiàn),即執(zhí)行不同的函數(shù)。
多態(tài)性分為靜態(tài)多態(tài)性和動(dòng)態(tài)多態(tài)性
靜態(tài)多態(tài)性:如任何類型都可以用運(yùn)算符+進(jìn)行運(yùn)算
動(dòng)態(tài)多態(tài)性:如下
1.?
2.
>>> def func(animal): #參數(shù)animal就是對(duì)態(tài)性的體現(xiàn) ... animal.talk() ... >>> people1=People() #產(chǎn)生一個(gè)人的對(duì)象 >>> pig1=Pig() #產(chǎn)生一個(gè)豬的對(duì)象 >>> dog1=Dog() #產(chǎn)生一個(gè)狗的對(duì)象 >>> func(people1) say hello >>> func(pig1) say aoao >>> func(dog1) say wangwang view code3.?
>>> def func(f): ... f.click() ... >>> t1=Text() >>> e1=ExeFile() >>> func(t1) open file >>> func(e1) execute file view code綜上我們也可以說,多態(tài)性是‘一個(gè)接口(函數(shù)func),多種實(shí)現(xiàn)(如f.click())’
二 為什么要用多態(tài)性(多態(tài)性的好處)
其實(shí)大家從上面多態(tài)性的例子可以看出,我們并沒有增加什么新的知識(shí),也就是說python本身就是支持多態(tài)性的,這么做的好處是什么呢?
1.增加了程序的靈活性
以不變應(yīng)萬(wàn)變,不論對(duì)象千變?nèi)f化,使用者都是同一種形式去調(diào)用,如func(animal)
2.增加了程序額可擴(kuò)展性
通過繼承animal類創(chuàng)建了一個(gè)新的類,使用者無(wú)需更改自己的代碼,還是用func(animal)去調(diào)用?
>>> class Cat(Animal): #屬于動(dòng)物的另外一種形態(tài):貓 ... def talk(self): ... print('say miao') ... >>> def func(animal): #對(duì)于使用者來說,自己的代碼根本無(wú)需改動(dòng) ... animal.talk() ... >>> cat1=Cat() #實(shí)例出一只貓 >>> func(cat1) #甚至連調(diào)用方式也無(wú)需改變,就能調(diào)用貓的talk功能 say miao''' 這樣我們新增了一個(gè)形態(tài)Cat,由Cat類產(chǎn)生的實(shí)例cat1,使用者可以在完全不需要修改自己代碼的情況下。使用和人、狗、豬一樣的方式調(diào)用cat1的talk方法,即func(cat1) ''' view code六 封裝
從封裝本身的意思去理解,封裝就好像是拿來一個(gè)麻袋,把小貓,小狗,小王八,還有alex一起裝進(jìn)麻袋,然后把麻袋封上口子。但其實(shí)這種理解相當(dāng)片面
??? 首先我們要了解
6.1 要封裝什么
你錢包的有多少錢(數(shù)據(jù)的封裝)
你的性取向(數(shù)據(jù)的封裝)
你撒尿的具體功能是怎么實(shí)現(xiàn)的(方法的封裝)
6.2 為什么要封裝
封裝數(shù)據(jù)的主要原因是:保護(hù)隱私(作為男人的你,臉上就寫著:我喜歡男人,你害怕么?)
封裝方法的主要原因是:隔離復(fù)雜度(快門就是傻瓜相機(jī)為傻瓜們提供的方法,該方法將內(nèi)部復(fù)雜的照相功能都隱藏起來了,比如你不必知道你自己的尿是怎么流出來的,你直接掏出自己的接口就能用尿這個(gè)功能)
你的身體沒有一處不體現(xiàn)著封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然后為你提供一個(gè)尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時(shí)候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。還有你的頭把你的腦子封裝到了腦殼里,然后提供了眼睛這個(gè)接口....
提示:在編程語(yǔ)言里,對(duì)外提供的接口,就是函數(shù),稱為接口函數(shù),這與接口的概念還不一樣,接口代表一組接口函數(shù)的集合體,后續(xù)我們將介紹到。
6.3 封裝分為兩個(gè)層面
封裝其實(shí)分為兩個(gè)層面,但很多資料中都混在一起去說,很容易讓初學(xué)者懵逼:
第一個(gè)層面的封裝(什么都不用做):創(chuàng)建類和對(duì)象會(huì)分別創(chuàng)建二者的名稱空間,我們只能用類名.或者obj.的方式去訪問里面的名字,這本身就是一種封裝
>>> r1.nickname '草叢倫' >>> Riven.camp 'Noxus'注意:對(duì)于這一層面的封裝(隱藏),類名.和實(shí)例名.就是訪問隱藏屬性的接口(或者叫入口)
第二個(gè)層面的封裝:類中把某些屬性和方法隱藏起來(或者說定義成私有的),只在類的內(nèi)部使用、外部無(wú)法訪問,或者留下少量接口(函數(shù))供外部訪問。
在python中用雙下劃線的方式實(shí)現(xiàn)隱藏屬性(設(shè)置成私有的)
類中所有雙下劃線開頭的名稱如__x都會(huì)自動(dòng)變形成:_類名__x的形式:
class A:__N=0 #類的數(shù)據(jù)屬性就應(yīng)該是共享的,但是語(yǔ)法上是可以把類的數(shù)據(jù)屬性設(shè)置成私有的如__N,會(huì)變形為_A__Ndef __init__(self):self.__X=10 #變形為self._A__Xdef __foo(self): #變形為_A__fooprint('from A')def bar(self):self.__foo() #只有在類內(nèi)部才可以通過__foo的形式訪問到.這種自動(dòng)變形的特點(diǎn):
1.類中定義的__x只能在內(nèi)部使用,如self.__x,引用的就是變形的結(jié)果。
2.這種變形其實(shí)正是針對(duì)外部的變形,在外部是無(wú)法通過__x這個(gè)名字訪問到的。
2.在子類定義的__x不會(huì)覆蓋在父類定義的__x,因?yàn)樽宇愔凶冃纬闪?#xff1a;_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時(shí),子類是無(wú)法覆蓋的。
注意:對(duì)于這一層面的封裝(隱藏),我們需要在類中定義一個(gè)函數(shù)(接口函數(shù))在它內(nèi)部訪問被隱藏的屬性,然后外部就可以使用了
也可以用6.4所講的特性property來解決,即將介紹
? 這種變形需要注意的問題是:
1.這種機(jī)制也并沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然后就可以訪問了,如a._A__N
>>> a=A() >>> a._A__N 0 >>> a._A__X 10 >>> A._A__N 02.變形的過程只在類的定義是發(fā)生一次,在定義后的賦值操作,不會(huì)變形
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): #在定義時(shí)就變形為_A__fa ... print('from A') ... def test(self): ... self.__fa() #只會(huì)與自己所在的類為準(zhǔn),即調(diào)用_A__fa ... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test() from Apython并不會(huì)真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那么from module import *時(shí)不能被導(dǎo)入,但是你from module import _private_module依然是可以導(dǎo)入的
其實(shí)很多時(shí)候你去調(diào)用一個(gè)模塊的功能時(shí)會(huì)遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內(nèi)部調(diào)用的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻逼一點(diǎn)點(diǎn)
6.4 特性(property)
1 什么是特性property
property是一種特殊的屬性,訪問它時(shí)會(huì)執(zhí)行一段功能(函數(shù))然后返回值
import math class Circle:def __init__(self,radius): #圓的半徑radiusself.radius=radius@propertydef area(self):return math.pi * self.radius**2 #計(jì)算面積@propertydef perimeter(self):return 2*math.pi*self.radius #計(jì)算周長(zhǎng)c=Circle(10) print(c.radius) print(c.area) #可以向訪問數(shù)據(jù)屬性一樣去訪問area,會(huì)觸發(fā)一個(gè)函數(shù)的執(zhí)行,動(dòng)態(tài)計(jì)算出一個(gè)值 print(c.perimeter) #同上 ''' 輸出結(jié)果: 10 314.1592653589793 62.83185307179586 '''注意:此時(shí)的特性arear和perimeter不能被賦值
c.area=3 #為特性area賦值 ''' 拋出異常: AttributeError: can't set attribute '''2 為什么要用property
將一個(gè)類的函數(shù)定義成特性以后,對(duì)象再去使用的時(shí)候obj.name,根本無(wú)法察覺自己的name是執(zhí)行了一個(gè)函數(shù)然后計(jì)算出來的,這種特性的使用方式遵循了統(tǒng)一訪問的原則
除此之外,看下
ps:面向?qū)ο蟮姆庋b有三種方式: 【public】 這種其實(shí)就是不封裝,是對(duì)外公開的 【protected】 這種封裝方式對(duì)外不公開,但對(duì)朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什么大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開 【private】 這種封裝對(duì)誰(shuí)都不公開python并沒有在語(yǔ)法上把它們?nèi)齻€(gè)內(nèi)建到自己的class機(jī)制中,在C++里一般會(huì)將所有的所有的數(shù)據(jù)都設(shè)置為私有的,然后提供set和get方法(接口)去設(shè)置和獲取,在python中通過property方法可以實(shí)現(xiàn)
class Foo:def __init__(self,val):self.__NAME=val #將所有的數(shù)據(jù)屬性都隱藏起來@propertydef name(self):return self.__NAME #obj.name訪問的是self.__NAME(這也是真實(shí)值的存放位置)@name.setterdef name(self,value):if not isinstance(value,str): #在設(shè)定值之前進(jìn)行類型檢查raise TypeError('%s must be str' %value)self.__NAME=value #通過類型檢查后,將值value存放到真實(shí)的位置self.__NAME@name.deleterdef name(self):raise TypeError('Can not delete')f=Foo('egon') print(f.name) # f.name=10 #拋出異常'TypeError: 10 must be str' del f.name #拋出異常'TypeError: Can not delete view code class Foo:def __init__(self,val):self.__NAME=val #將所有的數(shù)據(jù)屬性都隱藏起來def getname(self):return self.__NAME #obj.name訪問的是self.__NAME(這也是真實(shí)值的存放位置)def setname(self,value):if not isinstance(value,str): #在設(shè)定值之前進(jìn)行類型檢查raise TypeError('%s must be str' %value)self.__NAME=value #通過類型檢查后,將值value存放到真實(shí)的位置self.__NAMEdef delname(self):raise TypeError('Can not delete')name=property(getname,setname,delname) #不如裝飾器的方式清晰 view code6.5 封裝與擴(kuò)展性
封裝在于明確區(qū)分內(nèi)外,使得類實(shí)現(xiàn)者可以修改封裝內(nèi)的東西而不影響外部調(diào)用者的代碼;而外部使用用者只知道一個(gè)接口(函數(shù)),只要接口(函數(shù))名、參數(shù)不變,使用者的代碼永遠(yuǎn)無(wú)需改變。這就提供一個(gè)良好的合作基礎(chǔ)——或者說,只要接口這個(gè)基礎(chǔ)約定不變,則代碼改變不足為慮。
#類的設(shè)計(jì)者 class Room:def __init__(self,name,owner,width,length,high):self.name=nameself.owner=ownerself.__width=widthself.__length=lengthself.__high=highdef tell_area(self): #對(duì)外提供的接口,隱藏了內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),此時(shí)我們想求的是面積return self.__width * self.__length view code #使用者 >>> r1=Room('臥室','egon',20,20,20) >>> r1.tell_area() #使用者調(diào)用接口tell_area 400 view code #類的設(shè)計(jì)者,輕松的擴(kuò)展了功能,而類的使用者完全不需要改變自己的代碼 class Room:def __init__(self,name,owner,width,length,high):self.name=nameself.owner=ownerself.__width=widthself.__length=lengthself.__high=highdef tell_area(self): #對(duì)外提供的接口,隱藏內(nèi)部實(shí)現(xiàn),此時(shí)我們想求的是體積,內(nèi)部邏輯變了,只需求修該下列一行就可以很簡(jiǎn)答的實(shí)現(xiàn),而且外部調(diào)用感知不到,仍然使用該方法,但是功能已經(jīng)變了return self.__width * self.__length * self.__high view code對(duì)于仍然在使用tell_area接口的人來說,根本無(wú)需改動(dòng)自己的代碼,就可以用上新功能
>>> r1.tell_area() 8000七 靜態(tài)方法和類方法
通常情況下,在類中定義的所有函數(shù)(注意了,這里說的就是所有,跟self啥的沒關(guān)系,self也只是一個(gè)再普通不過的參數(shù)而已)都是對(duì)象的綁定方法,對(duì)象在調(diào)用綁定方法時(shí)會(huì)自動(dòng)將自己作為參數(shù)傳遞給方法的第一個(gè)參數(shù)。除此之外還有兩種常見的方法:靜態(tài)方法和類方法,二者是為類量身定制的,但是實(shí)例非要使用,也不會(huì)報(bào)錯(cuò),后續(xù)將介紹。
1 靜態(tài)方法
是一種普通函數(shù),位于類定義的命名空間中,不會(huì)對(duì)任何實(shí)例類型進(jìn)行操作,python為我們內(nèi)置了函數(shù)staticmethod來把類中的函數(shù)定義成靜態(tài)方法
class Foo:def spam(x,y,z): #類中的一個(gè)函數(shù),千萬(wàn)不要懵逼,self和x啥的沒有不同都是參數(shù)名print(x,y,z)spam=staticmethod(spam) #把spam函數(shù)做成靜態(tài)方法基于之前所學(xué)裝飾器的知識(shí),@staticmethod 等同于spam=staticmethod(spam),于是
class Foo:@staticmethod #裝飾器def spam(x,y,z):print(x,y,z)使用演示
print(type(Foo.spam)) #類型本質(zhì)就是函數(shù) Foo.spam(1,2,3) #調(diào)用函數(shù)應(yīng)該有幾個(gè)參數(shù)就傳幾個(gè)參數(shù)f1=Foo() f1.spam(3,3,3) #實(shí)例也可以使用,但通常靜態(tài)方法都是給類用的,實(shí)例在使用時(shí)喪失了自動(dòng)傳值的機(jī)制''' <class 'function'> 1 2 3 3 3 3 '''應(yīng)用場(chǎng)景:編寫類時(shí)需要采用很多不同的方式來創(chuàng)建實(shí)例,而我們只有一個(gè)__init__函數(shù),此時(shí)靜態(tài)方法就派上用場(chǎng)了
class Date:def __init__(self,year,month,day):self.year=yearself.month=monthself.day=day@staticmethoddef now(): #用Date.now()的形式去產(chǎn)生實(shí)例,該實(shí)例用的是當(dāng)前時(shí)間t=time.localtime() #獲取結(jié)構(gòu)化的時(shí)間格式return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建實(shí)例并且返回@staticmethoddef tomorrow():#用Date.tomorrow()的形式去產(chǎn)生實(shí)例,該實(shí)例用的是明天的時(shí)間t=time.localtime(time.time()+86400)return Date(t.tm_year,t.tm_mon,t.tm_mday)a=Date('1987',11,27) #自己定義時(shí)間 b=Date.now() #采用當(dāng)前時(shí)間 c=Date.tomorrow() #采用明天的時(shí)間print(a.year,a.month,a.day) print(b.year,b.month,b.day) print(c.year,c.month,c.day)2 類方法
類方法是給類用的,類在使用時(shí)會(huì)將類本身當(dāng)做參數(shù)傳給類方法的第一個(gè)參數(shù),python為我們內(nèi)置了函數(shù)classmethod來把類中的函數(shù)定義成類方法
class A:x=1@classmethoddef test(cls):print(cls,cls.x)class B(A):x=2 B.test()''' 輸出結(jié)果: <class '__main__.B'> 2 '''應(yīng)用場(chǎng)景:
import time class Date:def __init__(self,year,month,day):self.year=yearself.month=monthself.day=day@staticmethoddef now():t=time.localtime()return Date(t.tm_year,t.tm_mon,t.tm_mday)class EuroDate(Date):def __str__(self):return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)e=EuroDate.now() print(e) #我們的意圖是想觸發(fā)EuroDate.__str__,但是結(jié)果為 ''' 輸出結(jié)果: <__main__.Date object at 0x1013f9d68> '''因?yàn)閑就是用Date類產(chǎn)生的,所以根本不會(huì)觸發(fā)EuroDate.__str__,解決方法就是用classmethod
import time class Date:def __init__(self,year,month,day):self.year=yearself.month=monthself.day=day# @staticmethod# def now():# t=time.localtime()# return Date(t.tm_year,t.tm_mon,t.tm_mday)@classmethod #改成類方法def now(cls):t=time.localtime()return cls(t.tm_year,t.tm_mon,t.tm_mday) #哪個(gè)類來調(diào)用,即用哪個(gè)類cls來實(shí)例化class EuroDate(Date):def __str__(self):return 'year:%s month:%s day:%s' %(self.year,self.month,self.day)e=EuroDate.now() print(e) #我們的意圖是想觸發(fā)EuroDate.__str__,此時(shí)e就是由EuroDate產(chǎn)生的,所以會(huì)如我們所愿 ''' 輸出結(jié)果: year:2017 month:3 day:3 '''強(qiáng)調(diào),注意注意注意:靜態(tài)方法和類方法雖然是給類準(zhǔn)備的,但是如果實(shí)例去用,也是可以用的,只不過實(shí)例去調(diào)用的時(shí)候容易讓人混淆,不知道你要干啥
x=e.now() #通過實(shí)例e去調(diào)用類方法也一樣可以使用,靜態(tài)方法也一樣 print(x) ''' 輸出結(jié)果: year:2017 month:3 day:3 '''八 面向?qū)ο蟮能浖_發(fā)
很多人在學(xué)完了python的class機(jī)制之后,遇到一個(gè)生產(chǎn)中的問題,還是會(huì)懵逼,這其實(shí)太正常了,因?yàn)槿魏纬绦虻拈_發(fā)都是先設(shè)計(jì)后編程,python的class機(jī)制只不過是一種編程方式,如果你硬要拿著class去和你的問題死磕,變得更加懵逼都是分分鐘的事,在以前,軟件的開發(fā)相對(duì)簡(jiǎn)單,從任務(wù)的分析到編寫程序,再到程序的調(diào)試,可以由一個(gè)人或一個(gè)小組去完成。但是隨著軟件規(guī)模的迅速增大,軟件任意面臨的問題十分復(fù)雜,需要考慮的因素太多,在一個(gè)軟件中所產(chǎn)生的錯(cuò)誤和隱藏的錯(cuò)誤、未知的錯(cuò)誤可能達(dá)到驚人的程度,這也不是在設(shè)計(jì)階段就完全解決的。
??? 所以軟件的開發(fā)其實(shí)一整套規(guī)范,我們所學(xué)的只是其中的一小部分,一個(gè)完整的開發(fā)過程,需要明確每個(gè)階段的任務(wù),在保證一個(gè)階段正確的前提下再進(jìn)行下一個(gè)階段的工作,稱之為軟件工程
??? 面向?qū)ο蟮能浖こ贪ㄏ旅鎺讉€(gè)部:
1.面向?qū)ο蠓治?#xff08;object oriented analysis ,OOA)
??? 軟件工程中的系統(tǒng)分析階段,要求分析員和用戶結(jié)合在一起,對(duì)用戶的需求做出精確的分析和明確的表述,從大的方面解析軟件系統(tǒng)應(yīng)該做什么,而不是怎么去做。面向?qū)ο蟮姆治鲆凑彰嫦驅(qū)ο蟮母拍詈头椒?#xff0c;在對(duì)任務(wù)的分析中,從客觀存在的事物和事物之間的關(guān)系,貴南出有關(guān)的對(duì)象(對(duì)象的‘特征’和‘技能’)以及對(duì)象之間的聯(lián)系,并將具有相同屬性和行為的對(duì)象用一個(gè)類class來標(biāo)識(shí)。
??? 建立一個(gè)能反映這是工作情況的需求模型,此時(shí)的模型是粗略的。
2 面向?qū)ο笤O(shè)計(jì)(object oriented design,OOD)
??? 根據(jù)面向?qū)ο蠓治鲭A段形成的需求模型,對(duì)每一部分分別進(jìn)行具體的設(shè)計(jì)。
??? 首先是類的設(shè)計(jì),類的設(shè)計(jì)可能包含多個(gè)層次(利用繼承與派生機(jī)制)。然后以這些類為基礎(chǔ)提出程序設(shè)計(jì)的思路和方法,包括對(duì)算法的設(shè)計(jì)。
??? 在設(shè)計(jì)階段并不牽涉任何一門具體的計(jì)算機(jī)語(yǔ)言,而是用一種更通用的描述工具(如偽代碼或流程圖)來描述
3 面向?qū)ο缶幊?#xff08;object oriented programming,OOP)
??? 根據(jù)面向?qū)ο笤O(shè)計(jì)的結(jié)果,選擇一種計(jì)算機(jī)語(yǔ)言把它寫成程序,可以是python
4 面向?qū)ο鬁y(cè)試(object oriented test,OOT)
??? 在寫好程序后交給用戶使用前,必須對(duì)程序進(jìn)行嚴(yán)格的測(cè)試,測(cè)試的目的是發(fā)現(xiàn)程序中的錯(cuò)誤并修正它。
??? 面向?qū)Φ臏y(cè)試是用面向?qū)ο蟮姆椒ㄟM(jìn)行測(cè)試,以類作為測(cè)試的基本單元。
5 面向?qū)ο缶S護(hù)(object oriendted soft maintenance,OOSM)
??? 正如對(duì)任何產(chǎn)品都需要進(jìn)行售后服務(wù)和維護(hù)一樣,軟件在使用時(shí)也會(huì)出現(xiàn)一些問題,或者軟件商想改進(jìn)軟件的性能,這就需要修改程序。
??? 由于使用了面向?qū)ο蟮姆椒ㄩ_發(fā)程序,使用程序的維護(hù)比較容易。
??? 因?yàn)閷?duì)象的封裝性,修改一個(gè)對(duì)象對(duì)其他的對(duì)象影響很小,利用面向?qū)ο蟮姆椒ňS護(hù)程序,大大提高了軟件維護(hù)的效率,可擴(kuò)展性高。
??? 在面向?qū)ο蠓椒ㄖ?#xff0c;最早發(fā)展的肯定是面向?qū)ο缶幊?OOP),那時(shí)OOA和OOD都還沒有發(fā)展起來,因此程序設(shè)計(jì)者為了寫出面向?qū)ο蟮某绦?#xff0c;還必須深入到分析和設(shè)計(jì)領(lǐng)域,尤其是設(shè)計(jì)領(lǐng)域,那時(shí)的OOP實(shí)際上包含了現(xiàn)在的OOD和OOP兩個(gè)階段,這對(duì)程序設(shè)計(jì)者要求比較高,許多人感到很難掌握。
??? 現(xiàn)在設(shè)計(jì)一個(gè)大的軟件,是嚴(yán)格按照面向?qū)ο筌浖こ痰?個(gè)階段進(jìn)行的,這個(gè)5個(gè)階段的工作不是由一個(gè)人從頭到尾完成的,而是由不同的人分別完成,這樣OOP階段的任務(wù)就比較簡(jiǎn)單了。程序編寫者只需要根據(jù)OOd提出的思路,用面向?qū)ο笳Z(yǔ)言編寫出程序既可。
??? 在一個(gè)大型軟件開發(fā)過程中,OOP只是很小的一個(gè)部分。
??? 對(duì)于全棧開發(fā)的你來說,這五個(gè)階段都有了,對(duì)于簡(jiǎn)單的問題,不必嚴(yán)格按照這個(gè)5個(gè)階段進(jìn)行,往往由程序設(shè)計(jì)者按照面向?qū)ο蟮姆椒ㄟM(jìn)行程序設(shè)計(jì),包括類的設(shè)計(jì)和程序的設(shè)計(jì)
九 小白容易犯的錯(cuò)誤
1.面向?qū)ο蟮某绦蛟O(shè)計(jì)看起來高大上,所以我在編程時(shí)就應(yīng)該保證通篇class,這樣寫出的程序一定是好的程序(面向?qū)ο笾贿m合那些可擴(kuò)展性要求比較高的場(chǎng)景)
2.很多人喜歡說面向?qū)ο笕筇匦?#xff08;這是從哪傳出來的,封裝,多態(tài),繼承?漏洞太多太多,好吧暫且稱為三大特性),那么我在基于面向?qū)ο缶幊虝r(shí),我一定要讓我定義的類中完整的包含這三種特性,這樣寫肯定是好的程序
好家伙,我說降龍十八掌有十八掌,那么你每次跟人干仗都要從第一掌打到第18掌這才顯得你會(huì)了是么,我來一萬(wàn)個(gè)人你需要打10000*18掌對(duì)么,傻叉
3.類有類屬性,實(shí)例有實(shí)例屬性,所以我們?cè)诙xclass時(shí)一定要定義出那么幾個(gè)類屬性,想不到怎么辦,那就使勁的想,定義的越多越牛逼
這就犯了一個(gè)嚴(yán)重的錯(cuò)誤,程序越早面向?qū)ο?#xff0c;死的越早,為啥面向?qū)ο?#xff0c;因?yàn)槲覀円獙?shù)據(jù)與功能結(jié)合到一起,程序整體的結(jié)構(gòu)都沒有出來,或者說需要考慮的問題你都沒有搞清楚個(gè)八九不離十,你就開始面向?qū)ο罅?#xff0c;這就導(dǎo)致了,你在那里干想,自以為想通了,定義了一堆屬性,結(jié)果后來又都用不到,或者想不通到底應(yīng)該定義啥,那就一直想吧,想著想著就瘋了。
你見過哪家公司要開發(fā)一個(gè)軟件,上來就開始寫,肯定是頻繁的開會(huì)討論計(jì)劃,請(qǐng)看第八節(jié)
4.既然這么麻煩,那么我徹底解脫了,我們不要用面向?qū)ο缶幊塘?#xff0c;你啊,你有大才,你能成事啊,傻叉。
十 python中關(guān)于OOP的常用術(shù)語(yǔ)
抽象/實(shí)現(xiàn)
抽象指對(duì)現(xiàn)實(shí)世界問題和實(shí)體的本質(zhì)表現(xiàn),行為和特征建模,建立一個(gè)相關(guān)的子集,可以用于 繪程序結(jié)構(gòu),從而實(shí)現(xiàn)這種模型。抽象不僅包括這種模型的數(shù)據(jù)屬性,還定義了這些數(shù)據(jù)的接口。
對(duì)某種抽象的實(shí)現(xiàn)就是對(duì)此數(shù)據(jù)及與之相關(guān)接口的現(xiàn)實(shí)化(realization)。現(xiàn)實(shí)化這個(gè)過程對(duì)于客戶 程序應(yīng)當(dāng)是透明而且無(wú)關(guān)的。?
封裝/接口
封裝描述了對(duì)數(shù)據(jù)/信息進(jìn)行隱藏的觀念,它對(duì)數(shù)據(jù)屬性提供接口和訪問函數(shù)。通過任何客戶端直接對(duì)數(shù)據(jù)的訪問,無(wú)視接口,與封裝性都是背道而馳的,除非程序員允許這些操作。作為實(shí)現(xiàn)的 一部分,客戶端根本就不需要知道在封裝之后,數(shù)據(jù)屬性是如何組織的。在Python中,所有的類屬性都是公開的,但名字可能被“混淆”了,以阻止未經(jīng)授權(quán)的訪問,但僅此而已,再?zèng)]有其他預(yù)防措施了。這就需要在設(shè)計(jì)時(shí),對(duì)數(shù)據(jù)提供相應(yīng)的接口,以免客戶程序通過不規(guī)范的操作來存取封裝的數(shù)據(jù)屬性。
注意:封裝絕不是等于“把不想讓別人看到、以后可能修改的東西用private隱藏起來”
真正的封裝是,經(jīng)過深入的思考,做出良好的抽象,給出“完整且最小”的接口,并使得內(nèi)部細(xì)節(jié)可以對(duì)外透明
(注意:對(duì)外透明的意思是,外部調(diào)用者可以順利的得到自己想要的任何功能,完全意識(shí)不到內(nèi)部細(xì)節(jié)的存在)
合成
合成擴(kuò)充了對(duì)類的 述,使得多個(gè)不同的類合成為一個(gè)大的類,來解決現(xiàn)實(shí)問題。合成 述了 一個(gè)異常復(fù)雜的系統(tǒng),比如一個(gè)類由其它類組成,更小的組件也可能是其它的類,數(shù)據(jù)屬性及行為, 所有這些合在一起,彼此是“有一個(gè)”的關(guān)系。
派生/繼承/繼承結(jié)構(gòu)
派生描述了子類衍生出新的特性,新類保留已存類類型中所有需要的數(shù)據(jù)和行為,但允許修改或者其它的自定義操作,都不會(huì)修改原類的定義。
繼承描述了子類屬性從祖先類繼承這樣一種方式
繼承結(jié)構(gòu)表示多“代”派生,可以述成一個(gè)“族譜”,連續(xù)的子類,與祖先類都有關(guān)系。
泛化/特化
基于繼承
泛化表示所有子類與其父類及祖先類有一樣的特點(diǎn)。
特化描述所有子類的自定義,也就是,什么屬性讓它與其祖先類不同。
多態(tài)與多態(tài)性
多態(tài)指的是同一種事物的多種狀態(tài):水這種事物有多種不同的狀態(tài):冰,水蒸氣
多態(tài)性的概念指出了對(duì)象如何通過他們共同的屬性和動(dòng)作來操作及訪問,而不需考慮他們具體的類。
冰,水蒸氣,都繼承于水,它們都有一個(gè)同名的方法就是變成云,但是冰.變?cè)?),與水蒸氣.變?cè)?)是截然不同的過程,雖然調(diào)用的方法都一樣
自省/反射
自省也稱作反射,這個(gè)性質(zhì)展示了某對(duì)象是如何在運(yùn)行期取得自身信息的。如果傳一個(gè)對(duì)象給你,你可以查出它有什么能力,這是一項(xiàng)強(qiáng)大的特性。如果Python不支持某種形式的自省功能,dir和type內(nèi)建函數(shù),將很難正常工作。還有那些特殊屬性,像__dict__,__name__及__doc__
轉(zhuǎn)載于:https://www.cnblogs.com/can-H/articles/6506475.html
總結(jié)
以上是生活随笔為你收集整理的08-面向对象----的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL注入漏洞解决方法
- 下一篇: python-分页代码