python程序設(shè)計(jì) 課件 第11、12章 封裝、繼承與多態(tài);程序設(shè)計(jì)案例分析_第1頁
python程序設(shè)計(jì) 課件 第11、12章 封裝、繼承與多態(tài);程序設(shè)計(jì)案例分析_第2頁
python程序設(shè)計(jì) 課件 第11、12章 封裝、繼承與多態(tài);程序設(shè)計(jì)案例分析_第3頁
python程序設(shè)計(jì) 課件 第11、12章 封裝、繼承與多態(tài);程序設(shè)計(jì)案例分析_第4頁
python程序設(shè)計(jì) 課件 第11、12章 封裝、繼承與多態(tài);程序設(shè)計(jì)案例分析_第5頁
已閱讀5頁,還剩157頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第11章對(duì)象的封裝、繼承與多態(tài)Python程序設(shè)計(jì)第11章對(duì)象的封裝、繼承與多態(tài)本章介紹Python面向?qū)ο蟪绦蛟O(shè)計(jì)中的封裝、繼承和多態(tài),運(yùn)用這些概念與技術(shù),達(dá)到程序代碼重復(fù)使用、穩(wěn)定且易于維護(hù)的目標(biāo)。方法與運(yùn)算符的重載,可達(dá)到多態(tài)的需求?!し庋b、多態(tài)和繼承是面向?qū)ο箝_發(fā)的三個(gè)特點(diǎn)。封裝:將對(duì)象的實(shí)現(xiàn)細(xì)節(jié)與對(duì)象的使用方式分開,允許復(fù)雜程序的模塊化設(shè)計(jì)。第11章對(duì)象的封裝、繼承與多態(tài)繼承:可以從現(xiàn)有類派生一個(gè)新類,支持類之間的方法共享與代碼復(fù)用。多態(tài):不同的類可以實(shí)現(xiàn)具有相同簽名的方法,讓程序更加靈活,允許單行代碼在不同情況下調(diào)用不同的方法。第11章對(duì)象的封裝、繼承與多態(tài)·正確設(shè)計(jì)的類提供了封裝。對(duì)象的內(nèi)部細(xì)節(jié)隱藏在類定義之內(nèi),這樣程序的其他部分不需要知道對(duì)象的實(shí)現(xiàn)方式。這種關(guān)注點(diǎn)分離是Python中的編程慣例,對(duì)象的實(shí)例變量只能通過類的接口方法進(jìn)行訪問或修改。對(duì)象的封裝1面向?qū)ο蟪绦蛟O(shè)計(jì)過程4繼承和多態(tài)2對(duì)象信息的獲取3目錄11.1對(duì)象的封裝封裝的概念限制訪問11.1

對(duì)象的封裝封裝是指將一個(gè)計(jì)算機(jī)系統(tǒng)中的數(shù)據(jù)以及與這個(gè)數(shù)據(jù)相關(guān)的一切操作語言(即描述每一個(gè)對(duì)象的屬性以及其行為的程序代碼)組織到一起,一并封裝在一個(gè)實(shí)體“模塊”(即“類”)中,為軟件結(jié)構(gòu)的相關(guān)部件所具有的模塊性提供良好的基礎(chǔ)。在面向?qū)ο蠹夹g(shù)的相關(guān)原理以及程序語言中,封裝的最基本單位是對(duì)象,而使得軟件結(jié)構(gòu)的相關(guān)部件的實(shí)現(xiàn)“高內(nèi)聚、低耦合”的“最佳狀態(tài)”便是面向?qū)ο蠹夹g(shù)的封裝性所需要實(shí)現(xiàn)的基本目標(biāo)。用戶不需要清楚了解對(duì)象如何對(duì)各種行為進(jìn)行操作、運(yùn)行、實(shí)現(xiàn)等細(xì)節(jié),只需要通過封裝外的接口對(duì)計(jì)算機(jī)進(jìn)行相關(guān)操作即可,大大簡(jiǎn)化了操作步驟。11.1

對(duì)象的封裝封裝提供了一種方便的方式來組成復(fù)雜的解決方案。從設(shè)計(jì)的角度看,封裝提供了一種關(guān)鍵服務(wù),分離了“做什么”與“怎么做”。對(duì)象的實(shí)現(xiàn)與其使用無關(guān)。實(shí)現(xiàn)可以改變,但只要接口保持不變,依賴對(duì)象的其他組件就不會(huì)被破壞。封裝的另一個(gè)優(yōu)點(diǎn)是它支持代碼復(fù)用。它允許打包一般組件,在不同程序中使用。11.1.1封裝的概念通過前面的學(xué)習(xí)我們看到,定義類可以成為模塊化程序的好方法。一旦識(shí)別出一些有用的對(duì)象,就可以用這些對(duì)象編寫一個(gè)算法,并將實(shí)現(xiàn)細(xì)節(jié)推給合適的類定義。主程序只需要關(guān)心對(duì)象可以執(zhí)行的操作,而不用操心如何實(shí)現(xiàn)它們。在面向?qū)ο蠓椒ㄖ校瑢?duì)象的實(shí)現(xiàn)細(xì)節(jié)被封裝在類定義中。不過,封裝只是Python中的編程約定,而不是強(qiáng)制規(guī)則。11.1.1封裝的概念使用對(duì)象的主要原因之一,是為了在程序中隱藏這些對(duì)象的內(nèi)部復(fù)雜性。對(duì)實(shí)例變量的引用通常應(yīng)與其他實(shí)現(xiàn)細(xì)節(jié)在一起保留在類定義內(nèi)。在類之外,與對(duì)象的所有交互一般應(yīng)使用其方法提供的接口來完成。事實(shí)上,Python提供的“屬性”機(jī)制使得實(shí)例變量的訪問安全而優(yōu)雅。封裝的一個(gè)直接優(yōu)點(diǎn)是它允許獨(dú)立地修改和改進(jìn)類,而不用擔(dān)心“破壞”程序的其他部分。只要類提供的接口保持不變,程序的其余部分甚至不能分辨一個(gè)類是否已改變。在設(shè)計(jì)類的時(shí)候,應(yīng)該努力為每個(gè)類提供一套完整的方法。11.1.2限制訪問類的內(nèi)部有屬性和方法,而外部代碼可以通過直接調(diào)用實(shí)例變量的方法來操作數(shù)據(jù),這樣就隱藏了內(nèi)部的復(fù)雜邏輯。但是,一般情況下外部代碼還是可以自由地修改一個(gè)實(shí)例的屬性的。例如:>>>bart=Student('BartSimpson',59)>>>bart.score59>>>bart.score=99>>>bart.score9911.1.2限制訪問如果要讓內(nèi)部屬性不被外部訪問,可以在屬性名稱前加上兩個(gè)下劃線“__”,在Python中,實(shí)例的變量名如果以__開頭,就變成一個(gè)只有內(nèi)部可以訪問私有(private)變量,外部不能訪問,所以,我們把Student類改一改:classStudent(object):def__init__(self,name,score):self.__name=nameself.__score=scoredefprint_score(self):print('%s:%s'%(self.__name,self.__score))11.1.2限制訪問修改后,外部代碼已經(jīng)無法從外部訪問實(shí)例變量.__name和實(shí)例變量.__score了:>>>bart=Student('BartSimpson',59)>>>bart.__nameTraceback(mostrecentcalllast):File"<stdin>",line1,in<module>AttributeError:'Student'objecthasnoattribute'__name'11.1.2限制訪問這就確保外部代碼不能隨意修改對(duì)象內(nèi)部的狀態(tài),通過訪問限制的保護(hù),代碼更加健壯。如果外部代碼要獲取name和score,可以給Student類增加get_name和get_score這樣的方法:classStudent(object):...defget_name(self):returnself.__namedefget_score(self):returnself.__score11.1.2限制訪問如果又要允許外部代碼修改score怎么辦?可以再給Student類增加set_score方法:classStudent(object):...defset_score(self,score):self.__score=score11.1.2限制訪問這樣做,可以在方法中對(duì)參數(shù)做檢查,避免傳入無效的參數(shù):classStudent(object):...defset_score(self,score):if0<=score<=100:self.__score=scoreelse:raiseValueError('badscore')11.1.2限制訪問需要注意的是,在Python中,類似__xxx__的特殊變量的名稱是以雙下劃線開頭并以雙下劃線結(jié)尾的。特殊變量可以直接訪問,不是private變量,所以,不能用__name__、__score__這樣的變量名。有些時(shí)候,你會(huì)看到以一個(gè)下劃線開頭的實(shí)例變量名,比如_name,這樣的實(shí)例變量外部是可以訪問的。然而按照約定,這樣的變量雖然可以被訪問,但是一般視為私有變量,不要隨意訪問。類具備了信息隱蔽的能力,經(jīng)由方法提供公開接口,存取封裝在內(nèi)部的數(shù)據(jù),但我們也能以模塊、函數(shù)、閉包等技術(shù)達(dá)到同樣的效果,而類只是實(shí)現(xiàn)更方便而已。繼承的定義多態(tài)的定義__init__函數(shù)多重繼承機(jī)制元類、復(fù)用與重載11.2繼承和多態(tài)11.2

