版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
匯報(bào)人:WPS程序設(shè)計(jì)基礎(chǔ)第十章面向?qū)ο缶幊棠夸?1面向?qū)ο蠛?jiǎn)介02類、對(duì)象與封裝03繼承與多態(tài)04Python生態(tài)系統(tǒng)之tkinter庫目錄05小試牛刀06拓展實(shí)踐:試一試面向?qū)ο缶幊?
理解封裝、繼承與多態(tài)等概念。.理解Python對(duì)象實(shí)例化的過程。.初步掌握類的定義與使用方法。.使用tkinter庫設(shè)計(jì)簡(jiǎn)單的圖形界面。。學(xué)習(xí)目標(biāo)PART110.1面向?qū)ο蠛?jiǎn)介10.1面向?qū)ο蠛?jiǎn)介其實(shí)在前面各章使用Python庫的過程中已經(jīng)不知不覺地使用了面向?qū)ο缶幊痰囊恍└拍?。例如,Python內(nèi)置的open()函數(shù)可以返回一個(gè)代表磁盤文件的對(duì)象,這個(gè)對(duì)象有很多關(guān)于文件操作的方法、屬性,使用它來操控磁盤文件方便很多。又如,Python的字符串也因?yàn)閾碛幸惶鬃址姆椒ǘ兊悯r活起來。那么到底什么是面向?qū)ο蟮某绦蛟O(shè)計(jì)呢?它和結(jié)構(gòu)化程序設(shè)計(jì)有什么區(qū)別呢?在馮·諾依曼的計(jì)算機(jī)體系結(jié)構(gòu)下,程序的運(yùn)行本質(zhì)就是內(nèi)存中數(shù)據(jù)的演化。程序的輸入是數(shù)據(jù)的初始狀態(tài),程序的輸出是數(shù)據(jù)的結(jié)束狀態(tài),而程序的運(yùn)行過程是數(shù)據(jù)的演變過程。無論哪種程序設(shè)計(jì)理念,不過是提供了一種看待內(nèi)存中數(shù)據(jù)演變的視角。10.1面向?qū)ο蠛?jiǎn)介結(jié)構(gòu)化編程將程序運(yùn)行看成一個(gè)過程,有一個(gè)“上帝之手”在按照某種算法一步步地執(zhí)行。每執(zhí)行一步,內(nèi)存中的數(shù)據(jù)會(huì)跟著改變,直到最后一步完成,程序運(yùn)行結(jié)束,此時(shí)內(nèi)存中數(shù)據(jù)的狀態(tài)就是程序的輸出結(jié)果。在這種理念中,數(shù)據(jù)是“魚肉”,算法為“刀俎”,“上帝之手”拿著“刀俎”來修改“魚肉”。作為魚肉的數(shù)據(jù)是被動(dòng)地等待著被修改,刀俎和魚肉當(dāng)然是分開的,即構(gòu)成程序的兩個(gè)要素算法和數(shù)據(jù)是分開的。這種理念有什么問題呢?問題在于實(shí)際上并沒有“上帝之手”,真正拿著刀俎的是程序員,而程序員是人,會(huì)犯各種錯(cuò)誤。尤其當(dāng)軟件要解決的問題越來越復(fù)雜時(shí),拿著刀俎的程序員會(huì)感到越來越無力。面向?qū)ο蟪绦蛟O(shè)計(jì)就不同了,在面向?qū)ο蟮睦砟钪袛?shù)據(jù)和對(duì)數(shù)據(jù)的操作合體了,它們結(jié)合在一起形成對(duì)象。數(shù)據(jù)是對(duì)象的屬性,對(duì)數(shù)據(jù)的操作是對(duì)象的方法。數(shù)據(jù)和對(duì)數(shù)據(jù)的操作不再是魚肉和刀俎的關(guān)系,也沒有一個(gè)“上帝之手”在拿著刀俎操作魚肉。數(shù)據(jù)與對(duì)數(shù)據(jù)的操作現(xiàn)在構(gòu)成了一個(gè)有機(jī)的整體——對(duì)象。數(shù)據(jù)賦予對(duì)象血肉,方法賦予對(duì)象生命的活力。內(nèi)存中的對(duì)象就像一個(gè)個(gè)生命體,它們彼此之間可以溝通、可以交流合作,在程序員的指揮下共同完成程序運(yùn)行的任務(wù)。在這過程中不斷地有對(duì)象產(chǎn)生、消亡,恰如人類社會(huì),而程序員則好比是這場(chǎng)大戲的導(dǎo)演。10.1面向?qū)ο蠛?jiǎn)介面向?qū)ο缶幊毯徒Y(jié)構(gòu)化編程最大的不同在于面向?qū)ο缶幊谈诵曰?,使程序的結(jié)構(gòu)更接近人類社會(huì)的結(jié)構(gòu)。計(jì)算機(jī)的出現(xiàn)帶來了一個(gè)虛擬的世界,虛擬世界中的很多看似很深?yuàn)W的理論都來源于真實(shí)世界,甚至就是簡(jiǎn)單的生活常識(shí),如計(jì)算機(jī)系統(tǒng)中的緩存系統(tǒng)。面向?qū)ο罄砟钜彩翘摂M向真實(shí)學(xué)習(xí)的結(jié)果。在真實(shí)的人類社會(huì)中,不論是一個(gè)個(gè)自然人,還是由自然人組成的團(tuán)體、企業(yè)、機(jī)關(guān),所有這些形成了一個(gè)極其復(fù)雜的系統(tǒng),但這個(gè)系統(tǒng)卻能有條不紊地運(yùn)行著。人類社會(huì)是如何做到這一點(diǎn)的呢?答案是兩個(gè)字:對(duì)象。不管是自然人,還是團(tuán)體、企業(yè)、機(jī)關(guān)等,都是一個(gè)個(gè)具體的對(duì)象。每個(gè)對(duì)象都有自己的一些特征和功能。一個(gè)對(duì)象的特征決定了它自身的一些基本屬性,存儲(chǔ)了與它自身相關(guān)的一些信息;一個(gè)對(duì)象的功能則決定了它能做什么。例如,自然人有名字、身高、體重等特征,還有一些功能,也就是能力。10.1面向?qū)ο蠛?jiǎn)介例如,一個(gè)人是醫(yī)生,他就有看病的能力。每個(gè)對(duì)象調(diào)用其他對(duì)象提供的功能時(shí)不必知道該功能的實(shí)現(xiàn)細(xì)節(jié)。學(xué)生去教室上課,不必操心教室是如何安排才不會(huì)沖突的;打開教室的電燈,不必知道電來自哪個(gè)發(fā)電廠;預(yù)訂飛機(jī)票,只需向航空售票系統(tǒng)說明需求,不用操心票是如何定下來的;自動(dòng)柜員機(jī)是如何正確地出鈔的,它內(nèi)部的點(diǎn)鈔模塊是如何實(shí)現(xiàn)的,這些作為使用者都無須知道。實(shí)際上使用者只要知道柜員機(jī)的接口是什么樣子的就可以了,這里的接口就是柜員機(jī)的插卡口、出鈔口、屏幕等。從以上描述可以看出,面向?qū)ο蟮幕纠砟钍敲總€(gè)對(duì)象都把自己的實(shí)現(xiàn)細(xì)節(jié)封裝起來,只把接口展現(xiàn)給外部世界,其他對(duì)象在和這個(gè)對(duì)象交流時(shí)只需知道如何使用該對(duì)象的接口即可。這就是面向?qū)ο缶幊痰囊粋€(gè)基本理念——封裝。10.1面向?qū)ο蠛?jiǎn)介不僅如此,人類社會(huì)還為那些有著相同特征或功能的對(duì)象歸了類,如學(xué)校就是一類對(duì)象。社會(huì)上有千千萬萬所學(xué)校,每所學(xué)校都是一個(gè)具體的對(duì)象,但它們有一些共同的特征和功能,所以人們將它們歸為一類,定義為學(xué)校,這樣學(xué)校就成為一個(gè)類(class)。學(xué)校又可細(xì)分為小學(xué)、中學(xué)、大學(xué)等,即一個(gè)父類又分出子類,子類會(huì)繼承父類的特征。某某大學(xué)可以被認(rèn)為是一所(isa)大學(xué)(子類),也可以籠統(tǒng)地被認(rèn)為是一所學(xué)校(父類)。當(dāng)已知某單位是一所學(xué)校時(shí),就意味著這個(gè)單位應(yīng)該有教室、教師、學(xué)生等所有學(xué)校都有的共性的東西,但此時(shí)并不能確定這個(gè)單位一定是一所大學(xué),所以還不能說這個(gè)單位一定有系或下屬學(xué)院等大學(xué)這個(gè)子類才有的特征。10.1面向?qū)ο蠛?jiǎn)介正是因?yàn)橛辛祟?,現(xiàn)實(shí)世界才有序。雖然事物眾多,形形色色,但各屬其類。人們到一個(gè)陌生的城市也能輕車熟路地使用新城市的公交系統(tǒng),因?yàn)楣幌到y(tǒng)就是一個(gè)類,這個(gè)類有什么樣的特征和功能是比較明確的。各城市的公交系統(tǒng)不過是這個(gè)類的一個(gè)個(gè)具體的對(duì)象而已。人們通過自己城市的公交系統(tǒng)認(rèn)識(shí)的不僅僅是一個(gè)公交系統(tǒng),而是一類對(duì)象。當(dāng)然,不同城市的公交系統(tǒng)在具體實(shí)現(xiàn)一些功能時(shí)可以有自己的特色。例如,票價(jià)問題,小的城市可以無人售票,不論路途遠(yuǎn)近,一律一元;大的城市可以按路途遠(yuǎn)近分段制定票價(jià),同樣的收費(fèi)行為卻有不同的實(shí)現(xiàn)細(xì)節(jié)。又如,自然界中存在大量的動(dòng)物(父類),動(dòng)物又分為爬行動(dòng)物、哺乳動(dòng)物、兩棲動(dòng)物等(子類)。一個(gè)動(dòng)物應(yīng)該有一種能力,那就是移動(dòng)。但不同子類的動(dòng)物在實(shí)現(xiàn)父類的這種能力時(shí),實(shí)現(xiàn)細(xì)節(jié)又是多樣的,有的爬,有的游,有的奔跑,有的飛翔。父類定義的同一種行為子類卻有不同的實(shí)現(xiàn)方式,這種現(xiàn)象在面向?qū)ο缶幊讨蟹Q為多態(tài)。10.1面向?qū)ο蠛?jiǎn)介總結(jié)下來,面向?qū)ο缶幊虖默F(xiàn)實(shí)世界汲取了3個(gè)主要思想:封裝、繼承和多態(tài),并將這些思想搬到了計(jì)算機(jī)程序的虛擬世界中。當(dāng)一個(gè)程序沒有運(yùn)行時(shí),它就是躺在磁盤文件里的一堆代碼。一旦程序運(yùn)行起來,就會(huì)在內(nèi)存中生成眾多的變量。程序要達(dá)成目標(biāo),就要對(duì)這些變量中的數(shù)據(jù)做一系列的操作。面向?qū)ο缶幊滩贿^是以對(duì)象的視角來看待內(nèi)存中的變量而已。也就是說,如果將一個(gè)程序的內(nèi)存空間看成一個(gè)虛擬社會(huì),那么程序運(yùn)行時(shí)的眾多變量好比虛擬社會(huì)中的一個(gè)個(gè)對(duì)象。每個(gè)對(duì)象(變量)都有其自身的一些特征(屬性)和功能(方法)。虛擬社會(huì)中的眾多對(duì)象同樣也被劃分成不同的類型,這就是變量的數(shù)據(jù)類型。這些對(duì)象中有比較簡(jiǎn)單的,如數(shù)值型、字符串、列表、字典等,好比現(xiàn)實(shí)世界中的自然人對(duì)象;也有比較復(fù)雜的,它們的類型是程序員根據(jù)需要定義的類,好比現(xiàn)實(shí)世界中的學(xué)校、銀行、超市等人類創(chuàng)造的機(jī)關(guān)、單位。在面向?qū)ο缶幊讨校兞坎辉賰H僅是存放待處理數(shù)據(jù)的容器,而是一個(gè)個(gè)活生生的對(duì)象,不僅有數(shù)據(jù)屬性還有能對(duì)數(shù)據(jù)進(jìn)行操作的方法。這些方法、屬性使這些變量有了生命,可以和其他變量交互,每個(gè)變量可以為程序中的其他變量服務(wù),而且調(diào)用這種服務(wù)無須知道服務(wù)實(shí)現(xiàn)的細(xì)節(jié)。隨著程序的運(yùn)行,內(nèi)存空間中的這些變量生生滅滅,當(dāng)程序退出時(shí),這個(gè)虛擬的社會(huì)就消亡了。表10.1列出了現(xiàn)實(shí)世界和虛擬世界的對(duì)照關(guān)系。10.1面向?qū)ο蠛?jiǎn)介表10.1現(xiàn)實(shí)世界和虛擬世界的對(duì)照關(guān)系現(xiàn)實(shí)世界虛擬世界(內(nèi)存空間)千千萬萬的事物千千萬萬的變量事物有類別(動(dòng)物、植物、微生物等)變量有類型(數(shù)值、字符串、列表、自定義的類等)同一類別的事物有共同的特點(diǎn)同一類型的變量有共同的特點(diǎn)除了自然界本就存在的事物,人類還發(fā)明、定義了很多事物,如學(xué)校,醫(yī)院、各種交通工具等眾多的人類社會(huì)的對(duì)象除了程序語言本就定義好的一些變量類型,程序員還可以根據(jù)具
體的需要發(fā)明、定義一些特別的變量類型。程序員可以先定義一個(gè)類,在定義中說明這個(gè)類有什么樣的特征
和功能;然后根據(jù)這個(gè)類去創(chuàng)建一個(gè)個(gè)具體的對(duì)象供程序使用PART210.2類、對(duì)象與封裝
10.2.1定義一個(gè)類如前所述,類定義了一個(gè)新的數(shù)據(jù)類型,將數(shù)據(jù)及相關(guān)操作封裝在了類的定義中,根據(jù)類生成的具體對(duì)象都擁有類定義所規(guī)定的屬性特征和能力。在Python中使用class關(guān)鍵字定義類,類名一般使用首字母大寫的形式,類名之后跟一個(gè)冒號(hào),之后是實(shí)現(xiàn)類的內(nèi)部細(xì)節(jié)的代碼。下面的代碼10.1定義了一個(gè)Animal類。代碼10.1使用class關(guān)鍵字定義類classAnimal:definit(self,name,weight):=nameself.weight=weightdefmove(self):print(f"我是{},我真的在動(dòng),不騙你。")a1=Animal('老鷹',10)#由Animal類生成具體的對(duì)象10.2.1定義一個(gè)類代碼10.1使用class關(guān)鍵字定義類a2=Animal('老虎',280)print(f'a1是{},體重{a1.weight}千克。')print(f'a2是{},體重{a2.weight}千克。')a1.move()Animal.move(a2)10.2.1定義一個(gè)類從代碼10.1可以看出,類的定義就是要闡明這類事物所擁有的屬性和方法(能力)。屬性一般需要說明初始值,這是通過特殊的__init__()方法實(shí)現(xiàn)的。類的方法其實(shí)就是定義在類中的函數(shù),同樣需要有參數(shù)、函數(shù)體等。特別一點(diǎn)的是,Python類的方法都有一個(gè)特殊的參數(shù)self,而且是方法的第一個(gè)參數(shù)。self參數(shù)指由這個(gè)類生成的對(duì)象自己,要理解這一點(diǎn)需要了解對(duì)象的創(chuàng)建過程。下面來看由類生成具體對(duì)象的過程細(xì)節(jié)。10.2.2對(duì)象實(shí)例化過程以代碼10.1為例,定義好Animal類之后,就可以使用a1=Animal('老鷹',10)這種形式去生成一個(gè)具體的Animal對(duì)象并保存在a1變量中。在這個(gè)語句背后其實(shí)有一個(gè)比較復(fù)雜的過程。Python為每一個(gè)類指定兩個(gè)特殊的方法,一個(gè)是__new__(),在代碼10.1中沒有出現(xiàn)。另一個(gè)是代碼10.1中出現(xiàn)的__init__()方法。即使在定義類時(shí)沒有寫出這兩個(gè)方法,它們?cè)陬愔幸彩谴嬖诘?。這兩個(gè)方法的名稱比較古怪,前后各有兩個(gè)下畫線。Python語言有很多類似這種名稱的方法,它們都有特殊用途,因此不建議程序員將自己代碼中的方法如此命名。這兩個(gè)方法負(fù)責(zé)對(duì)象的誕生,具體來說,__new__()方法負(fù)責(zé)從無到有將一個(gè)對(duì)象創(chuàng)建,因此__new__()方法不需要self參數(shù),因?yàn)閟elf參數(shù)指代對(duì)象本身,而在調(diào)用__new__()方法時(shí),對(duì)象還沒有誕生,要等__new__()執(zhí)行完畢后對(duì)象才會(huì)誕生。方法__new__()執(zhí)行完畢后會(huì)緊跟著執(zhí)行類的__init__()方法,通過執(zhí)行__init__()方法中的代碼來對(duì)剛剛誕生的對(duì)象的屬性進(jìn)行初始化。因此每一個(gè)對(duì)象的實(shí)例化過程都要執(zhí)行類的兩個(gè)方法:__new__()和__init__()。10.2.2對(duì)象實(shí)例化過程在代碼10.1中,雖然沒有書寫__new__()方法,但當(dāng)程序執(zhí)行a1=Animal('老鷹',10)時(shí),仍然會(huì)調(diào)用Animal類的__new__()方法造出一個(gè)具體的對(duì)象,之后就會(huì)調(diào)用__init__()方法初始化這個(gè)對(duì)象。Animal類的__init__()方法規(guī)定每一個(gè)Animal對(duì)象應(yīng)該有兩個(gè)屬性,初始化對(duì)象時(shí)會(huì)接收兩個(gè)參數(shù)值name、weight,然后將它們賦給對(duì)象本身的屬性name和weight。為了將上述這個(gè)過程描述清楚就要用到self參數(shù)了。在代碼=name中,雖然左右各有一個(gè)name,但這兩個(gè)name的含義截然不同。右側(cè)的name是__init__()方法收到的參數(shù)值,而左側(cè)的則是“對(duì)象的name屬性”的意思,這里self特指剛剛誕生的這個(gè)對(duì)象。因此這種寫法相當(dāng)于聲明了Animal對(duì)象都有name屬性。而且name與weight兩個(gè)屬性要在對(duì)象剛剛誕生后就立刻賦值,這就意味著在實(shí)例化Animal對(duì)象時(shí)必須寫成a1=Animal('老鷹',10)的形式,而不能寫成a1=Animal(),因?yàn)閷?shí)例化過程對(duì)應(yīng)__new__()和__init__()兩個(gè)方法,在執(zhí)行__init__()方法時(shí)需要傳遞兩個(gè)參數(shù)值。10.2.2對(duì)象實(shí)例化過程雖然從執(zhí)行__init__()方法時(shí)刻開始后面所有的類方法都需要有一個(gè)self參數(shù),但通常這個(gè)參數(shù)并不需要程序顯式地給它賦值。因?yàn)橐话闱闆r程序是使用對(duì)象名來調(diào)用對(duì)象的方法的,如a1.move(),其含義當(dāng)然是調(diào)用a1對(duì)象的move()方法,不可能是其他的對(duì)象。所以此時(shí)Python會(huì)自動(dòng)將a1傳遞給move()方法的self參數(shù)。但Python也允許直接通過類名來調(diào)用對(duì)象的方法,如代碼10.1中最后的10.2.2對(duì)象實(shí)例化過程Animal.move(a2),此時(shí)就需要顯式地為move()方法的self傳遞參數(shù)值,也就是a2,這樣move()方法中的就是a2的name,因此輸出的名稱是老虎。10.2.3訪問控制面向?qū)ο缶幊汤砟畹囊粋€(gè)基本想法是封裝,程序?qū)⒑芏嗉?xì)節(jié)封裝在類中,其中一些細(xì)節(jié)是不需要也不希望外界知道的。外界只需調(diào)用對(duì)象公開展示的一些方法、屬性即可,就像生活中使用的自動(dòng)柜員機(jī)一樣。這就要求在類的定義中能表明哪些屬性、方法是公開給外界調(diào)用的,哪些屬性、方法是類在自己內(nèi)部使用的,不希望外界知道,更不希望外界直接使用這些私有的屬性和方法。為了達(dá)到對(duì)類的屬性、方法的訪問控制,諸如Java和C#等面向?qū)ο笳Z言中有一套類似private、protect、public的訪問控制關(guān)鍵字,使用這些關(guān)鍵字可以聲明類的哪些方法是公開的(Public),可以給外部使用者調(diào)用;哪些是私有的(Private),只能類在自己內(nèi)部使用。但Python沒有這幾個(gè)關(guān)鍵字,而是通過給方法、屬性的名稱前加一個(gè)或兩個(gè)下畫線的方式來暗示這些方法或?qū)傩允撬接械模瑳]有特殊理由不要在類的外界直接訪問它。10.2.3訪問控制下面的代碼10.2定義了一個(gè)歷史名人類,將有關(guān)的姓名、年份及備注等信息封裝到這個(gè)類中。其中,年份屬性以1個(gè)下畫線開頭,而備注屬性以2個(gè)下畫線開頭,以此暗示這兩個(gè)屬性不希望在類的外部直接訪問,類提供了專門訪問這兩個(gè)屬性的方法,分別是get_note()方法和get_year()方法。其中,get_year()方法會(huì)根據(jù)年份數(shù)值的正負(fù)進(jìn)行一些必要的處理,這樣可以避免展示負(fù)數(shù)年份。這也是不希望外界直接使用年份屬性的一個(gè)原因。10.2.3訪問控制代碼10.2使用下畫線暗示私有屬性classHistoryPerson():definit(self,name,year,note=""):=nameself._year=year#1個(gè)下畫線開頭self.note=note#2個(gè)下畫線開頭defget_note(self):returnself.notedefset_note(self,note):self.note=note10.2.3訪問控制代碼10.2使用下畫線暗示私有屬性defget_year(self):year_str=str(abs(self._year))ifself._year<0:year_str="公元前"+year_strreturnyear_strfamous_person=[]famous_person.append(HistoryPerson("李白",701))famous_person.append(HistoryPerson("老子",-551,"*"))forpersoninfamous_person:print(,end=":")ifperson.get_note()=="*":print(f"生卒年份不詳,大致活躍于{person.get_year()}年前后")else:10.2.3訪問控制代碼10.2使用下畫線暗示私有屬性print(f"生于{person.get_year()}年")lao=famous_person[1]print(lao._year)print(lao.note)#error10.2.3訪問控制代碼10.2中部的for循環(huán)中規(guī)中矩地通過get_note()方法和get_year()方法訪問了對(duì)象的相應(yīng)屬性,但最后兩行代碼違背了下畫線的暗示,嘗試直接訪問年份與備注兩個(gè)屬性。根據(jù)實(shí)際運(yùn)行的結(jié)果可知,1個(gè)下畫線開頭的年份屬性在類的外部直接訪問也是可以的,但訪問2個(gè)下畫線開頭的備注屬性卻會(huì)報(bào)錯(cuò)。這就是以1個(gè)下畫線和2個(gè)下畫線作為暗示的區(qū)別,Python解釋器不對(duì)1個(gè)下畫線開頭的屬性做任何特殊處理,這個(gè)下畫線是只給暗示的。但對(duì)于以2個(gè)下畫線開頭的屬性,Python解釋器會(huì)做一些控制,雖然這個(gè)控制其實(shí)很容易繞過去??傮w來說,Python對(duì)于屬性、方法的訪問控制沒有強(qiáng)制要求,而是交給程序員自行決定。PART310.3繼承與多態(tài)10.3繼承與多態(tài)面向?qū)ο蟮牧硗鈨蓚€(gè)理念是繼承與多態(tài)。它們都可以有效地消除代碼中重復(fù)的部分,將相同、類似的代碼塊提取出來放到更高一層的父類中去。先來看看繼承,它可以定義一種“isa”關(guān)系。10.3.1繼承的基本形式Python中所有的類都有一個(gè)共同的祖先:object類。注意這是一個(gè)類,類名為object。Python中所有的類都是從object類派生而來的。當(dāng)一個(gè)類在定義時(shí)沒有明確指明自己的父類時(shí)(如代碼10.1中的Animal類),其父類就是object類。那么如何在定義類時(shí)明確指明其父類呢?代碼10.3在Animal類存在的情況下定義了幾個(gè)更細(xì)分的子類。10.3.1繼承的基本形式代碼10.3類的繼承#父類classAnimal:definit(self,name,weight):=nameself.weight=weightdefmove(self):print(f"我是{},我真的在動(dòng),不騙你。")#子類classFish(Animal):pass#雖然只有一個(gè)pass,但Fish類繼承了Animal類的屬性與方法10.3.1繼承的基本形式代碼10.3類的繼承classBird(Animal):defsing(self):print("我是一只小小鳥,想要飛卻飛不高。")defmove(self):print(f"我是{},一只鳥,我可以振翅高飛。")classSnake(Animal):defmove(self):super().move()print("雖然我沒有腿,但我爬的速度并不慢。")10.3.1繼承的基本形式代碼10.3類的繼承classPerson(Animal):definit(self,name,weight,gender):super().init(name,weight)self.gender=genderdefspeak(self,message):print(message)defmove(self,veicle="高鐵"):print(f"人坐上{veicle}就快多了。")10.3.1繼承的基本形式代碼10.3類的繼承#實(shí)例化子類animals=[]animals.append(Fish('小丑魚',5))animals.append(Bird('小小鳥',0.5))animals.append(Snake('響尾蛇',4))animals.append(Person('三毛',48,'男'))10.3.1繼承的基本形式代碼10.3類的繼承foraniinanimals:ifisinstance(ani,Person):ani.speak('你好,我是一個(gè)人。')elifisinstance(ani,Bird):ani.sing()ani.move()print(f"我是Animal類的實(shí)例嗎:{isinstance(ani,Animal)}")print()代碼10.3的運(yùn)行結(jié)果如下。我是小丑魚,我真的在動(dòng),不騙你。我是Animal類的實(shí)例嗎:True我是一只小小鳥,想要飛卻飛不高。我是小小鳥,一只鳥,我可以振翅高飛。我是Animal類的實(shí)例嗎:True我是響尾蛇,我真的在動(dòng),不騙你。10.3.1繼承的基本形式代碼10.3的運(yùn)行結(jié)果如下。我是小丑魚,我真的在動(dòng),不騙你。我是Animal類的實(shí)例嗎:True我是一只小小鳥,想要飛卻飛不高。我是小小鳥,一只鳥,我可以振翅高飛。我是Animal類的實(shí)例嗎:True我是響尾蛇,我真的在動(dòng),不騙你。雖然我沒有腿,但我爬的速度并不慢。我是Animal類的實(shí)例嗎:True你好,我是一個(gè)人。人坐上高鐵就快多了。我是Animal類的實(shí)例嗎:True10.3.1繼承的基本形式
上述代碼中,從Fish到Person這幾個(gè)類都由Animal類派生而來,都是Animal類的子類,都繼承了Animal類的特征。第一個(gè)子類Fish雖然自身內(nèi)部什么都沒有定義,但并不意味著Fish類沒有屬性方法。它從Animal類繼承了name和weight屬性及move()方法,所以實(shí)例化Fish類時(shí)仍然要提供name和weight兩個(gè)參數(shù)值。代碼使用了isinstance()函數(shù)來判斷一個(gè)變量保存的對(duì)象是否為某個(gè)類的實(shí)例。對(duì)于animals列表中的所有對(duì)象來說,它們都是Animal類的實(shí)例,不過只有animal[1]是Bird類的對(duì)象,擁有sing()方法;只有animal[3]是Person類的對(duì)象,擁有speak()方法。10.3.2方法的覆蓋
仔細(xì)觀察代碼10.3,可以看出各子類在繼承Animal類時(shí)表現(xiàn)各異。有的直接照搬父類,如Fish類。有的則對(duì)父類進(jìn)行擴(kuò)充,如Bird類和Person類都擁有父類沒有的方法(Bird類的sing()方法、Person類的speak()方法)。這在子類中是很常見的,子類既然是在父類的基礎(chǔ)上派生出來的,往往具有一些父類不具備的行為特征。除此之外,子類還可以覆蓋(override)父類的方法,如Bird、Snake、Person這3個(gè)子類都用自己的move()方法覆蓋了父類Animal的move()方法。因?yàn)樽宇悓?duì)事物的描述更加精細(xì),所以對(duì)父類的某個(gè)行為往往會(huì)有更加精準(zhǔn)的表述,這種情形下子類需要對(duì)從父類繼承來的方法重新進(jìn)行定義,使用自己特有的版本來覆蓋父類的方法。因此在Person類的__init__()方法中也用到了super()方法來調(diào)用父類。10.3.2方法的覆蓋
從代碼10.3中可以看到,Bird類的move()方法對(duì)運(yùn)動(dòng)的方式描述更為精準(zhǔn),而Person類的move()方法則多了一個(gè)參數(shù)。這兩個(gè)子類的move()方法都是完全拋棄父類move()方法的細(xì)節(jié)而重新定義的,但Snake類的move()方法則不同,它保留了父類move()方法的實(shí)現(xiàn)細(xì)節(jié),希望在父類的move()方法上進(jìn)行改良,加上自己需要的額外的代碼。這就意味著在Snake類的move()方法中要調(diào)用父類的move()方法,此時(shí)就要使用super()這個(gè)特別的方法了。在一個(gè)類中使用super()方法意味著呼喚這個(gè)類的父類,所以Snake類的move()方法先輸出父類的move()方法的結(jié)果,然后輸出“雖然我沒有腿,但我爬的速度并不慢?!?0.3.2方法的覆蓋
對(duì)父類方法的覆蓋并不只限于普通方法,即便是__init__()這樣的特殊方法也可以被覆蓋。例如,Person類除了name和weight屬性,還有一個(gè)gender(性別)屬性,因此在實(shí)例化Person類對(duì)象時(shí),就要對(duì)3個(gè)屬性進(jìn)行初始化,所以Person類覆蓋了父類的__init__()方法。但這個(gè)覆蓋并不是完全重打鼓另開張,而是先調(diào)用父類的__init__()方法對(duì)name和weight兩個(gè)屬性初始化,然后完成自己特有屬性gender的初始化,因此在Person類的__init__()方法中也用到了super()方法來調(diào)用父類。10.3.3多態(tài)和鴨子類型父類的同一個(gè)行為在不同的子類中有不同的表現(xiàn)形式,這在面向?qū)ο笾斜环Q為多態(tài)(polymorphism),如Animal類的move()方法在多個(gè)子類中表現(xiàn)各異。多態(tài)是面向?qū)ο笏枷胫蟹浅?岬囊粋€(gè)想法,它讓程序既可以表現(xiàn)出各子類的獨(dú)特特點(diǎn),又可以在一個(gè)更高的層次上統(tǒng)領(lǐng)這些子類。想象一下如果沒有Animal類的move()方法,而是直接在Bird、Fish、Person等類中各自添加fly()方法、swim()方法和walk()方法,這樣是可以表現(xiàn)這些類各自的運(yùn)動(dòng)細(xì)節(jié),但這些類彼此就沒有什么關(guān)系了,程序無法用一個(gè)統(tǒng)一的視角來看待它們。當(dāng)程序的某個(gè)輸入只是需要一個(gè)擁有運(yùn)動(dòng)能力的對(duì)象時(shí),程序無法說清這個(gè)要求。10.3.3多態(tài)和鴨子類型如果說程序需要一個(gè)擁有fly()方法的對(duì)象,那就把除Bird對(duì)象外的其他擁有運(yùn)動(dòng)能力的對(duì)象都排除了。同樣也不能說需要一個(gè)擁有swim()方法的對(duì)象,實(shí)際上這種情況下說不清楚,因?yàn)闆]有一個(gè)更高層次的統(tǒng)一視角。而如果有了擁有move()能力的Animal類,程序只需要求輸入的對(duì)象是Animal類的對(duì)象即可,Bird、Fish、Person等子類對(duì)象都是合法的輸入對(duì)象,至于當(dāng)下輸入的Animal對(duì)象運(yùn)動(dòng)時(shí)是飛、是游、是走就無所謂了。當(dāng)然如果只有Animal類的move()方法也不行,這樣太籠統(tǒng),無法體現(xiàn)各不同子類對(duì)象的運(yùn)動(dòng)特點(diǎn)。因此多態(tài)才很酷。利用多態(tài)可以說明程序需要一個(gè)擁有move()能力的Animal對(duì)象,但具體傳入數(shù)據(jù)時(shí),可以傳入Bird對(duì)象,也可以傳入Fish對(duì)象,因?yàn)檫@些對(duì)象都是Animal類對(duì)象,都擁有move()能力,但它們又都保持了各自獨(dú)特的move()方法細(xì)節(jié)。10.3.3多態(tài)和鴨子類型多態(tài)是面向?qū)ο笾泻苤匾囊粋€(gè)概念,Python在這一點(diǎn)上走得更遠(yuǎn),提出了一個(gè)更有意思的概念:鴨子類型(DuckTyping)。正像有句話所說:“當(dāng)一只鳥走起來像鴨子、游起泳來像鴨子、叫起來也像鴨子時(shí),那么這只鳥就可以被稱為鴨子。”言外之意鴨子類型關(guān)注的不是對(duì)象到底是什么類型的,而是對(duì)象是否擁有程序需要的能力。一只鳥是不是鴨子并不重要,重要的是它能否像鴨子一樣地走、像鴨子一樣地叫、像鴨子一樣地游泳。因?yàn)檎f到底,程序需要的其實(shí)并不一定是鴨子,而是鴨子的那些能力。如果一只鳥完美地?fù)碛续喿拥倪@些能力,即便它不是鴨子又有什么問題呢。代碼10.4中的Car類并不是Animal類的子類,但它擁有一個(gè)move()方法,因此當(dāng)程序需要一個(gè)能move()的對(duì)象時(shí),一個(gè)Car類對(duì)象完全可以像Animal類的對(duì)象一樣勝任。這比需要繼承關(guān)系的多態(tài)更進(jìn)一步,更加靈活。10.3.3多態(tài)和鴨子類型代碼10.4多態(tài)的延伸:鴨子類型classAnimal:definit(self,name,weight):=nameself.weight=weightdefmove(self):print(f"我是{},我真的在動(dòng),不騙你。")classFish(Animal):pass10.3.3多態(tài)和鴨子類型代碼10.4多態(tài)的延伸:鴨子類型classCar:defmove(self):print("我是一輛轎車,我不是動(dòng)物,但我能動(dòng)。")classStone:passdefis_movable_obj(obj):try:obj.move()except:print("這不是一個(gè)能動(dòng)的東西。")10.3.3多態(tài)和鴨子類型代碼10.4多態(tài)的延伸:鴨子類型things=[]things.append(Fish('小丑魚',5))things.append(Car())things.append(Stone())forobjinthings:is_movable_obj(obj)10.3.3多態(tài)和鴨子類型代碼10.4多態(tài)的延伸:鴨子類型things=[]things.append(Fish('小丑魚',5))things.append(Car())things.append(Stone())forobjinthings:is_movable_obj(obj)PART410.4Python生態(tài)系統(tǒng)之tkinter庫10.4Python生態(tài)系統(tǒng)之tkinter庫前面各章的程序除小海龜繪圖外,大多沒有圖形用戶界面(GraphicUserInterface,GUI)。雖然GUI并不是程序必須具備的,但很多普通用戶還是習(xí)慣使用它,因此本節(jié)就來介紹一個(gè)可以進(jìn)行GUI設(shè)計(jì)的庫。在Python生態(tài)系統(tǒng)中可以進(jìn)行GUI設(shè)計(jì)的工具包很多,如tkinter、PyQt、wxPython、kivy、enaml等。其中,tkinter的功能雖然不是最強(qiáng)大的,但它是標(biāo)準(zhǔn)庫,無須額外安裝,而且容易理解、易于上手,不失為一個(gè)好的起點(diǎn)。10.4.1初識(shí)tkintertkinter庫將GUI看成一個(gè)樹狀結(jié)構(gòu)。首先有一個(gè)窗體作為根,位于窗體上的一系列的零部件(標(biāo)簽、按鈕、文本框、菜單等)都是根窗體的樹枝。利用tkinter工具包設(shè)計(jì)GUI的過程就是用代碼描述清楚各窗體零部件屬于哪個(gè)父窗體、位置如何的過程。tkinter庫采用面向?qū)ο罄砟钤O(shè)計(jì),提供了一系列的窗體零部件類,根據(jù)這些類可以很方便地生產(chǎn)出標(biāo)簽、文本框、按鈕等GUI所需的零部件。在生產(chǎn)這些零部件的同時(shí)可以指定它們的父窗體及零部件自身的一些特征,之后使用某種布局方式說明零部件在父窗體中的位置。tkinter提供了多種布局方式,其中place方式將父窗體看成坐標(biāo)原點(diǎn)在左上角的一個(gè)平面坐標(biāo)系,要說明各零部件位于窗體的位置只需指出零部件的左上角坐標(biāo)位置即可;而grid方式則將父窗體假想成一個(gè)有若干行列的表格,然后指出每個(gè)零部件位于第幾行、第幾列的單元格即可。10.4.1初識(shí)tkinter各種窗體零部件中有的只具有展示功能,信息的傳遞是單向的。但類似文本框或按鈕之類的零部件是可以接收用戶的操作的,如輸入文本、鼠標(biāo)單擊等,這類窗體零部件的信息傳遞是雙向的,對(duì)于這些零部件還會(huì)有輔助其工作的一些其他窗體要素。為了說明這些細(xì)節(jié),下面以常見的登錄界面為例,演示如何使用place布局方式打造一個(gè)如圖10.1所示的界面。在這個(gè)登錄界面中,主窗體尺寸比較小,而且不希望用戶調(diào)整窗口尺寸,所以窗體右上角的【最大化】按鈕為不可用狀態(tài)。窗體上有兩個(gè)標(biāo)簽Label用來展示說明性文字“用戶”“密碼”,還有兩個(gè)文本框Entry,兩個(gè)按鈕Button。單擊【登錄】按鈕,程序會(huì)檢驗(yàn)用戶輸入的用戶名和密碼是否正確,根據(jù)正確與否打開一個(gè)對(duì)話框給用戶反饋。單擊【重置】按鈕后,會(huì)將當(dāng)前文本框中輸入的內(nèi)容清空。接下來對(duì)程序的分析分成兩大部分,首先是窗體和負(fù)責(zé)靜態(tài)信息展示的標(biāo)簽的生成過程;之后是可以和用戶互動(dòng)的文本框、按鈕等零部件的生成過程。10.4.2生成窗體與標(biāo)簽生成登錄窗體及標(biāo)簽部件的代碼如代碼10.5所示,首先導(dǎo)入tkinter庫,因?yàn)榕袛嗤昝艽a是否正確后要用到消息對(duì)話框給用戶反饋,所以一并導(dǎo)入消息對(duì)話框模塊。接下來利用Tk類創(chuàng)建登錄界面的根窗體,并設(shè)置窗體的標(biāo)題欄文本為“請(qǐng)登錄...”,然后利用geometry()方法指明窗體的幾何特征,包括窗體的尺寸大小及在屏幕的位置。這些信息通過一個(gè)特殊格式的字符串來說明,需要指出的是寬高之間使用字母x分開,而不是乘號(hào)。最后在窗體對(duì)象的resizable()方法中聲明窗體在兩個(gè)維度上都不可以調(diào)整大小,因此右上角的【最大化】按鈕將失效。10.4.2生成窗體與標(biāo)簽接下來調(diào)用tkinter庫中的Label類生成兩個(gè)標(biāo)簽部件,分別顯示“用戶:”和“密碼:”。在生成這些界面零部件時(shí),首先要指明其所屬的父窗體,這里顯然都為root_win。另外,還有其他一些描述零部件特征的信息可以一并說明,對(duì)于標(biāo)簽部件而言,要顯示的文字內(nèi)容是必須要說明的,這可以通過text參數(shù)給出。標(biāo)簽對(duì)象誕生后還要指明其在父窗體上的位置,否則這些標(biāo)簽是不顯示的。這個(gè)例子使用place布局方式,所謂place布局方式其實(shí)就是調(diào)用零部件的place()方法,然后聲明零部件左上角在主窗體中的橫縱坐標(biāo)位置。代碼中將“用戶:”標(biāo)簽放置在(10,5)的位置,并且寬度為80像素、高度為25像素。另外一個(gè)標(biāo)簽的布局設(shè)置完全類似。10.4.2生成窗體與標(biāo)簽代碼10.5生成登錄界面的主窗體以及標(biāo)簽importtkinterimporttkinter.messageboxroot_win=tkinter.Tk()root_win.title('請(qǐng)登錄...')root_win.geometry('300x120+700+300')root_win.resizable(False,False)10.4.2生成窗體與標(biāo)簽代碼10.5生成登錄界面的主窗體以及標(biāo)簽#生成窗體#窗體標(biāo)題欄#寬300、高120,在屏幕的位置為(700,300)#不可以調(diào)整窗體的大小lbl_user=tkinter.Label(root_win,text='用戶:')lbl_user.place(x=10,y=5,width=80,height=25)lbl_pwd=tkinter.Label(root_win,text='密碼:')lbl_pwd.place(x=10,y=35,width=80,height=25)10.4.3生成文本框與按鈕文本框的設(shè)置略微復(fù)雜一些,因?yàn)槲谋究蚴恰半p向”的,不僅能展示信息,還能從用戶處獲取信息。但實(shí)際上文本框部件本身只是一層“皮”,真正的數(shù)據(jù)信息如用戶名是要保存在變量中的,因此每一個(gè)文本框一定有一個(gè)與其配套的變量。前臺(tái)的文本框和幕后的變量一起完成數(shù)據(jù)的展示或獲取,在這個(gè)過程中二者是捆綁在一起的。用戶在文本框中輸入的數(shù)據(jù)會(huì)保存在幕后的變量中,而幕后的變量值如果發(fā)生了變化,文本框中的內(nèi)容也會(huì)跟著改變。因此在描述窗體中的文本框時(shí),除文本框本身的外觀外,還要指明和文本框搭檔的幕后變量。由于幕后變量要與搭檔的文本框綁定在一起,以實(shí)現(xiàn)內(nèi)容信息的一致,因此這種變量不是一個(gè)普通的Python變量。tkinter庫提供了一個(gè)專門的類來創(chuàng)建這種幕后變量,這就是StringVar類。根據(jù)StringVar類創(chuàng)建的幕后變量都有g(shù)et()和set()方法用來讀取、修改幕后變量的值。10.4.3生成文本框與按鈕在代碼10.6中首先使用tkinter.StringVar()聲明一個(gè)變量var_user,并將其值設(shè)為空字符串。這就是要和用戶名文本框搭檔的幕后變量。接下來調(diào)用Entry類生成文本框,除指明父窗體外還要指明和其搭檔的幕后變量是誰,這是通過textvariable參數(shù)完成的。之后同樣調(diào)用文本框部件的place()方法設(shè)置其在主窗體上的位置。接下來的密碼文本框設(shè)置過程與用戶名文本框是類似的,只是多了一個(gè)show參數(shù),用來設(shè)置密碼文本框不把用戶輸入的密碼直接顯示出來,而是以*號(hào)代替。10.4.3生成文本框與按鈕界面元素還剩下兩個(gè)按鈕,單擊按鈕后要有反應(yīng)動(dòng)作,因此對(duì)于按鈕部件最重要的是要指明單擊按鈕之后執(zhí)行什么命令(command),即要為按鈕預(yù)備響應(yīng)函數(shù)。所以代碼10.6先定義了一個(gè)login()函數(shù),并在利用Button類生成【登錄】按鈕時(shí)通過command參數(shù)指明單擊按鈕后的響應(yīng)函數(shù)是login()函數(shù)。注意,command參數(shù)處只寫函數(shù)名,不需要函數(shù)的括號(hào)。在login()函數(shù)中通過和文本框配合的幕后變量的get()方法獲取文本框中用戶輸入的用戶名與密碼,然后與預(yù)存的正確值進(jìn)行比對(duì)。根據(jù)比對(duì)結(jié)果調(diào)用messagebox.showinfo()或showwarning()顯示消息對(duì)話框,這里可以設(shè)置對(duì)話框的標(biāo)題欄、要展示的消息文本等?!局刂谩堪粹o的處理是類似的,這里不再贅述。單擊【重置】按鈕后執(zhí)行reset()函數(shù),因此在reset()函數(shù)中調(diào)用兩個(gè)幕后變量的set()方法對(duì)兩個(gè)幕后變量的值進(jìn)行重置。因?yàn)檫@兩個(gè)幕后變量和界面中的兩個(gè)文本框是分別綁在一起的,所以當(dāng)它們的值改變時(shí),兩個(gè)文本框中的內(nèi)容也會(huì)隨之而變。10.4.3生成文本框與按鈕代碼的最后一行調(diào)用根窗體的mainloop()方法啟動(dòng)整個(gè)窗體,此方法運(yùn)行后,整個(gè)窗體和其上的零部件都將顯現(xiàn),直到用戶關(guān)閉窗體。代碼10.6生成登錄界面中的文本框與按鈕var_user=tkinter.StringVar()var_user.set('')entry_user=tkinter.Entry(root_win,textvariable=var_user)entry_user.place(x=100,y=5,width=170,height=25)entry_user.focus()#用戶文本框成為當(dāng)前焦點(diǎn)var_pwd=tkinter.StringVar()var_pwd.set('')entry_pwd=tkinter.Entry(root_win,show='*',textvariable=var_pwd)entry_pwd.place(x=100,y=35,width=170,height=25)10.4.3生成文本框與按鈕代碼的最后一行調(diào)用根窗體的mainloop()方法啟動(dòng)整個(gè)窗體,此方法運(yùn)行后,整個(gè)窗體和其上的零部件都將顯現(xiàn),直到用戶關(guān)閉窗體。代碼10.6生成登錄界面中的文本框與按鈕deflogin():user=var_user.get()pwd=var_pwd.get()ifuser=='admin'andpwd=='123456':tkinter.messagebox.showinfo(title='登錄成功!',message='您以管理員身份登錄。')else:tkinter.messagebox.showwarning(title='登錄失?。?,message='用戶名或密碼錯(cuò)誤。')#單擊【登錄】按鈕會(huì)執(zhí)行l(wèi)ogin()函數(shù)10.4.3生成文本框與按鈕代碼的最后一行調(diào)用根窗體的mainloop()方法啟動(dòng)整個(gè)窗體,此方法運(yùn)行后,整個(gè)窗體和其上的零部件都將顯現(xiàn),直到用戶關(guān)閉窗體。代碼10.6生成登錄界面中的文本框與按鈕btn_login=tkinter.Button(root_win,text='登錄',command=login)btn_login.place(x=30,y=75,width=110,height=25)defreset():var_user.set('')var_pwd.set('')#單擊【重置】按鈕會(huì)執(zhí)行reset()函數(shù)btn_reset=tkinter.Button(root_win,text='重置',command=reset)btn_reset.place(x=160,y=75,width=110,height=25)root_win.mainloop()PART510.5小試牛刀在第8章的小試牛刀環(huán)節(jié)有一個(gè)繪制歷史名人時(shí)間線的案例,其實(shí)未必一定是歷史人物,歷史事件、歷史發(fā)明或歷史文物都可以出現(xiàn)在時(shí)間線中。歷史人物、歷史事件、歷史發(fā)明、歷史文物等雖然各有不同的細(xì)節(jié),但都可以看成歷史時(shí)間線上的條目,擁有共同的一些特征。下面的代碼10.7利用類重構(gòu)了第8章的案例,除了歷史人物,還在時(shí)間線上添加了歷史事件。歷史人物與歷史事件都繼承了時(shí)間線條目的一些共同屬性、方法,同時(shí)又可以有各自獨(dú)特的地方,如歷史事件希望使用紅色來進(jìn)行繪制,這些都可以通過類的繼承與多態(tài)來輕松實(shí)現(xiàn)。(代碼見書235頁10.7)10.5.1使用類重構(gòu)歷史時(shí)間線案例這個(gè)案例將嘗試使用tkinter開發(fā)一個(gè)類似“打地鼠”的小游戲,界面效果如圖10.3所示。界面中有5行5列共25個(gè)按鈕,每個(gè)按鈕代表一個(gè)地鼠。界面最下方有4個(gè)標(biāo)簽,用來記錄玩家命中的地鼠數(shù)量及地鼠更換洞穴的速度。從圖10.3可以看出,整個(gè)界面的布局呈現(xiàn)明顯的表格效果,因此使用tkinter庫的grid布局方式是很方便的。在grid布局下,只需說明每個(gè)零部件位于窗體假想表格的第幾行、第幾列即可。10.5.2使用tkinter設(shè)計(jì)打地鼠游戲程序中的25個(gè)地鼠按鈕被保存在一個(gè)嵌套列表中,每個(gè)按鈕都有可用和不可用兩種狀態(tài),不可用時(shí)對(duì)單擊動(dòng)作沒有反應(yīng)。程序通過持續(xù)不斷地隨機(jī)改變某個(gè)按鈕的狀態(tài)來模擬隨機(jī)出現(xiàn)的地鼠。這個(gè)實(shí)現(xiàn)過程的關(guān)鍵點(diǎn)利用了按鈕零部件的after()方法,該方法可以在指定的時(shí)間過后去執(zhí)行某個(gè)函數(shù),正是在這個(gè)函數(shù)中讓當(dāng)前可用按鈕不可用,并再次隨機(jī)挑選下一個(gè)可用的按鈕,從而實(shí)現(xiàn)地鼠不斷出現(xiàn)的過程。具體的程序細(xì)節(jié)如代碼10.8所示。代碼分成幾個(gè)部分,在導(dǎo)入所需模塊、生成主窗體后,代碼迎來了輔助函數(shù)部分,這里定義了4個(gè)輔助函數(shù)。然后是3個(gè)全局變量,接下來就是各界面零部件的生成,先是保存在列表中的25個(gè)地鼠按鈕,然后是界面下方的4個(gè)標(biāo)簽部件。一切就緒后,程序啟動(dòng)地鼠隨機(jī)出現(xiàn)的過程。(代碼見書238頁10.8)10.5.2使用tkinter設(shè)計(jì)打地鼠游戲PART610.6拓展實(shí)踐:試一試面向?qū)ο缶幊?0.6拓展實(shí)踐:試一試面向?qū)ο缶幊毯?jiǎn)單的計(jì)算器允許使用者提供兩個(gè)數(shù)和一個(gè)運(yùn)算符,然后返回運(yùn)算結(jié)果。問題是計(jì)算器上會(huì)有比較多的運(yùn)算種類,如何處理眾多的運(yùn)算呢?當(dāng)一種新的運(yùn)算出現(xiàn)后,如何方便地?cái)U(kuò)展程序使其支持新的運(yùn)算呢?不同的運(yùn)算其處理流程是相似的,如何避免大量重復(fù)代碼呢?這些都是在設(shè)計(jì)程序時(shí)要考慮的問題。不僅要考慮解決當(dāng)下問題,還要考慮未來業(yè)務(wù)發(fā)生變化后程序是否能很好地適應(yīng)。計(jì)算器這個(gè)例子雖然很小,但也存在這些問題。接下來通過編寫一個(gè)簡(jiǎn)單的計(jì)算器程序,實(shí)際感受一下面向?qū)ο缶幊痰膬?yōu)勢(shì)。10.6.1識(shí)別對(duì)象與類在面向?qū)ο缶幊踢^程中恰當(dāng)?shù)刈R(shí)別程序需要的對(duì)象是非常重要的。在計(jì)算器這個(gè)例子中顯然計(jì)算器是一個(gè)核心對(duì)象,應(yīng)該將其定義為一個(gè)類。但計(jì)算器有很多運(yùn)算類型,加、減、乘、除等,如何處理它們呢?一種方案是定義一個(gè)計(jì)算器類,然后在其內(nèi)部定義對(duì)應(yīng)加、減、乘、除等一系列運(yùn)算的很多方法。另一種方案是定義一個(gè)父類,說明運(yùn)算的一般要素,如兩個(gè)輸入、一個(gè)輸出;然后定義很多子類,每個(gè)子類完成一種特定的運(yùn)算。顯然,第一種方案所有的程序邏輯都位于一個(gè)核心的計(jì)算器類中,隨著運(yùn)算種類的增加,程序變得越來越復(fù)雜,這個(gè)計(jì)算器類也就變得越來越龐大、臃腫,類中對(duì)應(yīng)各種運(yùn)算的方法之間會(huì)有很多代碼是類似的、重復(fù)的,而且因?yàn)槌绦虻墓δ芏紝懙搅诉@一個(gè)類中,程序的耦合度非常高。10.6.1識(shí)別對(duì)象與類而使用第二個(gè)方案,程序結(jié)構(gòu)就會(huì)清晰、均衡,因?yàn)樗蓄愋瓦\(yùn)算的一般要素都寫在父類中,各子類只需將注意力放在自己對(duì)應(yīng)的運(yùn)算類型的具體實(shí)現(xiàn)上,就會(huì)降低代碼的重復(fù)性和耦合度。即便將來支持的運(yùn)算類型越來越多,也只是從父類派生出的子類越來越多而已,程序的結(jié)構(gòu)不用改變,擴(kuò)展起來比較容易。10.6.1識(shí)別對(duì)象與類代碼10.9就是按照第二種方案實(shí)現(xiàn)的。首先定義父類AbstractCalculator,這個(gè)類定義了加、減、乘、除等眾多運(yùn)算器都應(yīng)該具有的共性特征。這個(gè)類好比Animal類,而下面具體的運(yùn)算器類就相當(dāng)于具體的動(dòng)物子類。例如,PlusCalculator類是加法運(yùn)算器類,負(fù)責(zé)實(shí)現(xiàn)加法運(yùn)算,其calculating()方法覆蓋父類的calculating()方法;MinusCalculator類是減法運(yùn)算器類,負(fù)責(zé)實(shí)現(xiàn)減法運(yùn)算,其calculating()方法也要覆蓋父類中的calculating()方法。通過這樣的設(shè)計(jì),以后程序如果想支持更多種類的運(yùn)算,只需添加相應(yīng)的子類,新添加的子類也只需覆蓋父類的calculating()方法,子類只需關(guān)心如何實(shí)現(xiàn)運(yùn)算細(xì)節(jié)即可。(代碼見書241頁10.9)10.6.2使用設(shè)計(jì)模式有了諸如PlusCalculator和MinusCalculator等具體的運(yùn)算器類之后,程序就可以根據(jù)用戶輸入的運(yùn)算符創(chuàng)建相應(yīng)的運(yùn)算器對(duì)象了,然后完成用戶的運(yùn)算要求。在生成具體的運(yùn)算器對(duì)象時(shí)可以使用簡(jiǎn)單工廠模式。模式(Pattern)是對(duì)一個(gè)重復(fù)發(fā)生的問題,以及人們總結(jié)出來的對(duì)該問題的一個(gè)較優(yōu)的解決方案的描述。例如,在工程上人們總結(jié)了很多問題的解決方案;又如,在棋類游戲中也有很多定式,什么樣的局面用什么樣的招數(shù),棋譜上都有說明。棋譜其實(shí)就是人們總結(jié)的各種模式。類似地,程序設(shè)計(jì)模式描述了程序設(shè)計(jì)過程中針對(duì)一些常見問題人們總結(jié)的比較優(yōu)良的解決方案。而簡(jiǎn)單工廠模式是關(guān)于如何創(chuàng)建所需對(duì)象的一種簡(jiǎn)單的模式。10.6.2使用設(shè)計(jì)模式在簡(jiǎn)單工廠模式中,負(fù)責(zé)生產(chǎn)具體運(yùn)算器對(duì)象的方法被定義在一個(gè)專門的工廠類中,工廠類僅作為這個(gè)方法的一個(gè)容器,但實(shí)際上調(diào)用這個(gè)方法并不需要再去實(shí)例化一個(gè)工廠類對(duì)象,那樣就太煩瑣了。因此把生產(chǎn)運(yùn)算器對(duì)象的方法通過@staticmethod修飾符聲明為靜態(tài)方法。靜態(tài)方法簡(jiǎn)單來說就是不需實(shí)例化對(duì)象,直接使用類名即可調(diào)用的方法,因此靜態(tài)方法沒有self參數(shù)。這樣工廠類就可以根據(jù)用戶提供的操作符來生成相應(yīng)的運(yùn)算器了。具體效果如代碼10.10所示。10.6.2使用設(shè)計(jì)模式代碼10.10負(fù)責(zé)生成運(yùn)算器對(duì)象的工廠類classCalculatorFactory:"""根據(jù)用戶輸入的運(yùn)算符生成真正的運(yùn)算器"""@staticmethoddefcreate_calculator(operator,op1,op2):ifoperator=='+':returnBC.PlusCalculator(op1,op2)elifoperator=='-':returnBC.MinusCalculator(op1,op2)elifoperator=='*':returnBC.MultiplyCalculator(op1,op2)elifoperator=='/':returnBC.DividCalculator(op1,op2)else:returnAbstractCalculator(op1,op2)至此,定義各種運(yùn)算功能的類都具備了,負(fù)責(zé)生成具體運(yùn)算器對(duì)象的工廠也具備了,剩下的就是程序和用戶交互的部分了。這個(gè)部分可以是命令行式的,也可以是圖形界面。代碼10.11提供了命令行方式的交互過程。10.6.2使用設(shè)計(jì)模式代碼10.11計(jì)算器程序的交互部分classMyCalculator:@staticmethoddefmain():op1=float(input('請(qǐng)輸入第一個(gè)數(shù):'))op2=float(input('請(qǐng)輸入第二個(gè)數(shù):'))operator=input('請(qǐng)輸入運(yùn)算符:')calc
溫馨提示
- 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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 芯片制造的工藝流程
- 項(xiàng)目成本效益分析
- 讀《燈光》有感15篇
- 參加軍訓(xùn)的心得體會(huì)5篇
- 江西省萬載縣株潭中學(xué)高中語文 1 荷塘月色教學(xué)實(shí)錄 新人教版必修2
- 重陽節(jié)主題活動(dòng)方案-15篇
- 2024春七年級(jí)語文下冊(cè) 第3單元 10阿長(zhǎng)與《山海經(jīng)》教學(xué)實(shí)錄 新人教版
- 北師大版八年級(jí)上冊(cè)數(shù)學(xué)期末考試試題帶答案
- 美食節(jié)活動(dòng)策劃方案合集9篇
- 2024年春八年級(jí)地理下冊(cè) 第七章 第三節(jié) 東方明珠 香港和澳門教學(xué)實(shí)錄 (新版)新人教版
- 八年級(jí)地理期末模擬卷(考試版A4)【測(cè)試范圍:晉教版八上全冊(cè)】
- 統(tǒng)編版語文2024-2025學(xué)年六年級(jí)上冊(cè)語文期末專題訓(xùn)練:字音字形(有答案)
- 2024年文化娛樂產(chǎn)業(yè)投資合同3篇
- 機(jī)器人課件模板下載
- 《肺癌病人的護(hù)理》課件
- 臨時(shí)工人勞動(dòng)合同范本(3篇)
- 江蘇省蘇州市2023-2024學(xué)年高二上學(xué)期期末學(xué)業(yè)質(zhì)量陽光指標(biāo)調(diào)研試題 物理 含答案
- 2024年安防監(jiān)控系統(tǒng)技術(shù)標(biāo)準(zhǔn)與規(guī)范
- 辦公樓外立面玻璃更換施工方案
- 出生醫(yī)學(xué)證明警示教育培訓(xùn)
- 酒店業(yè)安全管理雙重預(yù)防機(jī)制制度
評(píng)論
0/150
提交評(píng)論