




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(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類(lèi)、對(duì)象與封裝03繼承與多態(tài)04Python生態(tài)系統(tǒng)之tkinter庫(kù)目錄05小試牛刀06拓展實(shí)踐:試一試面向?qū)ο缶幊?
理解封裝、繼承與多態(tài)等概念。.理解Python對(duì)象實(shí)例化的過(guò)程。.初步掌握類(lèi)的定義與使用方法。.使用tkinter庫(kù)設(shè)計(jì)簡(jiǎn)單的圖形界面。。學(xué)習(xí)目標(biāo)PART110.1面向?qū)ο蠛?jiǎn)介10.1面向?qū)ο蠛?jiǎn)介其實(shí)在前面各章使用Python庫(kù)的過(guò)程中已經(jīng)不知不覺(jué)地使用了面向?qū)ο缶幊痰囊恍└拍睢@?,Python內(nèi)置的open()函數(shù)可以返回一個(gè)代表磁盤(pán)文件的對(duì)象,這個(gè)對(duì)象有很多關(guān)于文件操作的方法、屬性,使用它來(lái)操控磁盤(pán)文件方便很多。又如,Python的字符串也因?yàn)閾碛幸惶鬃址姆椒ǘ兊悯r活起來(lái)。那么到底什么是面向?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)行過(guò)程是數(shù)據(jù)的演變過(guò)程。無(wú)論哪種程序設(shè)計(jì)理念,不過(guò)是提供了一種看待內(nèi)存中數(shù)據(jù)演變的視角。10.1面向?qū)ο蠛?jiǎn)介結(jié)構(gòu)化編程將程序運(yùn)行看成一個(gè)過(guò)程,有一個(gè)“上帝之手”在按照某種算法一步步地執(zhí)行。每執(zhí)行一步,內(nèi)存中的數(shù)據(jù)會(huì)跟著改變,直到最后一步完成,程序運(yùn)行結(jié)束,此時(shí)內(nèi)存中數(shù)據(jù)的狀態(tài)就是程序的輸出結(jié)果。在這種理念中,數(shù)據(jù)是“魚(yú)肉”,算法為“刀俎”,“上帝之手”拿著“刀俎”來(lái)修改“魚(yú)肉”。作為魚(yú)肉的數(shù)據(jù)是被動(dòng)地等待著被修改,刀俎和魚(yú)肉當(dāng)然是分開(kāi)的,即構(gòu)成程序的兩個(gè)要素算法和數(shù)據(jù)是分開(kāi)的。這種理念有什么問(wèn)題呢?問(wèn)題在于實(shí)際上并沒(méi)有“上帝之手”,真正拿著刀俎的是程序員,而程序員是人,會(huì)犯各種錯(cuò)誤。尤其當(dāng)軟件要解決的問(wèn)題越來(lái)越復(fù)雜時(shí),拿著刀俎的程序員會(huì)感到越來(lái)越無(wú)力。面向?qū)ο蟪绦蛟O(shè)計(jì)就不同了,在面向?qū)ο蟮睦砟钪袛?shù)據(jù)和對(duì)數(shù)據(jù)的操作合體了,它們結(jié)合在一起形成對(duì)象。數(shù)據(jù)是對(duì)象的屬性,對(duì)數(shù)據(jù)的操作是對(duì)象的方法。數(shù)據(jù)和對(duì)數(shù)據(jù)的操作不再是魚(yú)肉和刀俎的關(guān)系,也沒(méi)有一個(gè)“上帝之手”在拿著刀俎操作魚(yú)肉。數(shù)據(jù)與對(duì)數(shù)據(jù)的操作現(xiàn)在構(gòu)成了一個(gè)有機(jī)的整體——對(duì)象。數(shù)據(jù)賦予對(duì)象血肉,方法賦予對(duì)象生命的活力。內(nèi)存中的對(duì)象就像一個(gè)個(gè)生命體,它們彼此之間可以溝通、可以交流合作,在程序員的指揮下共同完成程序運(yùn)行的任務(wù)。在這過(guò)程中不斷地有對(duì)象產(chǎn)生、消亡,恰如人類(lèi)社會(huì),而程序員則好比是這場(chǎng)大戲的導(dǎo)演。10.1面向?qū)ο蠛?jiǎn)介面向?qū)ο缶幊毯徒Y(jié)構(gòu)化編程最大的不同在于面向?qū)ο缶幊谈诵曰?,使程序的結(jié)構(gòu)更接近人類(lèi)社會(huì)的結(jié)構(gòu)。計(jì)算機(jī)的出現(xiàn)帶來(lái)了一個(gè)虛擬的世界,虛擬世界中的很多看似很深?yuàn)W的理論都來(lái)源于真實(shí)世界,甚至就是簡(jiǎn)單的生活常識(shí),如計(jì)算機(jī)系統(tǒng)中的緩存系統(tǒng)。面向?qū)ο罄砟钜彩翘摂M向真實(shí)學(xué)習(xí)的結(jié)果。在真實(shí)的人類(lèi)社會(huì)中,不論是一個(gè)個(gè)自然人,還是由自然人組成的團(tuán)體、企業(yè)、機(jī)關(guān),所有這些形成了一個(gè)極其復(fù)雜的系統(tǒng),但這個(gè)系統(tǒng)卻能有條不紊地運(yùn)行著。人類(lèi)社會(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ì)沖突的;打開(kāi)教室的電燈,不必知道電來(lái)自哪個(gè)發(fā)電廠;預(yù)訂飛機(jī)票,只需向航空售票系統(tǒng)說(shuō)明需求,不用操心票是如何定下來(lái)的;自動(dòng)柜員機(jī)是如何正確地出鈔的,它內(nèi)部的點(diǎn)鈔模塊是如何實(shí)現(xiàn)的,這些作為使用者都無(wú)須知道。實(shí)際上使用者只要知道柜員機(jī)的接口是什么樣子的就可以了,這里的接口就是柜員機(jī)的插卡口、出鈔口、屏幕等。從以上描述可以看出,面向?qū)ο蟮幕纠砟钍敲總€(gè)對(duì)象都把自己的實(shí)現(xiàn)細(xì)節(jié)封裝起來(lái),只把接口展現(xiàn)給外部世界,其他對(duì)象在和這個(gè)對(duì)象交流時(shí)只需知道如何使用該對(duì)象的接口即可。這就是面向?qū)ο缶幊痰囊粋€(gè)基本理念——封裝。10.1面向?qū)ο蠛?jiǎn)介不僅如此,人類(lèi)社會(huì)還為那些有著相同特征或功能的對(duì)象歸了類(lèi),如學(xué)校就是一類(lèi)對(duì)象。社會(huì)上有千千萬(wàn)萬(wàn)所學(xué)校,每所學(xué)校都是一個(gè)具體的對(duì)象,但它們有一些共同的特征和功能,所以人們將它們歸為一類(lèi),定義為學(xué)校,這樣學(xué)校就成為一個(gè)類(lèi)(class)。學(xué)校又可細(xì)分為小學(xué)、中學(xué)、大學(xué)等,即一個(gè)父類(lèi)又分出子類(lèi),子類(lèi)會(huì)繼承父類(lèi)的特征。某某大學(xué)可以被認(rèn)為是一所(isa)大學(xué)(子類(lèi)),也可以籠統(tǒng)地被認(rèn)為是一所學(xué)校(父類(lèi))。當(dāng)已知某單位是一所學(xué)校時(shí),就意味著這個(gè)單位應(yīng)該有教室、教師、學(xué)生等所有學(xué)校都有的共性的東西,但此時(shí)并不能確定這個(gè)單位一定是一所大學(xué),所以還不能說(shuō)這個(gè)單位一定有系或下屬學(xué)院等大學(xué)這個(gè)子類(lèi)才有的特征。10.1面向?qū)ο蠛?jiǎn)介正是因?yàn)橛辛祟?lèi),現(xiàn)實(shí)世界才有序。雖然事物眾多,形形色色,但各屬其類(lèi)。人們到一個(gè)陌生的城市也能輕車(chē)熟路地使用新城市的公交系統(tǒng),因?yàn)楣幌到y(tǒng)就是一個(gè)類(lèi),這個(gè)類(lèi)有什么樣的特征和功能是比較明確的。各城市的公交系統(tǒng)不過(guò)是這個(gè)類(lèi)的一個(gè)個(gè)具體的對(duì)象而已。人們通過(guò)自己城市的公交系統(tǒng)認(rèn)識(shí)的不僅僅是一個(gè)公交系統(tǒng),而是一類(lèi)對(duì)象。當(dāng)然,不同城市的公交系統(tǒng)在具體實(shí)現(xiàn)一些功能時(shí)可以有自己的特色。例如,票價(jià)問(wèn)題,小的城市可以無(wú)人售票,不論路途遠(yuǎn)近,一律一元;大的城市可以按路途遠(yuǎn)近分段制定票價(jià),同樣的收費(fèi)行為卻有不同的實(shí)現(xiàn)細(xì)節(jié)。又如,自然界中存在大量的動(dòng)物(父類(lèi)),動(dòng)物又分為爬行動(dòng)物、哺乳動(dòng)物、兩棲動(dòng)物等(子類(lèi))。一個(gè)動(dòng)物應(yīng)該有一種能力,那就是移動(dòng)。但不同子類(lèi)的動(dòng)物在實(shí)現(xiàn)父類(lèi)的這種能力時(shí),實(shí)現(xiàn)細(xì)節(jié)又是多樣的,有的爬,有的游,有的奔跑,有的飛翔。父類(lèi)定義的同一種行為子類(lèi)卻有不同的實(shí)現(xiàn)方式,這種現(xiàn)象在面向?qū)ο缶幊讨蟹Q(chēng)為多態(tài)。10.1面向?qū)ο蠛?jiǎn)介總結(jié)下來(lái),面向?qū)ο缶幊虖默F(xiàn)實(shí)世界汲取了3個(gè)主要思想:封裝、繼承和多態(tài),并將這些思想搬到了計(jì)算機(jī)程序的虛擬世界中。當(dāng)一個(gè)程序沒(méi)有運(yùn)行時(shí),它就是躺在磁盤(pán)文件里的一堆代碼。一旦程序運(yùn)行起來(lái),就會(huì)在內(nèi)存中生成眾多的變量。程序要達(dá)成目標(biāo),就要對(duì)這些變量中的數(shù)據(jù)做一系列的操作。面向?qū)ο缶幊滩贿^(guò)是以對(duì)象的視角來(lái)看待內(nèi)存中的變量而已。也就是說(shuō),如果將一個(gè)程序的內(nèi)存空間看成一個(gè)虛擬社會(huì),那么程序運(yùn)行時(shí)的眾多變量好比虛擬社會(huì)中的一個(gè)個(gè)對(duì)象。每個(gè)對(duì)象(變量)都有其自身的一些特征(屬性)和功能(方法)。虛擬社會(huì)中的眾多對(duì)象同樣也被劃分成不同的類(lèi)型,這就是變量的數(shù)據(jù)類(lèi)型。這些對(duì)象中有比較簡(jiǎn)單的,如數(shù)值型、字符串、列表、字典等,好比現(xiàn)實(shí)世界中的自然人對(duì)象;也有比較復(fù)雜的,它們的類(lèi)型是程序員根據(jù)需要定義的類(lèi),好比現(xiàn)實(shí)世界中的學(xué)校、銀行、超市等人類(lèi)創(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ú)須知道服務(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)存空間)千千萬(wàn)萬(wàn)的事物千千萬(wàn)萬(wàn)的變量事物有類(lèi)別(動(dòng)物、植物、微生物等)變量有類(lèi)型(數(shù)值、字符串、列表、自定義的類(lèi)等)同一類(lèi)別的事物有共同的特點(diǎn)同一類(lèi)型的變量有共同的特點(diǎn)除了自然界本就存在的事物,人類(lèi)還發(fā)明、定義了很多事物,如學(xué)校,醫(yī)院、各種交通工具等眾多的人類(lèi)社會(huì)的對(duì)象除了程序語(yǔ)言本就定義好的一些變量類(lèi)型,程序員還可以根據(jù)具
體的需要發(fā)明、定義一些特別的變量類(lèi)型。程序員可以先定義一個(gè)類(lèi),在定義中說(shuō)明這個(gè)類(lèi)有什么樣的特征
和功能;然后根據(jù)這個(gè)類(lèi)去創(chuàng)建一個(gè)個(gè)具體的對(duì)象供程序使用PART210.2類(lèi)、對(duì)象與封裝
10.2.1定義一個(gè)類(lèi)如前所述,類(lèi)定義了一個(gè)新的數(shù)據(jù)類(lèi)型,將數(shù)據(jù)及相關(guān)操作封裝在了類(lèi)的定義中,根據(jù)類(lèi)生成的具體對(duì)象都擁有類(lèi)定義所規(guī)定的屬性特征和能力。在Python中使用class關(guān)鍵字定義類(lèi),類(lèi)名一般使用首字母大寫(xiě)的形式,類(lèi)名之后跟一個(gè)冒號(hào),之后是實(shí)現(xiàn)類(lèi)的內(nèi)部細(xì)節(jié)的代碼。下面的代碼10.1定義了一個(gè)Animal類(lèi)。代碼10.1使用class關(guān)鍵字定義類(lèi)classAnimal:definit(self,name,weight):=nameself.weight=weightdefmove(self):print(f"我是{},我真的在動(dòng),不騙你。")a1=Animal('老鷹',10)#由Animal類(lèi)生成具體的對(duì)象10.2.1定義一個(gè)類(lèi)代碼10.1使用class關(guān)鍵字定義類(lèi)a2=Animal('老虎',280)print(f'a1是{},體重{a1.weight}千克。')print(f'a2是{},體重{a2.weight}千克。')a1.move()Animal.move(a2)10.2.1定義一個(gè)類(lèi)從代碼10.1可以看出,類(lèi)的定義就是要闡明這類(lèi)事物所擁有的屬性和方法(能力)。屬性一般需要說(shuō)明初始值,這是通過(guò)特殊的__init__()方法實(shí)現(xiàn)的。類(lèi)的方法其實(shí)就是定義在類(lèi)中的函數(shù),同樣需要有參數(shù)、函數(shù)體等。特別一點(diǎn)的是,Python類(lèi)的方法都有一個(gè)特殊的參數(shù)self,而且是方法的第一個(gè)參數(shù)。self參數(shù)指由這個(gè)類(lèi)生成的對(duì)象自己,要理解這一點(diǎn)需要了解對(duì)象的創(chuàng)建過(guò)程。下面來(lái)看由類(lèi)生成具體對(duì)象的過(guò)程細(xì)節(jié)。10.2.2對(duì)象實(shí)例化過(guò)程以代碼10.1為例,定義好Animal類(lèi)之后,就可以使用a1=Animal('老鷹',10)這種形式去生成一個(gè)具體的Animal對(duì)象并保存在a1變量中。在這個(gè)語(yǔ)句背后其實(shí)有一個(gè)比較復(fù)雜的過(guò)程。Python為每一個(gè)類(lèi)指定兩個(gè)特殊的方法,一個(gè)是__new__(),在代碼10.1中沒(méi)有出現(xiàn)。另一個(gè)是代碼10.1中出現(xiàn)的__init__()方法。即使在定義類(lèi)時(shí)沒(méi)有寫(xiě)出這兩個(gè)方法,它們?cè)陬?lèi)中也是存在的。這兩個(gè)方法的名稱(chēng)比較古怪,前后各有兩個(gè)下畫(huà)線。Python語(yǔ)言有很多類(lèi)似這種名稱(chēng)的方法,它們都有特殊用途,因此不建議程序員將自己代碼中的方法如此命名。這兩個(gè)方法負(fù)責(zé)對(duì)象的誕生,具體來(lái)說(shuō),__new__()方法負(fù)責(zé)從無(wú)到有將一個(gè)對(duì)象創(chuàng)建,因此__new__()方法不需要self參數(shù),因?yàn)閟elf參數(shù)指代對(duì)象本身,而在調(diào)用__new__()方法時(shí),對(duì)象還沒(méi)有誕生,要等__new__()執(zhí)行完畢后對(duì)象才會(huì)誕生。方法__new__()執(zhí)行完畢后會(huì)緊跟著執(zhí)行類(lèi)的__init__()方法,通過(guò)執(zhí)行__init__()方法中的代碼來(lái)對(duì)剛剛誕生的對(duì)象的屬性進(jìn)行初始化。因此每一個(gè)對(duì)象的實(shí)例化過(guò)程都要執(zhí)行類(lèi)的兩個(gè)方法:__new__()和__init__()。10.2.2對(duì)象實(shí)例化過(guò)程在代碼10.1中,雖然沒(méi)有書(shū)寫(xiě)__new__()方法,但當(dāng)程序執(zhí)行a1=Animal('老鷹',10)時(shí),仍然會(huì)調(diào)用Animal類(lèi)的__new__()方法造出一個(gè)具體的對(duì)象,之后就會(huì)調(diào)用__init__()方法初始化這個(gè)對(duì)象。Animal類(lèi)的__init__()方法規(guī)定每一個(gè)Animal對(duì)象應(yīng)該有兩個(gè)屬性,初始化對(duì)象時(shí)會(huì)接收兩個(gè)參數(shù)值name、weight,然后將它們賦給對(duì)象本身的屬性name和weight。為了將上述這個(gè)過(guò)程描述清楚就要用到self參數(shù)了。在代碼=name中,雖然左右各有一個(gè)name,但這兩個(gè)name的含義截然不同。右側(cè)的name是__init__()方法收到的參數(shù)值,而左側(cè)的則是“對(duì)象的name屬性”的意思,這里self特指剛剛誕生的這個(gè)對(duì)象。因此這種寫(xiě)法相當(dāng)于聲明了Animal對(duì)象都有name屬性。而且name與weight兩個(gè)屬性要在對(duì)象剛剛誕生后就立刻賦值,這就意味著在實(shí)例化Animal對(duì)象時(shí)必須寫(xiě)成a1=Animal('老鷹',10)的形式,而不能寫(xiě)成a1=Animal(),因?yàn)閷?shí)例化過(guò)程對(duì)應(yīng)__new__()和__init__()兩個(gè)方法,在執(zhí)行__init__()方法時(shí)需要傳遞兩個(gè)參數(shù)值。10.2.2對(duì)象實(shí)例化過(guò)程雖然從執(zhí)行__init__()方法時(shí)刻開(kāi)始后面所有的類(lèi)方法都需要有一個(gè)self參數(shù),但通常這個(gè)參數(shù)并不需要程序顯式地給它賦值。因?yàn)橐话闱闆r程序是使用對(duì)象名來(lái)調(diào)用對(duì)象的方法的,如a1.move(),其含義當(dāng)然是調(diào)用a1對(duì)象的move()方法,不可能是其他的對(duì)象。所以此時(shí)Python會(huì)自動(dòng)將a1傳遞給move()方法的self參數(shù)。但Python也允許直接通過(guò)類(lèi)名來(lái)調(diào)用對(duì)象的方法,如代碼10.1中最后的10.2.2對(duì)象實(shí)例化過(guò)程Animal.move(a2),此時(shí)就需要顯式地為move()方法的self傳遞參數(shù)值,也就是a2,這樣move()方法中的就是a2的name,因此輸出的名稱(chēng)是老虎。10.2.3訪問(wèn)控制面向?qū)ο缶幊汤砟畹囊粋€(gè)基本想法是封裝,程序?qū)⒑芏嗉?xì)節(jié)封裝在類(lèi)中,其中一些細(xì)節(jié)是不需要也不希望外界知道的。外界只需調(diào)用對(duì)象公開(kāi)展示的一些方法、屬性即可,就像生活中使用的自動(dòng)柜員機(jī)一樣。這就要求在類(lèi)的定義中能表明哪些屬性、方法是公開(kāi)給外界調(diào)用的,哪些屬性、方法是類(lèi)在自己內(nèi)部使用的,不希望外界知道,更不希望外界直接使用這些私有的屬性和方法。為了達(dá)到對(duì)類(lèi)的屬性、方法的訪問(wèn)控制,諸如Java和C#等面向?qū)ο笳Z(yǔ)言中有一套類(lèi)似private、protect、public的訪問(wèn)控制關(guān)鍵字,使用這些關(guān)鍵字可以聲明類(lèi)的哪些方法是公開(kāi)的(Public),可以給外部使用者調(diào)用;哪些是私有的(Private),只能類(lèi)在自己內(nèi)部使用。但Python沒(méi)有這幾個(gè)關(guān)鍵字,而是通過(guò)給方法、屬性的名稱(chēng)前加一個(gè)或兩個(gè)下畫(huà)線的方式來(lái)暗示這些方法或?qū)傩允撬接械模瑳](méi)有特殊理由不要在類(lèi)的外界直接訪問(wèn)它。10.2.3訪問(wèn)控制下面的代碼10.2定義了一個(gè)歷史名人類(lèi),將有關(guān)的姓名、年份及備注等信息封裝到這個(gè)類(lèi)中。其中,年份屬性以1個(gè)下畫(huà)線開(kāi)頭,而備注屬性以2個(gè)下畫(huà)線開(kāi)頭,以此暗示這兩個(gè)屬性不希望在類(lèi)的外部直接訪問(wèn),類(lèi)提供了專(zhuān)門(mén)訪問(wèn)這兩個(gè)屬性的方法,分別是get_note()方法和get_year()方法。其中,get_year()方法會(huì)根據(jù)年份數(shù)值的正負(fù)進(jìn)行一些必要的處理,這樣可以避免展示負(fù)數(shù)年份。這也是不希望外界直接使用年份屬性的一個(gè)原因。10.2.3訪問(wèn)控制代碼10.2使用下畫(huà)線暗示私有屬性classHistoryPerson():definit(self,name,year,note=""):=nameself._year=year#1個(gè)下畫(huà)線開(kāi)頭self.note=note#2個(gè)下畫(huà)線開(kāi)頭defget_note(self):returnself.notedefset_note(self,note):self.note=note10.2.3訪問(wèn)控制代碼10.2使用下畫(huà)線暗示私有屬性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訪問(wèn)控制代碼10.2使用下畫(huà)線暗示私有屬性print(f"生于{person.get_year()}年")lao=famous_person[1]print(lao._year)print(lao.note)#error10.2.3訪問(wèn)控制代碼10.2中部的for循環(huán)中規(guī)中矩地通過(guò)get_note()方法和get_year()方法訪問(wèn)了對(duì)象的相應(yīng)屬性,但最后兩行代碼違背了下畫(huà)線的暗示,嘗試直接訪問(wèn)年份與備注兩個(gè)屬性。根據(jù)實(shí)際運(yùn)行的結(jié)果可知,1個(gè)下畫(huà)線開(kāi)頭的年份屬性在類(lèi)的外部直接訪問(wèn)也是可以的,但訪問(wèn)2個(gè)下畫(huà)線開(kāi)頭的備注屬性卻會(huì)報(bào)錯(cuò)。這就是以1個(gè)下畫(huà)線和2個(gè)下畫(huà)線作為暗示的區(qū)別,Python解釋器不對(duì)1個(gè)下畫(huà)線開(kāi)頭的屬性做任何特殊處理,這個(gè)下畫(huà)線是只給暗示的。但對(duì)于以2個(gè)下畫(huà)線開(kāi)頭的屬性,Python解釋器會(huì)做一些控制,雖然這個(gè)控制其實(shí)很容易繞過(guò)去??傮w來(lái)說(shuō),Python對(duì)于屬性、方法的訪問(wèn)控制沒(méi)有強(qiáng)制要求,而是交給程序員自行決定。PART310.3繼承與多態(tài)10.3繼承與多態(tài)面向?qū)ο蟮牧硗鈨蓚€(gè)理念是繼承與多態(tài)。它們都可以有效地消除代碼中重復(fù)的部分,將相同、類(lèi)似的代碼塊提取出來(lái)放到更高一層的父類(lèi)中去。先來(lái)看看繼承,它可以定義一種“isa”關(guān)系。10.3.1繼承的基本形式Python中所有的類(lèi)都有一個(gè)共同的祖先:object類(lèi)。注意這是一個(gè)類(lèi),類(lèi)名為object。Python中所有的類(lèi)都是從object類(lèi)派生而來(lái)的。當(dāng)一個(gè)類(lèi)在定義時(shí)沒(méi)有明確指明自己的父類(lèi)時(shí)(如代碼10.1中的Animal類(lèi)),其父類(lèi)就是object類(lèi)。那么如何在定義類(lèi)時(shí)明確指明其父類(lèi)呢?代碼10.3在Animal類(lèi)存在的情況下定義了幾個(gè)更細(xì)分的子類(lèi)。10.3.1繼承的基本形式代碼10.3類(lèi)的繼承#父類(lèi)classAnimal:definit(self,name,weight):=nameself.weight=weightdefmove(self):print(f"我是{},我真的在動(dòng),不騙你。")#子類(lèi)classFish(Animal):pass#雖然只有一個(gè)pass,但Fish類(lèi)繼承了Animal類(lèi)的屬性與方法10.3.1繼承的基本形式代碼10.3類(lèi)的繼承classBird(Animal):defsing(self):print("我是一只小小鳥(niǎo),想要飛卻飛不高。")defmove(self):print(f"我是{},一只鳥(niǎo),我可以振翅高飛。")classSnake(Animal):defmove(self):super().move()print("雖然我沒(méi)有腿,但我爬的速度并不慢。")10.3.1繼承的基本形式代碼10.3類(lèi)的繼承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類(lèi)的繼承#實(shí)例化子類(lèi)animals=[]animals.append(Fish('小丑魚(yú)',5))animals.append(Bird('小小鳥(niǎo)',0.5))animals.append(Snake('響尾蛇',4))animals.append(Person('三毛',48,'男'))10.3.1繼承的基本形式代碼10.3類(lèi)的繼承foraniinanimals:ifisinstance(ani,Person):ani.speak('你好,我是一個(gè)人。')elifisinstance(ani,Bird):ani.sing()ani.move()print(f"我是Animal類(lèi)的實(shí)例嗎:{isinstance(ani,Animal)}")print()代碼10.3的運(yùn)行結(jié)果如下。我是小丑魚(yú),我真的在動(dòng),不騙你。我是Animal類(lèi)的實(shí)例嗎:True我是一只小小鳥(niǎo),想要飛卻飛不高。我是小小鳥(niǎo),一只鳥(niǎo),我可以振翅高飛。我是Animal類(lèi)的實(shí)例嗎:True我是響尾蛇,我真的在動(dòng),不騙你。10.3.1繼承的基本形式代碼10.3的運(yùn)行結(jié)果如下。我是小丑魚(yú),我真的在動(dòng),不騙你。我是Animal類(lèi)的實(shí)例嗎:True我是一只小小鳥(niǎo),想要飛卻飛不高。我是小小鳥(niǎo),一只鳥(niǎo),我可以振翅高飛。我是Animal類(lèi)的實(shí)例嗎:True我是響尾蛇,我真的在動(dòng),不騙你。雖然我沒(méi)有腿,但我爬的速度并不慢。我是Animal類(lèi)的實(shí)例嗎:True你好,我是一個(gè)人。人坐上高鐵就快多了。我是Animal類(lèi)的實(shí)例嗎:True10.3.1繼承的基本形式
上述代碼中,從Fish到Person這幾個(gè)類(lèi)都由Animal類(lèi)派生而來(lái),都是Animal類(lèi)的子類(lèi),都繼承了Animal類(lèi)的特征。第一個(gè)子類(lèi)Fish雖然自身內(nèi)部什么都沒(méi)有定義,但并不意味著Fish類(lèi)沒(méi)有屬性方法。它從Animal類(lèi)繼承了name和weight屬性及move()方法,所以實(shí)例化Fish類(lèi)時(shí)仍然要提供name和weight兩個(gè)參數(shù)值。代碼使用了isinstance()函數(shù)來(lái)判斷一個(gè)變量保存的對(duì)象是否為某個(gè)類(lèi)的實(shí)例。對(duì)于animals列表中的所有對(duì)象來(lái)說(shuō),它們都是Animal類(lèi)的實(shí)例,不過(guò)只有animal[1]是Bird類(lèi)的對(duì)象,擁有sing()方法;只有animal[3]是Person類(lèi)的對(duì)象,擁有speak()方法。10.3.2方法的覆蓋
仔細(xì)觀察代碼10.3,可以看出各子類(lèi)在繼承Animal類(lèi)時(shí)表現(xiàn)各異。有的直接照搬父類(lèi),如Fish類(lèi)。有的則對(duì)父類(lèi)進(jìn)行擴(kuò)充,如Bird類(lèi)和Person類(lèi)都擁有父類(lèi)沒(méi)有的方法(Bird類(lèi)的sing()方法、Person類(lèi)的speak()方法)。這在子類(lèi)中是很常見(jiàn)的,子類(lèi)既然是在父類(lèi)的基礎(chǔ)上派生出來(lái)的,往往具有一些父類(lèi)不具備的行為特征。除此之外,子類(lèi)還可以覆蓋(override)父類(lèi)的方法,如Bird、Snake、Person這3個(gè)子類(lèi)都用自己的move()方法覆蓋了父類(lèi)Animal的move()方法。因?yàn)樽宇?lèi)對(duì)事物的描述更加精細(xì),所以對(duì)父類(lèi)的某個(gè)行為往往會(huì)有更加精準(zhǔn)的表述,這種情形下子類(lèi)需要對(duì)從父類(lèi)繼承來(lái)的方法重新進(jìn)行定義,使用自己特有的版本來(lái)覆蓋父類(lèi)的方法。因此在Person類(lèi)的__init__()方法中也用到了super()方法來(lái)調(diào)用父類(lèi)。10.3.2方法的覆蓋
從代碼10.3中可以看到,Bird類(lèi)的move()方法對(duì)運(yùn)動(dòng)的方式描述更為精準(zhǔn),而Person類(lèi)的move()方法則多了一個(gè)參數(shù)。這兩個(gè)子類(lèi)的move()方法都是完全拋棄父類(lèi)move()方法的細(xì)節(jié)而重新定義的,但Snake類(lèi)的move()方法則不同,它保留了父類(lèi)move()方法的實(shí)現(xiàn)細(xì)節(jié),希望在父類(lèi)的move()方法上進(jìn)行改良,加上自己需要的額外的代碼。這就意味著在Snake類(lèi)的move()方法中要調(diào)用父類(lèi)的move()方法,此時(shí)就要使用super()這個(gè)特別的方法了。在一個(gè)類(lèi)中使用super()方法意味著呼喚這個(gè)類(lèi)的父類(lèi),所以Snake類(lèi)的move()方法先輸出父類(lèi)的move()方法的結(jié)果,然后輸出“雖然我沒(méi)有腿,但我爬的速度并不慢?!?0.3.2方法的覆蓋
對(duì)父類(lèi)方法的覆蓋并不只限于普通方法,即便是__init__()這樣的特殊方法也可以被覆蓋。例如,Person類(lèi)除了name和weight屬性,還有一個(gè)gender(性別)屬性,因此在實(shí)例化Person類(lèi)對(duì)象時(shí),就要對(duì)3個(gè)屬性進(jìn)行初始化,所以Person類(lèi)覆蓋了父類(lèi)的__init__()方法。但這個(gè)覆蓋并不是完全重打鼓另開(kāi)張,而是先調(diào)用父類(lèi)的__init__()方法對(duì)name和weight兩個(gè)屬性初始化,然后完成自己特有屬性gender的初始化,因此在Person類(lèi)的__init__()方法中也用到了super()方法來(lái)調(diào)用父類(lèi)。10.3.3多態(tài)和鴨子類(lèi)型父類(lèi)的同一個(gè)行為在不同的子類(lèi)中有不同的表現(xiàn)形式,這在面向?qū)ο笾斜环Q(chēng)為多態(tài)(polymorphism),如Animal類(lèi)的move()方法在多個(gè)子類(lèi)中表現(xiàn)各異。多態(tài)是面向?qū)ο笏枷胫蟹浅?岬囊粋€(gè)想法,它讓程序既可以表現(xiàn)出各子類(lèi)的獨(dú)特特點(diǎn),又可以在一個(gè)更高的層次上統(tǒng)領(lǐng)這些子類(lèi)。想象一下如果沒(méi)有Animal類(lèi)的move()方法,而是直接在Bird、Fish、Person等類(lèi)中各自添加fly()方法、swim()方法和walk()方法,這樣是可以表現(xiàn)這些類(lèi)各自的運(yùn)動(dòng)細(xì)節(jié),但這些類(lèi)彼此就沒(méi)有什么關(guān)系了,程序無(wú)法用一個(gè)統(tǒng)一的視角來(lái)看待它們。當(dāng)程序的某個(gè)輸入只是需要一個(gè)擁有運(yùn)動(dòng)能力的對(duì)象時(shí),程序無(wú)法說(shuō)清這個(gè)要求。10.3.3多態(tài)和鴨子類(lèi)型如果說(shuō)程序需要一個(gè)擁有fly()方法的對(duì)象,那就把除Bird對(duì)象外的其他擁有運(yùn)動(dòng)能力的對(duì)象都排除了。同樣也不能說(shuō)需要一個(gè)擁有swim()方法的對(duì)象,實(shí)際上這種情況下說(shuō)不清楚,因?yàn)闆](méi)有一個(gè)更高層次的統(tǒng)一視角。而如果有了擁有move()能力的Animal類(lèi),程序只需要求輸入的對(duì)象是Animal類(lèi)的對(duì)象即可,Bird、Fish、Person等子類(lèi)對(duì)象都是合法的輸入對(duì)象,至于當(dāng)下輸入的Animal對(duì)象運(yùn)動(dòng)時(shí)是飛、是游、是走就無(wú)所謂了。當(dāng)然如果只有Animal類(lèi)的move()方法也不行,這樣太籠統(tǒng),無(wú)法體現(xiàn)各不同子類(lèi)對(duì)象的運(yùn)動(dòng)特點(diǎn)。因此多態(tài)才很酷。利用多態(tài)可以說(shuō)明程序需要一個(gè)擁有move()能力的Animal對(duì)象,但具體傳入數(shù)據(jù)時(shí),可以傳入Bird對(duì)象,也可以傳入Fish對(duì)象,因?yàn)檫@些對(duì)象都是Animal類(lèi)對(duì)象,都擁有move()能力,但它們又都保持了各自獨(dú)特的move()方法細(xì)節(jié)。10.3.3多態(tài)和鴨子類(lèi)型多態(tài)是面向?qū)ο笾泻苤匾囊粋€(gè)概念,Python在這一點(diǎn)上走得更遠(yuǎn),提出了一個(gè)更有意思的概念:鴨子類(lèi)型(DuckTyping)。正像有句話所說(shuō):“當(dāng)一只鳥(niǎo)走起來(lái)像鴨子、游起泳來(lái)像鴨子、叫起來(lái)也像鴨子時(shí),那么這只鳥(niǎo)就可以被稱(chēng)為鴨子?!毖酝庵怿喿宇?lèi)型關(guān)注的不是對(duì)象到底是什么類(lèi)型的,而是對(duì)象是否擁有程序需要的能力。一只鳥(niǎo)是不是鴨子并不重要,重要的是它能否像鴨子一樣地走、像鴨子一樣地叫、像鴨子一樣地游泳。因?yàn)檎f(shuō)到底,程序需要的其實(shí)并不一定是鴨子,而是鴨子的那些能力。如果一只鳥(niǎo)完美地?fù)碛续喿拥倪@些能力,即便它不是鴨子又有什么問(wèn)題呢。代碼10.4中的Car類(lèi)并不是Animal類(lèi)的子類(lèi),但它擁有一個(gè)move()方法,因此當(dāng)程序需要一個(gè)能move()的對(duì)象時(shí),一個(gè)Car類(lèi)對(duì)象完全可以像Animal類(lèi)的對(duì)象一樣勝任。這比需要繼承關(guān)系的多態(tài)更進(jìn)一步,更加靈活。10.3.3多態(tài)和鴨子類(lèi)型代碼10.4多態(tài)的延伸:鴨子類(lèi)型classAnimal:definit(self,name,weight):=nameself.weight=weightdefmove(self):print(f"我是{},我真的在動(dòng),不騙你。")classFish(Animal):pass10.3.3多態(tài)和鴨子類(lèi)型代碼10.4多態(tài)的延伸:鴨子類(lèi)型classCar:defmove(self):print("我是一輛轎車(chē),我不是動(dòng)物,但我能動(dòng)。")classStone:passdefis_movable_obj(obj):try:obj.move()except:print("這不是一個(gè)能動(dòng)的東西。")10.3.3多態(tài)和鴨子類(lèi)型代碼10.4多態(tài)的延伸:鴨子類(lèi)型things=[]things.append(Fish('小丑魚(yú)',5))things.append(Car())things.append(Stone())forobjinthings:is_movable_obj(obj)10.3.3多態(tài)和鴨子類(lèi)型代碼10.4多態(tài)的延伸:鴨子類(lèi)型things=[]things.append(Fish('小丑魚(yú)',5))things.append(Car())things.append(Stone())forobjinthings:is_movable_obj(obj)PART410.4Python生態(tài)系統(tǒng)之tkinter庫(kù)10.4Python生態(tài)系統(tǒng)之tkinter庫(kù)前面各章的程序除小海龜繪圖外,大多沒(méi)有圖形用戶界面(GraphicUserInterface,GUI)。雖然GUI并不是程序必須具備的,但很多普通用戶還是習(xí)慣使用它,因此本節(jié)就來(lái)介紹一個(gè)可以進(jìn)行GUI設(shè)計(jì)的庫(kù)。在Python生態(tài)系統(tǒng)中可以進(jìn)行GUI設(shè)計(jì)的工具包很多,如tkinter、PyQt、wxPython、kivy、enaml等。其中,tkinter的功能雖然不是最強(qiáng)大的,但它是標(biāo)準(zhǔn)庫(kù),無(wú)須額外安裝,而且容易理解、易于上手,不失為一個(gè)好的起點(diǎn)。10.4.1初識(shí)tkintertkinter庫(kù)將GUI看成一個(gè)樹(shù)狀結(jié)構(gòu)。首先有一個(gè)窗體作為根,位于窗體上的一系列的零部件(標(biāo)簽、按鈕、文本框、菜單等)都是根窗體的樹(shù)枝。利用tkinter工具包設(shè)計(jì)GUI的過(guò)程就是用代碼描述清楚各窗體零部件屬于哪個(gè)父窗體、位置如何的過(guò)程。tkinter庫(kù)采用面向?qū)ο罄砟钤O(shè)計(jì),提供了一系列的窗體零部件類(lèi),根據(jù)這些類(lèi)可以很方便地生產(chǎn)出標(biāo)簽、文本框、按鈕等GUI所需的零部件。在生產(chǎn)這些零部件的同時(shí)可以指定它們的父窗體及零部件自身的一些特征,之后使用某種布局方式說(shuō)明零部件在父窗體中的位置。tkinter提供了多種布局方式,其中place方式將父窗體看成坐標(biāo)原點(diǎn)在左上角的一個(gè)平面坐標(biāo)系,要說(shuō)明各零部件位于窗體的位置只需指出零部件的左上角坐標(biāo)位置即可;而grid方式則將父窗體假想成一個(gè)有若干行列的表格,然后指出每個(gè)零部件位于第幾行、第幾列的單元格即可。10.4.1初識(shí)tkinter各種窗體零部件中有的只具有展示功能,信息的傳遞是單向的。但類(lèi)似文本框或按鈕之類(lèi)的零部件是可以接收用戶的操作的,如輸入文本、鼠標(biāo)單擊等,這類(lèi)窗體零部件的信息傳遞是雙向的,對(duì)于這些零部件還會(huì)有輔助其工作的一些其他窗體要素。為了說(shuō)明這些細(xì)節(jié),下面以常見(jiàn)的登錄界面為例,演示如何使用place布局方式打造一個(gè)如圖10.1所示的界面。在這個(gè)登錄界面中,主窗體尺寸比較小,而且不希望用戶調(diào)整窗口尺寸,所以窗體右上角的【最大化】按鈕為不可用狀態(tài)。窗體上有兩個(gè)標(biāo)簽Label用來(lái)展示說(shuō)明性文字“用戶”“密碼”,還有兩個(gè)文本框Entry,兩個(gè)按鈕Button。單擊【登錄】按鈕,程序會(huì)檢驗(yàn)用戶輸入的用戶名和密碼是否正確,根據(jù)正確與否打開(kāi)一個(gè)對(duì)話框給用戶反饋。單擊【重置】按鈕后,會(huì)將當(dāng)前文本框中輸入的內(nèi)容清空。接下來(lái)對(duì)程序的分析分成兩大部分,首先是窗體和負(fù)責(zé)靜態(tài)信息展示的標(biāo)簽的生成過(guò)程;之后是可以和用戶互動(dòng)的文本框、按鈕等零部件的生成過(guò)程。10.4.2生成窗體與標(biāo)簽生成登錄窗體及標(biāo)簽部件的代碼如代碼10.5所示,首先導(dǎo)入tkinter庫(kù),因?yàn)榕袛嗤昝艽a是否正確后要用到消息對(duì)話框給用戶反饋,所以一并導(dǎo)入消息對(duì)話框模塊。接下來(lái)利用Tk類(lèi)創(chuàng)建登錄界面的根窗體,并設(shè)置窗體的標(biāo)題欄文本為“請(qǐng)登錄...”,然后利用geometry()方法指明窗體的幾何特征,包括窗體的尺寸大小及在屏幕的位置。這些信息通過(guò)一個(gè)特殊格式的字符串來(lái)說(shuō)明,需要指出的是寬高之間使用字母x分開(kāi),而不是乘號(hào)。最后在窗體對(duì)象的resizable()方法中聲明窗體在兩個(gè)維度上都不可以調(diào)整大小,因此右上角的【最大化】按鈕將失效。10.4.2生成窗體與標(biāo)簽接下來(lái)調(diào)用tkinter庫(kù)中的Label類(lèi)生成兩個(gè)標(biāo)簽部件,分別顯示“用戶:”和“密碼:”。在生成這些界面零部件時(shí),首先要指明其所屬的父窗體,這里顯然都為root_win。另外,還有其他一些描述零部件特征的信息可以一并說(shuō)明,對(duì)于標(biāo)簽部件而言,要顯示的文字內(nèi)容是必須要說(shuō)明的,這可以通過(guò)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è)置完全類(lèi)似。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è)過(guò)程中二者是捆綁在一起的。用戶在文本框中輸入的數(shù)據(jù)會(huì)保存在幕后的變量中,而幕后的變量值如果發(fā)生了變化,文本框中的內(nèi)容也會(huì)跟著改變。因此在描述窗體中的文本框時(shí),除文本框本身的外觀外,還要指明和文本框搭檔的幕后變量。由于幕后變量要與搭檔的文本框綁定在一起,以實(shí)現(xiàn)內(nèi)容信息的一致,因此這種變量不是一個(gè)普通的Python變量。tkinter庫(kù)提供了一個(gè)專(zhuān)門(mén)的類(lèi)來(lái)創(chuàng)建這種幕后變量,這就是StringVar類(lèi)。根據(jù)StringVar類(lèi)創(chuàng)建的幕后變量都有g(shù)et()和set()方法用來(lái)讀取、修改幕后變量的值。10.4.3生成文本框與按鈕在代碼10.6中首先使用tkinter.StringVar()聲明一個(gè)變量var_user,并將其值設(shè)為空字符串。這就是要和用戶名文本框搭檔的幕后變量。接下來(lái)調(diào)用Entry類(lèi)生成文本框,除指明父窗體外還要指明和其搭檔的幕后變量是誰(shuí),這是通過(guò)textvariable參數(shù)完成的。之后同樣調(diào)用文本框部件的place()方法設(shè)置其在主窗體上的位置。接下來(lái)的密碼文本框設(shè)置過(guò)程與用戶名文本框是類(lèi)似的,只是多了一個(gè)show參數(shù),用來(lái)設(shè)置密碼文本框不把用戶輸入的密碼直接顯示出來(lái),而是以*號(hào)代替。10.4.3生成文本框與按鈕界面元素還剩下兩個(gè)按鈕,單擊按鈕后要有反應(yīng)動(dòng)作,因此對(duì)于按鈕部件最重要的是要指明單擊按鈕之后執(zhí)行什么命令(command),即要為按鈕預(yù)備響應(yīng)函數(shù)。所以代碼10.6先定義了一個(gè)login()函數(shù),并在利用Button類(lèi)生成【登錄】按鈕時(shí)通過(guò)command參數(shù)指明單擊按鈕后的響應(yīng)函數(shù)是login()函數(shù)。注意,command參數(shù)處只寫(xiě)函數(shù)名,不需要函數(shù)的括號(hào)。在login()函數(shù)中通過(guò)和文本框配合的幕后變量的get()方法獲取文本框中用戶輸入的用戶名與密碼,然后與預(yù)存的正確值進(jìn)行比對(duì)。根據(jù)比對(duì)結(jié)果調(diào)用messagebox.showinfo()或showwarning()顯示消息對(duì)話框,這里可以設(shè)置對(duì)話框的標(biāo)題欄、要展示的消息文本等。【重置】按鈕的處理是類(lèi)似的,這里不再贅述。單擊【重置】按鈕后執(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利用類(lèi)重構(gòu)了第8章的案例,除了歷史人物,還在時(shí)間線上添加了歷史事件。歷史人物與歷史事件都繼承了時(shí)間線條目的一些共同屬性、方法,同時(shí)又可以有各自獨(dú)特的地方,如歷史事件希望使用紅色來(lái)進(jìn)行繪制,這些都可以通過(guò)類(lèi)的繼承與多態(tài)來(lái)輕松實(shí)現(xiàn)。(代碼見(jiàn)書(shū)235頁(yè)10.7)10.5.1使用類(lèi)重構(gòu)歷史時(shí)間線案例這個(gè)案例將嘗試使用tkinter開(kāi)發(fā)一個(gè)類(lèi)似“打地鼠”的小游戲,界面效果如圖10.3所示。界面中有5行5列共25個(gè)按鈕,每個(gè)按鈕代表一個(gè)地鼠。界面最下方有4個(gè)標(biāo)簽,用來(lái)記錄玩家命中的地鼠數(shù)量及地鼠更換洞穴的速度。從圖10.3可以看出,整個(gè)界面的布局呈現(xiàn)明顯的表格效果,因此使用tkinter庫(kù)的grid布局方式是很方便的。在grid布局下,只需說(shuō)明每個(gè)零部件位于窗體假想表格的第幾行、第幾列即可。10.5.2使用tkinter設(shè)計(jì)打地鼠游戲程序中的25個(gè)地鼠按鈕被保存在一個(gè)嵌套列表中,每個(gè)按鈕都有可用和不可用兩種狀態(tài),不可用時(shí)對(duì)單擊動(dòng)作沒(méi)有反應(yīng)。程序通過(guò)持續(xù)不斷地隨機(jī)改變某個(gè)按鈕的狀態(tài)來(lái)模擬隨機(jī)出現(xiàn)的地鼠。這個(gè)實(shí)現(xiàn)過(guò)程的關(guān)鍵點(diǎn)利用了按鈕零部件的after()方法,該方法可以在指定的時(shí)間過(guò)后去執(zhí)行某個(gè)函數(shù),正是在這個(gè)函數(shù)中讓當(dāng)前可用按鈕不可用,并再次隨機(jī)挑選下一個(gè)可用的按鈕,從而實(shí)現(xiàn)地鼠不斷出現(xiàn)的過(guò)程。具體的程序細(xì)節(jié)如代碼10.8所示。代碼分成幾個(gè)部分,在導(dǎo)入所需模塊、生成主窗體后,代碼迎來(lái)了輔助函數(shù)部分,這里定義了4個(gè)輔助函數(shù)。然后是3個(gè)全局變量,接下來(lái)就是各界面零部件的生成,先是保存在列表中的25個(gè)地鼠按鈕,然后是界面下方的4個(gè)標(biāo)簽部件。一切就緒后,程序啟動(dòng)地鼠隨機(jī)出現(xiàn)的過(guò)程。(代碼見(jiàn)書(shū)238頁(yè)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é)果。問(wèn)題是計(jì)算器上會(huì)有比較多的運(yùn)算種類(lèi),如何處理眾多的運(yùn)算呢?當(dāng)一種新的運(yùn)算出現(xiàn)后,如何方便地?cái)U(kuò)展程序使其支持新的運(yùn)算呢?不同的運(yùn)算其處理流程是相似的,如何避免大量重復(fù)代碼呢?這些都是在設(shè)計(jì)程序時(shí)要考慮的問(wèn)題。不僅要考慮解決當(dāng)下問(wèn)題,還要考慮未來(lái)業(yè)務(wù)發(fā)生變化后程序是否能很好地適應(yīng)。計(jì)算器這個(gè)例子雖然很小,但也存在這些問(wèn)題。接下來(lái)通過(guò)編寫(xiě)一個(gè)簡(jiǎn)單的計(jì)算器程序,實(shí)際感受一下面向?qū)ο缶幊痰膬?yōu)勢(shì)。10.6.1識(shí)別對(duì)象與類(lèi)在面向?qū)ο缶幊踢^(guò)程中恰當(dāng)?shù)刈R(shí)別程序需要的對(duì)象是非常重要的。在計(jì)算器這個(gè)例子中顯然計(jì)算器是一個(gè)核心對(duì)象,應(yīng)該將其定義為一個(gè)類(lèi)。但計(jì)算器有很多運(yùn)算類(lèi)型,加、減、乘、除等,如何處理它們呢?一種方案是定義一個(gè)計(jì)算器類(lèi),然后在其內(nèi)部定義對(duì)應(yīng)加、減、乘、除等一系列運(yùn)算的很多方法。另一種方案是定義一個(gè)父類(lèi),說(shuō)明運(yùn)算的一般要素,如兩個(gè)輸入、一個(gè)輸出;然后定義很多子類(lèi),每個(gè)子類(lèi)完成一種特定的運(yùn)算。顯然,第一種方案所有的程序邏輯都位于一個(gè)核心的計(jì)算器類(lèi)中,隨著運(yùn)算種類(lèi)的增加,程序變得越來(lái)越復(fù)雜,這個(gè)計(jì)算器類(lèi)也就變得越來(lái)越龐大、臃腫,類(lèi)中對(duì)應(yīng)各種運(yùn)算的方法之間會(huì)有很多代碼是類(lèi)似的、重復(fù)的,而且因?yàn)槌绦虻墓δ芏紝?xiě)到了這一個(gè)類(lèi)中,程序的耦合度非常高。10.6.1識(shí)別對(duì)象與類(lèi)而使用第二個(gè)方案,程序結(jié)構(gòu)就會(huì)清晰、均衡,因?yàn)樗蓄?lèi)型運(yùn)算的一般要素都寫(xiě)在父類(lèi)中,各子類(lèi)只需將注意力放在自己對(duì)應(yīng)的運(yùn)算類(lèi)型的具體實(shí)現(xiàn)上,就會(huì)降低代碼的重復(fù)性和耦合度。即便將來(lái)支持的運(yùn)算類(lèi)型越來(lái)越多,也只是從父類(lèi)派生出的子類(lèi)越來(lái)越多而已,程序的結(jié)構(gòu)不用改變,擴(kuò)展起來(lái)比較容易。10.6.1識(shí)別對(duì)象與類(lèi)代碼10.9就是按照第二種方案實(shí)現(xiàn)的。首先定義父類(lèi)AbstractCalculator,這個(gè)類(lèi)定義了加、減、乘、除等眾多運(yùn)算器都應(yīng)該具有的共性特征。這個(gè)類(lèi)好比Animal類(lèi),而下面具體的運(yùn)算器類(lèi)就相當(dāng)于具體的動(dòng)物子類(lèi)。例如,PlusCalculator類(lèi)是加法運(yùn)算器類(lèi),負(fù)責(zé)實(shí)現(xiàn)加法運(yùn)算,其calculating()方法覆蓋父類(lèi)的calculating()方法;MinusCalculator類(lèi)是減法運(yùn)算器類(lèi),負(fù)責(zé)實(shí)現(xiàn)減法運(yùn)算,其calculating()方法也要覆蓋父類(lèi)中的calculating()方法。通過(guò)這樣的設(shè)計(jì),以后程序如果想支持更多種類(lèi)的運(yùn)算,只需添加相應(yīng)的子類(lèi),新添加的子類(lèi)也只需覆蓋父類(lèi)的calculating()方法,子類(lèi)只需關(guān)心如何實(shí)現(xiàn)運(yùn)算細(xì)節(jié)即可。(代碼見(jiàn)書(shū)241頁(yè)10.9)10.6.2使用設(shè)計(jì)模式有了諸如PlusCalculator和MinusCalculator等具體的運(yùn)算器類(lèi)之后,程序就可以根據(jù)用戶輸入的運(yùn)算符創(chuàng)建相應(yīng)的運(yùn)算器對(duì)象了,然后完成用戶的運(yùn)算要求。在生成具體的運(yùn)算器對(duì)象時(shí)可以使用簡(jiǎn)單工廠模式。模式(Pattern)是對(duì)一個(gè)重復(fù)發(fā)生的問(wèn)題,以及人們總結(jié)出來(lái)的對(duì)該問(wèn)題的一個(gè)較優(yōu)的解決方案的描述。例如,在工程上人們總結(jié)了很多問(wèn)題的解決方案;又如,在棋類(lèi)游戲中也有很多定式,什么樣的局面用什么樣的招數(shù),棋譜上都有說(shuō)明。棋譜其實(shí)就是人們總結(jié)的各種模式。類(lèi)似地,程序設(shè)計(jì)模式描述了程序設(shè)計(jì)過(guò)程中針對(duì)一些常見(jiàn)問(wèn)題人們總結(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è)專(zhuān)門(mén)的工廠類(lèi)中,工廠類(lèi)僅作為這個(gè)方法的一個(gè)容器,但實(shí)際上調(diào)用這個(gè)方法并不需要再去實(shí)例化一個(gè)工廠類(lèi)對(duì)象,那樣就太煩瑣了。因此把生產(chǎn)運(yùn)算器對(duì)象的方法通過(guò)@staticmethod修飾符聲明為靜態(tài)方法。靜態(tài)方法簡(jiǎn)單來(lái)說(shuō)就是不需實(shí)例化對(duì)象,直接使用類(lèi)名即可調(diào)用的方法,因此靜態(tài)方法沒(méi)有self參數(shù)。這樣工廠類(lèi)就可以根據(jù)用戶提供的操作符來(lái)生成相應(yīng)的運(yùn)算器了。具體效果如代碼10.10所示。10.6.2使用設(shè)計(jì)模式代碼10.10負(fù)責(zé)生成運(yùn)算器對(duì)象的工廠類(lèi)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)算功能的類(lèi)都具備了,負(fù)責(zé)生成具體運(yùn)算器對(duì)象的工廠也具備了,剩下的就是程序和用戶交互的部分了。這個(gè)部分可以是命令行式的,也可以是圖形界面。代碼10.11提供了命令行方式的交互過(guò)程。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. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年公交優(yōu)先戰(zhàn)略與城市交通擁堵治理協(xié)同發(fā)展研究報(bào)告
- 安全管理考證試題及答案
- ppp項(xiàng)目培訓(xùn)課件下載
- 電動(dòng)貨車(chē)培訓(xùn)課件圖片
- 周末收心班會(huì)課件
- 中國(guó)動(dòng)漫繪畫(huà)課件下載
- 超聲引導(dǎo)下穿刺技術(shù)應(yīng)用規(guī)范
- 中國(guó)刺繡課件英語(yǔ)
- 創(chuàng)意美術(shù)水果房子
- 中國(guó)農(nóng)大葡萄酒課件
- 2025年廣東省高考地理試卷真題(含答案)
- Unit 1 Happy Holiday 第4課時(shí)(Section B 1a-1d) 2025-2026學(xué)年人教版英語(yǔ)八年級(jí)下冊(cè)
- 新生兒吞咽吸吮功能訓(xùn)練
- 2025年連云港市中考語(yǔ)文試卷真題(含標(biāo)準(zhǔn)答案及解析)
- 2025-2030年中國(guó)期貨行業(yè)市場(chǎng)深度調(diào)研及競(jìng)爭(zhēng)格局與投資策略研究報(bào)告
- 2025-2030年中國(guó)農(nóng)業(yè)科技行業(yè)市場(chǎng)深度調(diào)研及前景趨勢(shì)與投資研究報(bào)告
- 成人重癥患者顱內(nèi)壓增高防控護(hù)理專(zhuān)家共識(shí)
- 2025至2030年中國(guó)腫瘤治療行業(yè)市場(chǎng)發(fā)展?jié)摿扒熬皯?zhàn)略分析報(bào)告
- 危險(xiǎn)化學(xué)品-經(jīng)營(yíng)安全管理制度與崗位操作流程
- 2024年河南省豫地科技集團(tuán)有限公司招聘真題
- 2025年高考語(yǔ)文真題作文深度分析之全國(guó)二卷作文寫(xiě)作講解
評(píng)論
0/150
提交評(píng)論