繼承和多態(tài)繼承是面向?qū)ο蠓椒ǖ闹匾攸c(diǎn)之一。繼承是指,當(dāng)我們定義一個(gè)類的時(shí)候,可以從另一個(gè)類借用(繼承)其行為來定義一個(gè)新類。新類(借用者)被稱為“子類”,現(xiàn)有的類(被借用的類)是其“超類”(或父類、基類)。11.2.1繼承的定義繼承性主要是指兩種或者兩種以上的類之間的聯(lián)系與區(qū)別。繼承,顧名思義,是后者延續(xù)前者的某些方面的特點(diǎn),而在面向?qū)ο蠹夹g(shù)中是指一個(gè)對(duì)象針對(duì)于另一個(gè)對(duì)象的某些獨(dú)有的特點(diǎn)、能力進(jìn)行復(fù)制或者延續(xù)。11.2.1繼承的定義按照繼承源進(jìn)行劃分,繼承可以分為單繼承(一個(gè)對(duì)象僅僅從另外一個(gè)對(duì)象中繼承其相應(yīng)的特點(diǎn))與多繼承(一個(gè)對(duì)象可以同時(shí)從另外兩個(gè)或者兩個(gè)以上的對(duì)象中繼承所需要的特點(diǎn)與能力,并且不會(huì)發(fā)生沖突等現(xiàn)象);如果從繼承中包含的內(nèi)容進(jìn)行劃分,則繼承可以分為四類,分別為取代繼承(一個(gè)對(duì)象在繼承另一個(gè)對(duì)象的能力與特點(diǎn)之后將父對(duì)象進(jìn)行取代)、包含繼承(一個(gè)對(duì)象在將另一個(gè)對(duì)象的能力與特點(diǎn)進(jìn)行完全的繼承之后,又繼承了其他對(duì)象所包含的相應(yīng)內(nèi)容,結(jié)果導(dǎo)致這個(gè)對(duì)象所具有的能力與特點(diǎn)大于等于父對(duì)象,實(shí)現(xiàn)了對(duì)于父對(duì)象的包含)、受限繼承、特化繼承。11.2.1繼承的定義以角色扮演游戲?yàn)槔?,劍、弓、匕首、流星錘都是一種類,各自擁有不同的性質(zhì),包括攻擊距離、傷害力度等,而這些類都繼承自武器類,都能夠被裝備、發(fā)動(dòng)攻擊;而所有武器、防具(盔甲、頭盔、足靴)、食物(藥水、藥草、水果),這些種種又繼承自物品類,代表可使用、可拿取丟棄、可被破壞的一般物品,另外游戲中與情節(jié)相關(guān)的物品,例如門鎖鑰匙、某個(gè)非玩家角色想得到的書籍、某瓶救命藥水等,這些屬于特殊物品,可設(shè)計(jì)為一旦拿取后便無法丟棄,只能用于特定場(chǎng)合。11.2.1繼承的定義通過繼承關(guān)系,子類便可重復(fù)使用父類的程序代碼,例如劍與流星錘都擁有attack(攻擊)方法,計(jì)算攻擊成功與否與傷害力度的程序代碼若相同,便可把該方法放在武器類里,如此一來,劍與流星錘類就能繼承使用;而若是需要擁有不同attack方法實(shí)現(xiàn)的子類,例如弓屬于遠(yuǎn)程武器,還須加上距離的判斷以及所攜帶弓箭的特性,此時(shí)可復(fù)用、自行實(shí)現(xiàn)新的attack方法。11.2.1繼承的定義運(yùn)用繼承建構(gòu)出來的類層級(jí)架構(gòu),讓我們?cè)谘凶x程序代碼時(shí)更容易理解。例如Python的list(列表)與tuple(元組)都繼承了抽象類型序列,因此具備同樣的界面,都可使用索引、切片、內(nèi)置函數(shù)len、方法index等等;而list其實(shí)繼承自可變序列抽象類型,因此具備tuple沒有的原地修改方法,諸如append、insert、remove、clear、pop等等。比如,我們已經(jīng)編寫了一個(gè)名為Animal的類,有一個(gè)run()方法可以直接打?。篶lassAnimal(object):defrun(self):print('Animalisrunning...')11.2.1繼承的定義當(dāng)我們需要編寫Dog和Cat類時(shí),就可以直接從Animal類繼承:classDog(Animal):passclassCat(Animal):passAnimal是Dog的父類,Dog則是Animal的子類。11.2.1繼承的定義繼承的最大的好處是子類獲得了父類的全部功能。由于Animial實(shí)現(xiàn)了run()方法,因此,Dog和Cat作為它的子類,就自動(dòng)擁有了run()方法:dog=Dog()dog.run()cat=Cat()cat.run()運(yùn)行結(jié)果如下:Animalisrunning...Animalisrunning...11.2.1繼承的定義當(dāng)然,也可以對(duì)子類增加一些方法,比如Dog類:classDog(Animal):defrun(self):print('Dogisrunning...')defeat(self):print('Eatingmeat...')11.2.1繼承的定義可以看到,無論是Dog還是Cat,它們r(jià)un()的時(shí)候,顯示的都是Animalisrunning...,而符合邏輯的做法是分別顯示Dogisrunning...和Catisrunning...,因此,對(duì)Dog和Cat類改進(jìn)如下:classDog(Animal):defrun(self):print('Dogisrunning...')classCat(Animal):defrun(self):print('Catisrunning...')11.2.1繼承的定義再次運(yùn)行,結(jié)果如下:Dogisrunning...Catisrunning...11.2.2多態(tài)的定義當(dāng)子類和父類存在相同的run()方法時(shí),子類的run()將覆蓋父類的run(),在代碼運(yùn)行的時(shí)候,總是會(huì)調(diào)用子類的run()。這樣,我們就獲得了繼承的第二個(gè)好處:多態(tài)。多態(tài)是面向?qū)ο蠓椒ǖ牧硪粋€(gè)重要特點(diǎn)。從宏觀的角度來講,多態(tài)性是指在面向?qū)ο蠹夹g(shù)中,當(dāng)不同的多個(gè)對(duì)象同時(shí)接收到同一個(gè)完全相同的消息之后,所表現(xiàn)出來的動(dòng)作是各不相同的,具有多種形態(tài);從微觀的角度來講,多態(tài)性是指在一組對(duì)象的一個(gè)類中,面向?qū)ο蠹夹g(shù)可以使用相同的調(diào)用方式來對(duì)相同的函數(shù)名進(jìn)行調(diào)用,即便這若干個(gè)具有相同函數(shù)名的函數(shù)所表示的函數(shù)是不同的。11.2.2多態(tài)的定義Python采用“動(dòng)態(tài)類型”,只看名稱并不知道對(duì)象指向何種類型,例如程序代碼“a+b”,你并不知道a與b是什么類型、“+”是整數(shù)加法還是字符串連接,同樣的,光看“s.append(t)”也不知道“append”是哪個(gè)類型對(duì)象的方法;直到執(zhí)行時(shí),Python解釋器由該名稱找到綁定的對(duì)象,得知其類型后,才去執(zhí)行相對(duì)應(yīng)的方法或函數(shù),這就是多態(tài)的概念。在執(zhí)行時(shí)才去找出名稱與運(yùn)算符的意義,稱為延遲綁定;另外,運(yùn)算符的多態(tài)也叫做運(yùn)算符重載。11.2.2多態(tài)的定義當(dāng)我們定義一個(gè)類的時(shí)候,實(shí)際上就是定義了一種數(shù)據(jù)類型,自定義的數(shù)據(jù)類型和Python自帶的數(shù)據(jù)類型,比如str、list、dict沒什么兩樣:a=list() #a是list類型b=Animal() #b是Animal類型c=Dog() #c是Dog類型11.2.2多態(tài)的定義判斷一個(gè)變量是否是某個(gè)類型可以用isinstance():>>>isinstance(a,list)True>>>isinstance(b,Animal)True>>>isinstance(c,Dog)True看來a、b、c確實(shí)對(duì)應(yīng)著list、Animal、Dog這3種類型。11.2.2多態(tài)的定義但是,再試試:>>>isinstance(c,Animal)True看來c不僅僅是Dog,c還是Animal。這是因?yàn)镈og是從Animal繼承下來的,當(dāng)我們創(chuàng)建了一個(gè)Dog的實(shí)例c時(shí),c的數(shù)據(jù)類型是Dog,但c同時(shí)也是Animal,因?yàn)镈og本來就是Animal的一種。11.2.2多態(tài)的定義所以,在繼承關(guān)系中,如果一個(gè)實(shí)例的數(shù)據(jù)類型是某個(gè)子類,那它的數(shù)據(jù)類型也可以被看作是父類。但是,反過來就不行:>>>b=Animal()>>>isinstance(b,Dog)FalseDog可以看成Animal,但Animal不可以看成Dog。11.2.2多態(tài)的定義要理解多態(tài)的好處,我們?cè)倬帉懸粋€(gè)函數(shù),這個(gè)函數(shù)接受一個(gè)Animal類型的變量:defrun_twice(animal):animal.run()animal.run()當(dāng)我們傳入Animal的實(shí)例時(shí),run_twice()就打印出:>>>run_twice(Animal())Animalisrunning...Animalisrunning...11.2.2多態(tài)的定義當(dāng)我們傳入Dog的實(shí)例時(shí),run_twice()就打印出:>>>run_twice(Dog())Dogisrunning...Dogisrunning...當(dāng)我們傳入Cat的實(shí)例時(shí),run_twice()就打印出:>>>run_twice(Cat())Catisrunning...Catisrunning...11.2.2多態(tài)的定義現(xiàn)在,如果我們?cè)俣x一個(gè)Tortoise類型,也從Animal派生:classTortoise(Animal):defrun(self):print('Tortoiseisrunningslowly...')當(dāng)我們調(diào)用run_twice()時(shí),傳入Tortoise的實(shí)例:>>>run_twice(Tortoise())Tortoiseisrunningslowly...Tortoiseisrunningslowly...11.2.2多態(tài)的定義可見,新增一個(gè)Animal的子類,不必對(duì)run_twice()做任何修改,實(shí)際上,任何依賴Animal作為參數(shù)的函數(shù)或者方法都可以不加修改地正常運(yùn)行,原因就在于多態(tài)。通過繼承,我們可以構(gòu)建一個(gè)系統(tǒng)的類,以避免重復(fù)操作。此外,新類通??梢曰谠械念?,促進(jìn)代碼復(fù)用。當(dāng)我們需要傳入Dog、Cat、Tortoise…時(shí),我們只需要接收Animal類型就可以了,因?yàn)镈og、Cat、Tortoise…都是Animal類型,然后,按照Animal類型進(jìn)行操作。由于Animal類型有run()方法,因此,傳入的任意類型,只要是Animal類或者子類,就會(huì)自動(dòng)調(diào)用實(shí)際類型的run()方法,即多態(tài)。11.2.2多態(tài)的定義對(duì)于一個(gè)變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調(diào)用run()方法,而具體調(diào)用的run()方法是作用在Animal、Dog、Cat還是Tortoise對(duì)象上,由運(yùn)行時(shí)該對(duì)象的確切類型決定,這就是多態(tài)的威力所在:調(diào)用方只管調(diào)用,不管細(xì)節(jié),而當(dāng)我們新增一種Animal的子類時(shí),只要確保run()方法編寫正確,不管原來的代碼是如何調(diào)用的。繼承還可以一級(jí)一級(jí)地繼承下來,這些繼承關(guān)系看上去就像一顆倒著的樹。

繼承樹11.2.2多態(tài)的定義定義類時(shí),把想要繼承的父類名寫在子類名后的括號(hào)內(nèi)即可;所有Python類缺省都繼承自object,所以下面三個(gè)類定義都相同。classFoo(object):passclassFoo():passclassFoo:pass #可省略括號(hào)11.2.2多態(tài)的定義子類繼承了父類的所有屬性項(xiàng),所以子類的實(shí)例也能存取父類定義的方法,換句話說,子類繼承了所有接口,以此為基礎(chǔ),再做修改進(jìn)行定制。classA(): #定義類別A,缺省繼承自objectx,y=3,4 #類的屬性項(xiàng)

def__init__(self,a,b):self.a=a #實(shí)例的屬性項(xiàng)

self.b=bdeffoo(self):print(self.a+self.b+self.x+self.y)11.2.2多態(tài)的定義classB(A): #定義類B,繼承自Aww=1000x=1003 #B自己沒有__init__defbar(self):self.foo() #使用父類的方法

print(self.x+self.ww)11.2.2多態(tài)的定義a0=A(13,14) #建立類A的實(shí)例a0.foo() #調(diào)用方法b0=B(103,104) #建立類B的實(shí)例b0.foo() #B繼承了A的方法foo,也可調(diào)用b0.bar() #B自己新增的方法11.2.2多態(tài)的定義11.2.2多態(tài)的定義在上面例子中,B沒有定義__init__,所以調(diào)用構(gòu)造函數(shù)B(103,104)時(shí),Python解釋器在B里找不到后、會(huì)到A里去找,以此進(jìn)行初始化,所以B的實(shí)例也會(huì)有a與b。B既然繼承了A的所有屬性項(xiàng),自然也能夠調(diào)用A的foo,而且B又新增了方法bar以及類屬性項(xiàng)x,它會(huì)隱蔽A的x,所以在bar方法里的self.x會(huì)是1003。每當(dāng)出現(xiàn)“對(duì)象.屬性項(xiàng)”的語法時(shí),其中的對(duì)象包括類與實(shí)例,Python都會(huì)啟動(dòng)“屬性項(xiàng)搜尋程序”,若是實(shí)例的話,會(huì)先在實(shí)例的命名空間)里尋找,找不到再去類里找,然后再到父類里去找;若是類的話,就到類里、再到父類里尋找。11.2.3__init__函數(shù)前一個(gè)例子中的類B并沒有__init__,但因?yàn)槔^承了A的__init__,所以建立實(shí)例對(duì)象時(shí),仍會(huì)去調(diào)用A的__init__,所以仍擁有a與b。若B想要增加屬性項(xiàng)c,必須寫在B的__init__里,但B的__init__并不會(huì)自動(dòng)調(diào)用A的__init__,為此,請(qǐng)看下面例子:def__init__(self): #B的__init__#self.__init__(13,14) #不行

#A.__init__(self,13,14) #可以

#super(B,self).__init__(13,14) #可以

super().__init__(13,14) #可以,3.x版才可

self.c=1511.2.3__init__函數(shù)若使用“self.__init__(13,14)”的話,因?yàn)檫@是“對(duì)象.屬性項(xiàng)”的語法,所以又會(huì)啟動(dòng)MRO(MethodResolutionOrder,方法解析順序),到self實(shí)例里找到__init__(類B),需要一個(gè)參數(shù),而你卻傳入三個(gè)參數(shù):self、13、14,所以出錯(cuò)。11.2.3__init__函數(shù)對(duì)于支持繼承的編程語言來說,其方法(屬性)可能定義在當(dāng)前類,也可能來自于基類,所以在方法調(diào)用時(shí)就需要對(duì)當(dāng)前類和基類進(jìn)行搜索以確定方法所在的位置。而搜索的順序就是所謂的“方法解析順序(MRO)”。對(duì)于只支持單繼承的語言來說,MRO一般比較簡(jiǎn)單;而對(duì)于Python這種支持多繼承的語言來說,MRO就復(fù)雜很多。11.2.3__init__函數(shù)為此,Python提供了內(nèi)置函數(shù)super,只能用于新式類,調(diào)用后可得到代理對(duì)象,它的作用是把方法調(diào)用導(dǎo)引到父類。傳入當(dāng)前類與實(shí)例“super(B,self)”,便可得到類似于父類實(shí)例的代理對(duì)象,然后調(diào)用父類的__init__。至于無參數(shù)“super()”則由Python解釋器自動(dòng)填入?yún)?shù)。11.2.4多重繼承機(jī)制之前定義的類都只有繼承一個(gè)父類,但若想繼承兩個(gè)以上的父類呢?例如蝙蝠既是哺乳類動(dòng)物、也是有翅動(dòng)物。此時(shí)我們需要多重繼承的機(jī)制,只須在定義類時(shí)把父類依序?qū)懺诶ㄌ?hào)之內(nèi)。classA():defam(self):print('Aam')classB():defbm(self):print('Bbm')classC(A,B):defcm(self):print('Ccm')11.2.4多重繼承機(jī)制上述例子中,以C建立實(shí)例后,可調(diào)用方法am、bm、cm,都能找到正確的實(shí)現(xiàn)。讀者可檢查看看類C的屬性項(xiàng)__bases__,這是個(gè)記錄著它的父類的tuple,也就是A與B,其列出順序正是定義時(shí)放在括號(hào)里的順序。不過若是A與B繼承自同一個(gè)父類,形成如圖所示的鉆石形,此時(shí)將會(huì)出現(xiàn)許多問題。以下面程序碼為例,c.foo()應(yīng)該執(zhí)行哪一個(gè)方法呢?

鉆石形(菱形)多重繼承11.2.4多重繼承機(jī)制classS():deffoo(self):passclassA(S):deffoo(self):print('Afoo') #A的fooclassB(S):deffoo(self):print('Bfoo') #B的fooclassC(A,B):passC().foo() #應(yīng)該是哪個(gè)呢?11.2.4多重繼承機(jī)制當(dāng)某類的父類重復(fù)出現(xiàn)時(shí),會(huì)先到實(shí)例與實(shí)例的類里搜尋,找不到時(shí),會(huì)根據(jù)類定義列出的父類,從左到右依序搜尋,若都找不到,再去更上一層的父類。以上面舉例而言,搜尋順序是C-A-B-Sobject,所以會(huì)輸出'Afoo'。11.2.4多重繼承機(jī)制雖然在實(shí)際中并不多見,但多重繼承可能會(huì)非常復(fù)雜,因?yàn)槌烁鶕?jù)上述順序來搜尋,還須符合每個(gè)類列出的父類順序,例如下面舉例:classA(object):passclassB(object):passclassX(A,B):pass #X說先A再BclassY(B,A):pass #Y說先B再AclassZ(X,Y):pass11.2.4多重繼承機(jī)制對(duì)Z來說,其繼承的兩個(gè)父類X與Y,分別列出不一樣順序的A與B,這么一來,Z就沒辦法決定到底A還是B應(yīng)該在前,碰到這種狀況時(shí),Python無法決定,就會(huì)出現(xiàn)錯(cuò)誤信息“TypeError:Cannotcreateaconsistentmethodresolutionorder(MRO)forbasesB,A”(類型錯(cuò)誤:無法為父類A、B建立一致的MRO),必須修改繼承關(guān)系。11.2.5元類的概念所謂元類(又稱“后設(shè)類”)是類的類(見圖)。圖中所有的橢圓形都是對(duì)象,右欄是我們最熟悉的對(duì)象,例如整數(shù)對(duì)象、列表對(duì)象,為了區(qū)別起見,稱為實(shí)例;中欄則是類(類型),包括整數(shù)類型、列表類型、自定義類,另外還有類繼承層級(jí)的頂端:類型object。

元類、類、實(shí)例11.2.5元類的概念圖中的空心箭頭代表“繼承”關(guān)系,類型int與類型list都繼承自object。實(shí)心箭頭則代表“建立、生產(chǎn)”關(guān)系,例如類型int建立出整數(shù)對(duì)象3、整數(shù)對(duì)象1000。從繼承關(guān)系可得到整個(gè)繼承層級(jí)架構(gòu),而從建立關(guān)系可知道實(shí)例的類型為何,順著實(shí)心箭頭即可得知。所有對(duì)象都繼承自object,除了object自身以外。除此之外,圖中左欄還有個(gè)非常特殊的對(duì)象,其類型為type、名稱也是type,稱之為元類。我們知道實(shí)例對(duì)象由類對(duì)象建立,那么類對(duì)象又是由誰建立呢?正是元類對(duì)象type,所以中欄的類對(duì)象的類型都是type。從圖中還可得知,object也是由type建立,而type的父類是object,type自己建立自己,看起來很奇怪,但這是示意圖,實(shí)際上type與object很難清楚地劃分界限。11.2.6復(fù)用與重載繼承時(shí),若子類也定義了跟父類相同的方法名,提供不同的功能,稱為“復(fù)用”;而不同類型的實(shí)例,調(diào)用同名方法時(shí)會(huì)去執(zhí)行不同的方法,則稱為重載,屬于多態(tài)的表現(xiàn)形式。classAnimal():defshout(self):print('Animalshout')classDog(Animal): #雖然狗與貓都繼承自動(dòng)物

defshout(self):print('Dogshout')classCat(Animal): #但它們的叫聲都不一樣

defshout(self):print('Catshout')d=Dog();d.shout()c=Cat();c.shout()11.2.6復(fù)用與重載類Dog與Cat都有方法shout,也就是說接口相同,但實(shí)現(xiàn)不同,當(dāng)調(diào)用shout時(shí),Python解釋器會(huì)根據(jù)實(shí)例的類來決定該執(zhí)行哪一個(gè)方法,這就是多態(tài)的意思。我們不只能重載自己定義的類的方法,也能重載類object的方法,例如__str__,某實(shí)例需要以字符串形式表達(dá)自己時(shí),就會(huì)調(diào)用此方法。重載方法__str__的話,就能為類提供適當(dāng)?shù)淖址憩F(xiàn)形式。classMyClass():def__str__(self):return'aMyClassinstance'mc=MyClass()print(mc) #需要字符串形式,print(str(mc)) #內(nèi)置函數(shù)str,也會(huì)去調(diào)用實(shí)例的__str__方法11.2.6復(fù)用與重載事實(shí)上print只能輸出字符串,當(dāng)參數(shù)類型不是字符串時(shí),并非由print負(fù)責(zé)把參數(shù)(某種對(duì)象)轉(zhuǎn)為字符串,因?yàn)槟敲醋龅脑?,將來增加新類時(shí)還要修改print;而是由對(duì)象自己想辦法提供表達(dá)自己的字符串形式,也就是由__str__負(fù)責(zé)。同樣的,也能為自定義類提供__repr__,返回適合被Python解釋器解析、再次建立相同實(shí)例的字符串形式(會(huì)被視為程序代碼)以及__format__,提供格式化的字符串形式,會(huì)被str.format()與內(nèi)置函數(shù)format使用??烧{(diào)用內(nèi)置函數(shù)dir來查看類object可重載的方法。11.3對(duì)象信息的獲取使用type()函數(shù)使用dir()函數(shù)11.3

對(duì)象信息的獲取當(dāng)我們拿到一個(gè)對(duì)象的引用時(shí),如何知道這個(gè)對(duì)象是什么類型呢?可以使用isinstance()函數(shù)??偸莾?yōu)先使用isinstance()判斷類型,可以將指定類型及其子類“一網(wǎng)打盡”。此外,還可以使用type()或dir()函數(shù)。11.3.1type()函數(shù)基本類型都可以用type()來判斷。例如:>>>type(123)<class'int'>>>>type('str')<class'str'>>>>type(None)<type(None)'NoneType'>11.3.1type()函數(shù)如果一個(gè)變量指向函數(shù)或者類,也可以用type()判斷:>>>type(abs)<class'builtin_function_or_method'>>>>type(a)<class'__main__.Animal'>11.3.1type()函數(shù)type()函數(shù)返回對(duì)應(yīng)的類類型。如果我們要在if語句中判斷,就需要比較兩個(gè)變量的type類型是否相同:>>>type(123)==type(456)True>>>type(123)==intTrue>>>type('abc')==type('123')True>>>type('abc')==strTrue>>>type('abc')==type(123)False11.3.1type()函數(shù)判斷基本數(shù)據(jù)類型可以直接寫int,str等,但如果要判斷一個(gè)對(duì)象是否是函數(shù),可以使用types模塊中定義的常量:>>>importtypes>>>deffn():...pass...>>>type(fn)==types.FunctionTypeTrue>>>type(abs)==types.BuiltinFunctionTypeTrue>>>type(lambdax:x)==types.LambdaTypeTrue>>>type((xforxinrange(10)))==types.GeneratorTypeTrue11.3.2dir()函數(shù)如果要獲得一個(gè)對(duì)象的所有屬性和方法,可以使用dir()函數(shù),它返回一個(gè)包含字符串的list,比如,獲得一個(gè)str對(duì)象的所有屬性和方法:>>>dir('ABC')['__add__','__class__',...,'__subclasshook__','capitalize','casefold',...,'zfill']類似__xxx__的屬性和方法在Python中都是有特殊用途的,比如__len__方法返回長(zhǎng)度。如果你調(diào)用len()函數(shù)試圖獲取一個(gè)對(duì)象的長(zhǎng)度,實(shí)際上,在len()函數(shù)內(nèi)部,它自動(dòng)去調(diào)用該對(duì)象的__len__()方法,所以,下面的代碼是等價(jià)的:>>>len('ABC')3>>>'ABC'.__len__()311.3.2dir()函數(shù)自定義類如果也想用len(myObj)的話,就自己寫一個(gè)__len__()方法:>>>classMyDog(object):...def__len__(self):...return100...>>>dog=MyDog()>>>len(dog)100剩下的都是普通屬性或方法,比如lower()返回小寫的字符串:>>>'ABC'.lower()'abc'11.3.2dir()函數(shù)僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),可以直接操作一個(gè)對(duì)象的狀態(tài):>>>classMyObject(object):...def__init__(self):...self.x=9...defpower(self):...returnself.x*self.x...>>>obj=MyObject()11.3.2dir()函數(shù)緊接著,可以測(cè)試該對(duì)象的屬性:>>>hasattr(obj,'x') #有屬性'x'嗎?True>>>obj.x9>>>hasattr(obj,'y') #有屬性'y'嗎?False>>>setattr(obj,'y',19) #設(shè)置一個(gè)屬性'y'>>>hasattr(obj,'y') #有屬性'y'嗎?True>>>getattr(obj,'y') #獲取屬性'y'19>>>obj.y #獲取屬性'y'1911.3.2dir()函數(shù)如果試圖獲取不存在的屬性,會(huì)反饋AttributeError錯(cuò)誤:>>>getattr(obj,'z') #獲取屬性'z'Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>AttributeError:'MyObject'objecthasnoattribute'z'11.3.2dir()函數(shù)可以傳入一個(gè)default參數(shù),如果屬性不存在,就返回默認(rèn)值:>>>getattr(obj,'z',404) #獲取屬性'z',如果不存在,返回默認(rèn)值40440411.3.2dir()函數(shù)也可以獲得對(duì)象的方法:>>>hasattr(obj,'power') #有屬性'power'嗎?True>>>getattr(obj,'power') #獲取屬性'power'<boundmethodMyObject.powerof<__main__.MyObjectobjectat0x10077a6a0>>>>>fn=getattr(obj,'power')#獲取屬性'power'并賦值到變量fn>>>fn #fn指向obj.power<boundmethodMyObject.powerof<__main__.MyObjectobjectat0x10077a6a0>>>>>fn() #調(diào)用fn()與調(diào)用obj.power()是一樣的8111.3.2dir()函數(shù)可見,通過內(nèi)置的一系列函數(shù),可以對(duì)任意一個(gè)Python對(duì)象進(jìn)行剖析,拿到其內(nèi)部的數(shù)據(jù)。要注意的是,只有在不知道對(duì)象信息的時(shí)候,才會(huì)去獲取對(duì)象信息。如果可以直接寫: sum=obj.x+obj.y就不要寫: sum=getattr(obj,'x')+getattr(obj,'y')一個(gè)正確的用法的例子如下:defreadImage(fp):ifhasattr(fp,'read'):returnreadData(fp)returnNone11.3.2dir()函數(shù)假設(shè)我們希望從文件流fp中讀取圖像,首先要判斷該fp對(duì)象是否存在read方法,如果存在,則該對(duì)象是一個(gè)流,如果不存在,則無法讀取。hasattr()就派上了用場(chǎng)。11.4

面向?qū)ο蟪绦蛟O(shè)計(jì)過程11.4

面向?qū)ο蟪绦蛟O(shè)計(jì)過程大多數(shù)現(xiàn)代計(jì)算機(jī)應(yīng)用程序是用以數(shù)據(jù)為中心的計(jì)算視圖進(jìn)行設(shè)計(jì)的。這種所謂的面向?qū)ο笤O(shè)計(jì)(OOD)過程,是自頂向下設(shè)計(jì)的有力補(bǔ)充,用于開發(fā)可靠的、性價(jià)比高的軟件系統(tǒng)。設(shè)計(jì)的本質(zhì)是模塊化方法及其接口的角度來描述系統(tǒng),每個(gè)組件通過其接口提供一組服務(wù),其他組件是服務(wù)的用戶或“客戶”??蛻舳酥恍枰私夥?wù)的接口,該服務(wù)的實(shí)現(xiàn)細(xì)節(jié)并不重要。事實(shí)上,內(nèi)部細(xì)節(jié)可能會(huì)發(fā)生根本變化,但不會(huì)影響客戶。類似地,提供服務(wù)的組件不必考慮如何使用該服務(wù),只需要確保提供的服務(wù)可信賴。這種關(guān)注點(diǎn)分離的方法使復(fù)雜系統(tǒng)的設(shè)計(jì)成為可能。在自頂向下的設(shè)計(jì)中,函數(shù)扮演著模塊化的角色??蛻舫绦蛑灰芾斫夂瘮?shù)的功能,就可以使用該函數(shù),函數(shù)完成的細(xì)節(jié)被封裝在函數(shù)定義中。11.4

面向?qū)ο蟪绦蛟O(shè)計(jì)過程對(duì)象的關(guān)鍵在于定義。一旦編寫了一個(gè)合適的類定義,就可以完全忽略該類的工作方式,僅僅依賴于外部接口,即方法。如果我們可以將一個(gè)大問題分解為一系列合作的類,在理解程序任何給定的部分時(shí),就會(huì)大大降低要考慮的復(fù)雜程度。每個(gè)類都是獨(dú)立的。面向?qū)ο笤O(shè)計(jì)是一個(gè)過程,針對(duì)給定問題來尋找并定義一組有用的類。像所有設(shè)計(jì)一樣,它既是藝術(shù)又是科學(xué)。OOD有許多不同的方法,每種方法都有自己的特殊技術(shù)、符號(hào)等等。事實(shí)上,了解OOD設(shè)計(jì)的最佳方式是盡可能多的去做類的設(shè)計(jì)。11.4

面向?qū)ο蟪绦蛟O(shè)計(jì)過程以下是關(guān)于OOD的一些直觀指導(dǎo):(1)尋找候選對(duì)象。設(shè)計(jì)的目標(biāo)是定義一組有助于解決問題的對(duì)象。首先仔細(xì)考慮問題陳述。對(duì)象通常由名詞描述。可以在問題陳述中劃出所有名詞,并逐一考慮。其中哪些實(shí)際上會(huì)在程序中表示出來?哪些有“有趣”的行為?可以表示為基本數(shù)據(jù)類型(數(shù)字或字符串)的東西可能不是重要的候選對(duì)象,關(guān)注點(diǎn)應(yīng)該在涉及一組相關(guān)數(shù)據(jù)項(xiàng)的內(nèi)容上。(2)識(shí)別實(shí)例變量。一旦發(fā)現(xiàn)了一些可能的對(duì)象,應(yīng)考慮每個(gè)對(duì)象完成工作所需的信息。實(shí)例變量有什么樣的值?一些對(duì)象屬性將具有基本類型的值,其他屬性可能是復(fù)雜的類型,表明需要其他有用的對(duì)象/類。努力為程序中的所有數(shù)據(jù)找到良好的“家庭”類。11.4

面向?qū)ο蟪绦蛟O(shè)計(jì)過程(3)考慮接口。當(dāng)識(shí)別出潛在的對(duì)象/類和一些關(guān)聯(lián)的數(shù)據(jù)時(shí),請(qǐng)考慮該類的對(duì)象需要哪些操作才能使用??梢韵瓤紤]問題陳述中的動(dòng)詞。動(dòng)詞用于描述動(dòng)作:必須做什么。列出類需要的方法。請(qǐng)記住,對(duì)象數(shù)據(jù)的所有操作應(yīng)通過你所提供的方法進(jìn)行。(4)簡(jiǎn)化復(fù)雜方法。一些方法看起來可以用幾行代碼來完成。其他方法則需要相當(dāng)大的努力來開發(fā)一種算法。使用自頂向下的設(shè)計(jì)和逐步求精來了解更多較難方法的細(xì)節(jié)。隨著你取得進(jìn)展,可能會(huì)發(fā)現(xiàn)需要與其他類進(jìn)行一些新的交互,這可能迫使你向其他類添加新的方法。有時(shí)你可能會(huì)發(fā)現(xiàn)需要一種全新的對(duì)象,要求對(duì)另一個(gè)類進(jìn)行定義。(5)迭代式設(shè)計(jì)。在設(shè)計(jì)過程中,你會(huì)在設(shè)計(jì)新類和向已有類添加方法之間進(jìn)行多次反復(fù)。任何事情,只要似乎值得你注意,就為之投入工作。11.4

面向?qū)ο蟪绦蛟O(shè)計(jì)過程(6)嘗試替代方案。不要害怕廢除似乎無效的方法,也不要害怕探索一個(gè)想法。良好的設(shè)計(jì)涉及大量的思考與嘗試。當(dāng)你查看他人的程序時(shí),會(huì)看到完成的作品,而不是他們實(shí)現(xiàn)的過程。如果程序設(shè)計(jì)良好,可能不是第一次嘗試的結(jié)果。(7)保持簡(jiǎn)單。在設(shè)計(jì)的每個(gè)步驟中,嘗試找出解決手頭問題的最簡(jiǎn)單方法,除非確實(shí)需要更復(fù)雜的方法來實(shí)現(xiàn)功能,否則不要做復(fù)雜設(shè)計(jì)。謝謝大家第12章綜合案例分析Python程序設(shè)計(jì)第12章綜合案例分析作為一本Python程序設(shè)計(jì)的入門書,本章已至尾聲,但Python與程序設(shè)計(jì)所涵蓋的范疇,絕非一本書的篇幅所能容納。在這最后一章,我們通過幾個(gè)典型程序設(shè)計(jì)案例的分析,盡可能完整地展現(xiàn)Python程序設(shè)計(jì)的各個(gè)方面,展示Python程序設(shè)計(jì)的基本功與技巧,助推大家喜歡Python,熱愛程序設(shè)計(jì),順利發(fā)展自己的專業(yè)特長(zhǎng)。GUI設(shè)計(jì)1并行處理機(jī)制2模擬乒乓球比賽3目錄12.1GUI設(shè)計(jì)Tkinter模塊程序?qū)嵗河肎UI界面計(jì)算斐波那契數(shù)函數(shù)程序?qū)嵗汉?jiǎn)單計(jì)算器12.1GUI設(shè)計(jì)GUI(GraphicalUserInterface,圖形用戶接口,或稱圖形用戶界面)是一種結(jié)合計(jì)算機(jī)科學(xué)、美學(xué)、心理學(xué)、行為學(xué)及各商業(yè)領(lǐng)域需求分析的人機(jī)系統(tǒng)工程,它強(qiáng)調(diào)人-機(jī)-環(huán)境三者作為一個(gè)系統(tǒng)來進(jìn)行總體設(shè)計(jì)。Python提供了一些圖形開發(fā)界面的庫供開發(fā)者選擇,常用的PythonGUI庫包括:

(1)Tkinter:Tkinter模塊(Tk接口)是Python的標(biāo)準(zhǔn)TkGUI工具包的接口,Tk可以應(yīng)用在Windows環(huán)境,實(shí)現(xiàn)本地窗口風(fēng)格。

(2)wxpython:一款開源軟件,是Python語言中一套優(yōu)秀的GUI圖形庫,能夠很方便的創(chuàng)建完整的、功能健全的GUI用戶界面。

(3)Jython:可以實(shí)現(xiàn)和Java的無縫集成。除了一些標(biāo)準(zhǔn)模塊,Jython使用Java的模塊,Jython幾乎擁有標(biāo)準(zhǔn)的Python中不依賴于C語言的全部模塊。12.1.1Tkinter模塊Tk是Python默認(rèn)的一套跨平臺(tái)建立圖形化用戶界面的圖形元件庫,Tk的Python接口是Tkinter,通過標(biāo)準(zhǔn)程序庫中的模塊Tkinter調(diào)用Tk進(jìn)行圖形界面開發(fā)。與其他開發(fā)庫相比,Tk非常簡(jiǎn)單,它所提供的功能用來開發(fā)一般的應(yīng)用完全夠用,且能在大部分平臺(tái)上運(yùn)行。不足之處在于,Tk缺少合適的可視化界面設(shè)計(jì)工具,需要通過代碼來完成窗口設(shè)計(jì)和元素布局。12.1.1Tkinter模塊Tkinter模塊已在Python中內(nèi)置,使用時(shí)只需導(dǎo)入即可。導(dǎo)入方法是:方法1:importtkinterastk:導(dǎo)入tkinter,但沒引入任何組件,在使用時(shí)需要使用tk前綴,例如,為引入按鈕,則表示為:tkButton。方法2:fromtkinterimport*:將tkinter中的所有組件一次性引入。12.1.1Tkinter模塊利用tkinter模塊來引用Tk構(gòu)建和運(yùn)行GUI,需要5步:(1)導(dǎo)入tkinter模塊;(2)創(chuàng)建一個(gè)頂層窗口;(3)在頂層窗口構(gòu)建所需要的GUI模塊和功能;(4)將每一個(gè)模塊與底層程序代碼關(guān)聯(lián)起來;(5)執(zhí)行主循環(huán)。12.1.1Tkinter模塊Tkinter的主要組件包括:Button:按鈕。類似標(biāo)簽,但提供額外功能,如鼠標(biāo)按下、釋放及鍵盤操作事件。Canvas:畫布。提供繪圖功能,包含圖形或位圖。Checkbutton:選擇按鈕。一組方框,可以選擇其中的任意多個(gè)。Radiobutton:?jiǎn)芜x按鈕。一組方框,其中只有一個(gè)可被選中。Entry:文本框。單行文字域,用來收集鍵盤輸入。Frame:框架。包含其他組件的純?nèi)萜?。Label:標(biāo)簽。用來顯示文字或圖片。Listbox:列表框。一個(gè)選項(xiàng)列表,用戶可以從中選擇。Menu:菜單。單擊后,彈出一個(gè)選項(xiàng)列表,用戶可以從中選擇。Menubutton:菜單按鈕。用來包含菜單的組件。12.1.1Tkinter模塊Message:消息框。類似于標(biāo)簽,但可以顯示多行文本。Scale:進(jìn)度條。線性“滑塊”組件,可設(shè)定起始值和結(jié)束值,顯示當(dāng)前位置的精確度。Scrollbar:滾動(dòng)條。對(duì)其支持的組件提供滾動(dòng)功能。Text:文本域。多行文字區(qū)域,用來收集(或顯示)用戶輸入的文字。Toplevel:頂級(jí)。類似框架,但提供一個(gè)獨(dú)立的窗口容器。Tkinter組件的共同屬性是:dimensions:尺寸。colors:顏色。fonts:字體。anchors:錨。12.1.1Tkinter模塊reliefstyles:浮雕式。bitmaps:顯示位圖。cursors:光標(biāo)的外形。12.1.1Tkinter模塊創(chuàng)建GUi應(yīng)用程序窗口的代碼模板是:fromtkinterimport*:tk=Tk()#代碼...tk.mainloop() #進(jìn)入消息循環(huán)。在Tkinter開發(fā)的應(yīng)用程序中,需要在其他窗口之前創(chuàng)建一個(gè)頂層窗口(根窗口)。12.1.2

程序?qū)嵗河肎UI界面計(jì)算斐波那契數(shù)函數(shù)斐波那契數(shù)(亦稱斐波那契數(shù)列、黃金分割數(shù)列、費(fèi)氏數(shù)列等)指的是這樣一個(gè)數(shù)列:1、1、2、3、5、8、13、21、......在數(shù)學(xué)上,斐波那契數(shù)列以如下遞歸方法定義:F0=0,F(xiàn)1=1,F(xiàn)n=Fn-1+Fn-2(n>=2,n∈N*),用文字來說,就是斐波那契數(shù)列由0和1開始,之后的斐波那契數(shù)列系數(shù)就是由前面的兩數(shù)相加。【程序?qū)嵗?2-1】用圖形界面計(jì)算斐波那契數(shù)函數(shù)。#fibonacci.pyfromtkinterimport* #讀入模塊tkinterdeffib(n): #計(jì)算費(fèi)氏數(shù)的函數(shù)

a,b=0,1foriinrange(n):a,b=b,a+breturna12.1.2

程序?qū)嵗河肎UI界面計(jì)算斐波那契數(shù)函數(shù)classApp(Frame): #繼承圖形元件Framedef__init__(self,master=None):Frame.__init__(self,master)self.pack() #pack會(huì)調(diào)整大小,設(shè)為顯示狀態(tài)

#在Frame里放入輸入欄字節(jié)件Entryself.entry_n=Entry(self,width=10)self.entry_n.pack()self.fn=StringVar() #產(chǎn)生字符串變數(shù),

self.label_fn=Label(self,textvariable=self.fn,width=50) #與標(biāo)簽元件Label綁在一起

self.fn.set('結(jié)果') #改變時(shí),標(biāo)簽也會(huì)隨之更新12.1.2

程序?qū)嵗河肎UI界面計(jì)算斐波那契數(shù)函數(shù)self.label_fn.pack()#建立按鈕元件Button,也是放在Frame里

self.btn_cal=Button(self,text="計(jì)算",command=self.cal_fib)self.btn_cal.pack()#按下時(shí),會(huì)調(diào)用方法cal_fib#另一個(gè)按鈕元件,按下會(huì)關(guān)閉根元件

self.btn_quit=Button(self,text="退出",fg="red",command=root.destroy)self.btn_quit.pack(side=BOTTOM)12.1.2

程序?qū)嵗河肎UI界面計(jì)算斐波那契數(shù)函數(shù)defcal_fib(self): #當(dāng)按下計(jì)算按鈕時(shí)

try:n=int(self.entry_n.get()) #取得輸入,轉(zhuǎn)成數(shù)字

self.fn.set(str(fib(n))) #更新顯示計(jì)算結(jié)果

exceptException: #若轉(zhuǎn)換失敗,顯示錯(cuò)誤信息

self.fn.set('非法輸入')root=Tk() #產(chǎn)生根元件app=App(root) #加入Frame子類app.mainloop() #啟動(dòng)事件循環(huán)12.1.2

程序?qū)嵗河肎UI界面計(jì)算斐波那契數(shù)函數(shù)12.1.2

程序?qū)嵗河肎UI界面計(jì)算斐波那契數(shù)函數(shù)程序分析:用圖形界面計(jì)算斐波那契數(shù)函數(shù)的程序運(yùn)行結(jié)果如圖所示。

GUI示例的運(yùn)行結(jié)果12.1.2

程序?qū)嵗河肎UI界面計(jì)算斐波那契數(shù)函數(shù)建立一個(gè)根元件,在建立圖形元件并設(shè)定好元件之間的關(guān)系后,必須調(diào)用最重要的mainloop啟動(dòng)事件循環(huán),此循環(huán)是圖形界面的核心概念,也是與一般文字模式程序最大的不同之處。我們可把事件循環(huán)想象成不斷執(zhí)行的循環(huán),在此循環(huán)中,會(huì)檢查有無事件。事件代表著用戶的輸入,例如鼠標(biāo)與鍵盤,事件也可能來自系統(tǒng),例如窗口被移動(dòng)、最小化、關(guān)閉等等,另外還有tkinter本身的事件,負(fù)責(zé)管理窗口與圖形元件的外觀以及在有需要時(shí)更新顯示畫面。請(qǐng)記錄:錄入并調(diào)試運(yùn)行本程序?qū)嵗?。如果不能順利完成,?qǐng)分析可能的原因是什么?12.1.3

程序?qū)嵗汉?jiǎn)單計(jì)算器我們來開發(fā)一個(gè)簡(jiǎn)單的Python計(jì)算器程序。該計(jì)算器有十個(gè)數(shù)字(0~9)、小數(shù)點(diǎn)(.)、四種運(yùn)算(+、-、*、/)以及一些特殊按鈕組成:C用于清除顯示,=用于計(jì)算。當(dāng)按鈕被點(diǎn)擊時(shí),對(duì)應(yīng)的字符將出現(xiàn)在顯示框中,按下=鍵將進(jìn)行求值并顯示結(jié)果值。Python簡(jiǎn)單計(jì)算器示例【程序?qū)嵗?2-2】計(jì)算器的完整程序。#cal.py簡(jiǎn)單的計(jì)算器importtkinterastkclassCalc(tk.Tk):#計(jì)算器窗體類

def__init__(self):#初始化實(shí)例

tk.Tk.__init__(self)self.title("計(jì)算器")self.memory=0 #暫存數(shù)值

self.create()12.1.3程序?qū)嵗汉?jiǎn)單計(jì)算器defcreate(self):#創(chuàng)建界面

btn_list=["C","M->","->M","/","7","8","9","*","4","5","6","-","1","2","3","+","+/-","0",".","="]12.1.3程序?qū)嵗汉?jiǎn)單計(jì)算器r=1c=0forbinbtn_list:self.button=tk.Button(self,text=b,width=5,command=(lambdax=b:self.click(x)))self.button.grid(row=r,column=c,padx=3,pady=6)c+=1ifc>3:c=0r+=1self.entry=tk.Entry(self,width=24,borderwidth=2,bg="yellow",font=("Consolas",12))12.1.3程序?qū)嵗汉?jiǎn)單計(jì)算器self.entry.grid(row=0,column=0,columnspan=4,padx=8,pady=6)defclick(self,key):#響應(yīng)按鈕

ifkey=="=": #輸出結(jié)果

result=eval(self.entry.get())self.entry.insert(tk.END,"="+str(result))elifkey=="C": #清空輸入框

self.entry.delete(0,tk.END)elifkey=="->M": #存入數(shù)值

self.memory=self.entry.get()12.1.3程序?qū)嵗汉?jiǎn)單計(jì)算器if"="inself.memory:ix=self.memory.find("=")self.memory=self.memory[ix+2:]self.title("M="+self.memory)elifkey=="M->": #取出數(shù)值

ifself.memory:self.entry.insert(tk.END,self.memory)elifkey=="+/-": #正負(fù)翻轉(zhuǎn)

if"="inself.entry.get():self.entry.delete(0,tk.END)elifself.entry.get()[0]=="-":self.entry.delete(0)12.1.3程序?qū)嵗汉?jiǎn)單計(jì)算器else:self.entry.insert(0,"-")else: #其他鍵

if"="inself.entry.get():self.entry.delete(0,tk.END)self.entry.insert(tk.END,key)if__name__=="__main__":Calc().mainloop()12.1.3程序?qū)嵗汉?jiǎn)單計(jì)算器12.1.3

程序?qū)嵗汉?jiǎn)單計(jì)算器程序分析:特別要注意程序的末尾。要運(yùn)行應(yīng)用程序,我們創(chuàng)建一個(gè)Calculator類的實(shí)例,然后調(diào)用它的run方法。計(jì)算器示例利用Button對(duì)象列表來簡(jiǎn)化代碼。在這個(gè)例子中,將類似對(duì)象的集合作為列表就是為了編程的方便,因?yàn)榘粹o列表的內(nèi)容從未改變。如果集合在程序執(zhí)行期間動(dòng)態(tài)變化,列表(或其他集合類型)就變得至關(guān)重要。請(qǐng)記錄:錄入并調(diào)試運(yùn)行本程序?qū)嵗?。如果不能順利完成,?qǐng)分析可能的原因是什么?12.2并行處理機(jī)制程序?qū)嵗弘娪霸嘿u票程序?qū)嵗赫軐W(xué)家用餐12.2

并行處理機(jī)制根據(jù)摩爾定律,芯片可容納的半導(dǎo)體數(shù)目每隔24個(gè)月便增加一倍,效能表現(xiàn)也隨之提高,但因?yàn)槿缃窦呻娐返闹圃炷芰σ言絹碓浇咏雽?dǎo)體的物理極限,密度增長(zhǎng)的趨勢(shì)已逐漸放緩,而且還有散熱這個(gè)大問題,因此,處理器工作頻率的提升幅度已經(jīng)不如以往,而是朝向多核心的方向邁進(jìn),在同一顆處理器芯片里,塞進(jìn)多個(gè)處理單元。有別于單核心的時(shí)代,軟件程序若不能善加利用,那么CPU中的多個(gè)處理器(核)就不能發(fā)揮其效用。Python支持許多形式的并行處理,如執(zhí)行線程與進(jìn)程,分別由模塊threading與模塊multiprocessing代表,后來還提供更高階的界面。下面,我們以電影院賣票與哲學(xué)家用餐這兩個(gè)經(jīng)典的例子,來示范解決執(zhí)行線程的問題。因?yàn)镻ython有著解釋器全局鎖的限制,執(zhí)行線程的作法較適合用于受限I/O的情況,不適合用于需要CPU大量計(jì)算的情況。12.2.1程序?qū)嵗弘娪霸嘿u票有家電影院,要賣100張票,有10個(gè)售票員(窗口)。如果把票事先平均分配給各售票員,若某窗口的售票員的動(dòng)作較快,迅速把賣完了,接下來該售票員就只能閑著了。因此,我們采取這樣的辦法:把電影票放在一個(gè)地方,讓所有售票員都能取得,取一張賣一張。在分析各種工程問題時(shí),如果需要模擬某種不可預(yù)期且不規(guī)則的現(xiàn)象,可以利用隨機(jī)數(shù)(又稱亂數(shù))方式產(chǎn)生近似數(shù)據(jù)。經(jīng)由隨機(jī)數(shù)產(chǎn)生的數(shù)據(jù)每一次的值皆不相同(因?yàn)槲覀円笃渚哂胁豢深A(yù)期且不規(guī)則的特性),它是由數(shù)學(xué)理論推導(dǎo)出的方程式來計(jì)算??梢詫㈦S機(jī)數(shù)依其統(tǒng)計(jì)分布特性分為:均勻隨機(jī)數(shù),常態(tài)隨機(jī)數(shù)。均勻隨機(jī)數(shù)是指其值平均的分布于一區(qū)間,而常態(tài)隨機(jī)數(shù)的值則是呈現(xiàn)高斯分布,形狀像一個(gè)中間高二頭低的山丘。12.2.1程序?qū)嵗弘娪霸嘿u票下面的程序代碼使用模塊threading,主程序會(huì)產(chǎn)生10個(gè)執(zhí)行線程代表10個(gè)售票員,存取代表電影票這個(gè)共同的對(duì)象。使用隨機(jī)數(shù)來模擬售票所需時(shí)間,每賣出一張都會(huì)輸出信息,最后輸出每個(gè)售票員賣出的總張數(shù)?!境绦虼a12-3】電影院賣票。#ticket.pyimportrandomimporttimefromthreadingimportThread,Locknum_agents=10 #10個(gè)售票員num_tickets=[100] #共100張票,把1到100視為每張票的編號(hào)12.2.1程序?qū)嵗弘娪霸嘿u票#售票函數(shù),agent_id是售票員代號(hào),nt是電影票張數(shù),lock是鎖defsell_tickets(agent_id,nt,lock):total=0 #記錄此售票員共賣出幾張

whileTrue: #不斷賣票,直到賣光

withlock: #若取得鎖,才能去拿電影票來賣

ifnt[0]>0: #若還有電影票,賣出,并輸出信息

print('Agent%dsellsaticketNo.%d'%(agent_id,nt[0]))nt[0]-=1 #賣出后,電影票少了一張

total+=1 #記錄此售票員多賣出一張

elifnt[0]==0: #已全部賣光,跳出while循環(huán)

break;12.2.1程序?qū)嵗弘娪霸嘿u票#下面讓此執(zhí)行線程隨機(jī)數(shù)沉睡一段時(shí)間

#time.sleep(random.randrange(1,3))time.sleep(random.random()*(1+agent_id/2))print('Agent%ddone.Totallysells%dtickets'%(agent_id,total))#產(chǎn)生10個(gè)執(zhí)行線程,并調(diào)用Lock()產(chǎn)生鎖,因?yàn)橐嫒⊥粋€(gè)對(duì)象foriinrange(num_agents):t=Thread(target=sell_tickets,args=(i,num_tickets,Lock()))t.start() #執(zhí)行線程開始動(dòng)作12.2.1程序?qū)嵗弘娪霸嘿u票12.2.1程序?qū)嵗弘娪霸嘿u票程序分析:執(zhí)行后,可得到如下輸出。每個(gè)人執(zhí)行的輸出情況不一定會(huì)相同,甚至每次執(zhí)行都不相同。Agent0sellsaticketNo.100 #0號(hào)售票員賣出編號(hào)100的電影票Agent1sellsaticketNo.99Agent2sellsaticketNo.98Ag

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論