版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
匯報人:WPS程序設計基礎第九章模塊與包目錄01模塊與包的本質(zhì)02庫的安裝與導入03Python生態(tài)系統(tǒng)之Pygame庫04小試牛刀05拓展實踐:使用模塊組織代碼.
理解模塊與包的概念。.深入理解import語句。.能使用模塊與包組織代碼。學習目標PART19.1模塊與包的本質(zhì)9.1模塊與包的本質(zhì)我們已經(jīng)知道函數(shù)是完成特定功能的一段程序,為代碼的復用帶來方便。如果編寫的多個函數(shù)具有相關性,組合起來可以解決更大的問題,如文件路徑的處理或時間的處理。那這些函數(shù)就可以被組織在一個代碼文件(以“.py”為擴展名)中,整個代碼文件可以被其他的程序復用,這個代碼文件就是一個模塊。因此Python的模塊沒有什么神秘的,其本質(zhì)就是一個代碼文件。代碼文件中會有變量、語句、函數(shù)或類等內(nèi)容。而這個代碼文件或者說模塊可以被其他程序?qū)?,重復使用,因此模塊的價值在于代碼復用。沒有復用價值的代碼文件雖然理論上也是一個模塊,但意義不大。9.1模塊與包的本質(zhì)如果函數(shù)或類比較多,組織在一個代碼文件中會過于沉重,則會被分散到多個代碼文件。這多個代碼文件又會被組織在文件夾下,只要在文件夾下添加一個名為__init__.py的文件(init前后各有兩個下畫線,只要存在該文件就行,文件可以沒有內(nèi)容),該文件夾就成為包。由此可見,所謂模塊、包,其實都是對代碼的合理組織,目的是讓程序結(jié)構(gòu)更清晰,更好地復用代碼。而之前一直提到的庫則是一種通俗的稱謂,它強調(diào)的是一組有關聯(lián)的、可以廣為其他程序使用的代碼文件集合。庫既可以是模塊也可以是包,這取決于庫的規(guī)模。例如,random庫就是一個random.py代碼文件,因此random庫是一個模塊。而xml庫的代碼則分散在多個文件夾下的多個代碼文件中,因此xml庫是一個包。事實上在很多Python資料中,模塊、包與庫等這些概念往往混用,不會特意區(qū)別。本書之前也沒有刻意區(qū)別這些術語。9.1模塊與包的本質(zhì)這么看來,在邏輯上一個Python程序可以由包、模塊、函數(shù)組成。包之下可以有子包或模塊,而模塊則是寫在同一個文件中的函數(shù)、類等的集合。在物理上,Python程序是一行行的代碼,這些代碼分散在一個個的文件中,這些文件又位于一個個的文件夾中,如圖9.1所示。圖9.1顯示的是xml庫的目錄結(jié)構(gòu)。從圖左側(cè)可知,xml庫下有dom、etree、parsers、sax等多個文件夾。圖左側(cè)選中了etree文件夾,右側(cè)可見有一個__init__.py文件。事實上這些文件夾下均有一個__init__.py文件,因此它們都是包。而etree包下還有多個Python代碼文件,它們都是模塊。每一個模塊中都有可被其他程序使用的函數(shù)、類等,如第4章解析XML格式數(shù)據(jù)用到的fromstring()函數(shù)就位于ElementTree.py模塊中。PART29.2庫的安裝與導入
9.2.1使用pip安裝第三方庫Python
中的庫通常分為以下幾類。(
1)內(nèi)置的。內(nèi)置庫通常是
Python
的核心模塊,隨著
Python解釋器一并安裝,因此不需要另外
安裝,可以直接導入使用。內(nèi)置庫又稱為
Python標準庫。(2)第三方的。經(jīng)過幾十年的發(fā)展,Python
擁有的庫非常多,不可能都與解釋器一并安裝。這
些庫經(jīng)過審核后可以被廣大
Python開發(fā)者使用,這種現(xiàn)成的但并未隨著解釋器內(nèi)置的庫被統(tǒng)稱為第
三方庫。使用它們時需要先安裝再導入。(3)自定義的。在大型系統(tǒng)的開發(fā)中,開發(fā)者往往將系統(tǒng)功能分為多個模塊來實現(xiàn),然后在主
模塊文件或其他代碼文件中導入使用其他功能的模塊。這些主要為自己的軟件程序服務的模塊就是
自定義的模塊。9.2.1使用pip安裝第三方庫內(nèi)置庫直接導入即可使用,但第三方庫是需要安裝的。Python
生態(tài)系統(tǒng)中有很多第三方庫具有
廣泛的應用領域,如
NumPy、SciPy
等在科學計算、數(shù)據(jù)分析、人工智能等很多領域都有廣泛的應
用。在第
5
章使用jieba庫時介紹了如何在
PyCharm
中安裝第三方庫,但安裝第三方庫未必要依賴
PyCharm編程工具,更通用的方式是使用pip工具。高版本的
Python解釋器在安裝過程中默認已經(jīng)
將pip
工具一并安裝了,所以可以直接在Windows
命令提示符窗口中使用pip
工具。下面以安裝
Pygame為例,演示如何使用pip安裝第三方庫。打開
Windows
的命令提示符窗口,在光標后輸入“pip”,然后直接按
Enter鍵即可看到關于pip
工具的使用幫助。如果是要安裝工具包,如
Pygame,則只需輸入“pipinstallpygame”命令,按
Enter
鍵后
pip會自動去網(wǎng)絡上下載指定的工具包并安裝,如圖
9.2所示。pip工具默認是去國外的站點上
下載,如果速度過慢則可以更換為國內(nèi)的鏡像服務器,這方面的信息可以去搜索引擎進行查詢。9.2.2導入模塊的不同形式庫只是一個形象的、通俗的稱謂,“導入庫”的真實含義是要復用該庫提供的某些代碼。而庫的代碼是位于模塊(代碼文件)中的,因此使用import語句導入庫時要指明導入的代碼文件,也就是模塊。1.導入整個模塊導入整個模塊是最基本的形式,即import<模塊名>。前面的例子中導入庫大多使用的是這種形式。以這種形式導入后,使用模塊中的函數(shù)、類等內(nèi)容時都要添加模塊名前綴。但由于庫的規(guī)模不同,有的庫本身只是一個模塊,如random庫、math庫等,導入就很簡單;有的庫有多個模塊,如xml庫。對于xml這類有多個模塊的庫,導入時不能簡單地書寫庫名,因為真正要導入的是模塊,所以必須說明要導入xml庫中的哪個模塊。一般這種情形下模塊都會位于包(文件夾)下,因此在導入模塊時必然涉及文件夾路徑。只是import語句中會將路徑分隔符改為點,于是就有下面代碼9.1的寫法,這也是在第4章出現(xiàn)過的寫法,現(xiàn)在應該理解得更清楚了。9.2.2導入模塊的不同形式代碼
9.1
導入整個模塊
9.2.2導入模塊的不同形式代碼
9.1
導入整個模塊
代碼中的random
模塊導入時直接書寫模塊對應的代碼文件名,注意不用寫文件的擴展名。而
xml庫的
ElementTree.py模塊在
xml\etree\文件夾下,因此導入時將路徑分隔符改為點,同樣不用寫
文件擴展名。當然,無論模塊文件所處的路徑深淺,這種將整個模塊導入的形式要求在使用模塊中
的函數(shù)等屬性時必須添加模塊前綴,所以
choice()函數(shù)及
fromstring()函數(shù)在使用時都必須有前綴名。
而
xml.etree.ElementTree作為前綴確實過長,書寫不便,所以
import語句可以使用
as關鍵字給導入
的模塊起別名,這樣只需寫
ET.fromstring()即可,較為方便。另外,程序要導入多個模塊時,理論上可以使用一個
import
語句,模塊名使用逗號分隔。但Python
不建議使用一行
import導入多個模塊,推薦使用多個
import語句,每行導入一個模塊。9.2.2導入模塊的不同形式代碼
9.1
導入整個模塊
2.導入模塊的某個屬性如果只是需要使用模塊中的一兩個屬性,不想將該模塊中的其他內(nèi)容都導入自己的代碼文件的作用域中,因為那樣可能會讓自己的程序變得臃腫。針對這種情形可以使用如代碼9.2所示的形式來直接導入目標屬性。相對于傳統(tǒng)的import形式,代碼9.2所示的形式是使用什么只導入什么,更有針對性。但如果要使用模塊中較多的屬性內(nèi)容,這種方式就不適合了。9.2.2導入模塊的不同形式代碼
9.1
導入整個模塊
代碼9.2導入模塊中的個別屬性fromrandomimportchoice#僅導入模塊中要使用的函數(shù)fromxml.etree.ElementTreeimportfromstringxml_str="""<data><entryauthor="孔子">學而時習之,不亦說乎?</entry><entryauthor="子夏">日知其所亡,月無忘其所能,可謂好學也已矣。</entry><entryauthor="顧炎武">有一日未死之身,則有一日未聞之道。</entry></data>"""root=fromstring(xml_str)#直接書寫函數(shù)名,沒有前綴data_base=[]forentryinroot:author=entry.attrib["author"]content=entry.textdata_base.append((author,content))entry_selected=choice(data_base)#沒有前綴print(entry_selected)9.2.2導入模塊的不同形式代碼
9.1
導入整個模塊
使用from直接導入模塊中的目標屬性后,使用它們時無須書寫模塊前綴。這一點既有優(yōu)點也有弊端。優(yōu)點是書寫簡潔、方便;弊端是在導入多個模塊時,因為沒有模塊前綴,所以不知道哪個函數(shù)來自哪個模塊,不同模塊的同名函數(shù)會發(fā)生名稱沖突,為程序引入了不必要的混亂因素。PART39.3Python生態(tài)系統(tǒng)之Pygame庫9.3Python生態(tài)系統(tǒng)之Pygame庫代碼
9.1
導入整個模塊
Pygame庫是Python生態(tài)系統(tǒng)中比較有名的用來開發(fā)游戲的庫,它提供了易用的、豐富的音頻、視頻操作功能,非常適合用來開發(fā)小型游戲。實際上,動手編寫游戲是一個非常好的學習編程的途徑,學習編寫游戲可能比玩游戲更有意思。9.3.1初識Pygame代碼
9.1
導入整個模塊
Pygame庫中有很多模塊,分別負責游戲開發(fā)中的各類任務。例如,pygame.display負責處理界面顯示方面的操作,pygame.time負責和時間有關的操作,pygame.mixer可以處理音頻。其中,pygame.display下的set_mode()函數(shù)用來創(chuàng)建游戲的主界面,這個函數(shù)會返回一個Surface對象,代表著游戲主界面。Surface對象有一個blit()方法,可以將游戲形象(Pygame稱為精靈)“繪制”在需要的位置。游戲一般有一些精心設計的游戲形象,Pygame將這些游戲形象稱為精靈(sprite)。精靈可能是任何東西,可以是拿著武器沖鋒陷陣的戰(zhàn)士,也可以是被動挨打的無生命的乒乓球,又或者是戰(zhàn)士手中的槍、用來打球的球拍等,這些都可以看成游戲形象,都可以被稱為精靈。游戲就是按照預設的邏輯讓這些精靈不斷變化,形成不同的游戲局面,讓玩家沉浸其中,得到樂趣。9.3.1初識Pygame代碼
9.1
導入整個模塊
游戲過程有兩面,一面是玩家看到的屏幕上五光十色的游戲角色在運動、動作、產(chǎn)生、消亡。這些精靈們有各自的外觀形象,它們在屏幕上的活動也是連續(xù)的,真的好像有生命一樣。但這一切都是假象,精靈的形象不過是一些圖片,精靈不同的狀態(tài)對應不同的圖片,這些圖片可以根據(jù)游戲需要出現(xiàn)在指定位置。游戲的另一面才是其真容:沒有動作,更不連續(xù),一切都是數(shù)據(jù)的變化。所有的精靈都只是一組數(shù)據(jù),數(shù)據(jù)的改變意味著精靈出現(xiàn)了某種變化。游戲只是記錄每個精靈下一刻的數(shù)據(jù)狀態(tài),然后根據(jù)這些數(shù)據(jù)將對應的精靈形象(圖片)在恰當?shù)臅r間“繪制”在屏幕恰當?shù)奈恢?。因為畫面更新速度非???,在人看來畫面是連續(xù)的。所以游戲開發(fā)除設計好各色精靈的外觀形象外,更重要的其實還是看不見的各種數(shù)據(jù)結(jié)構(gòu)的設計,使用什么樣的數(shù)據(jù)結(jié)構(gòu)記錄維護游戲精靈的狀態(tài),使用什么樣的算法去改變數(shù)據(jù)狀態(tài)是游戲內(nèi)在的靈魂。接下來通過一個簡單的小例子來練習使用Pygame開發(fā)游戲。9.3.2搭建游戲主框架代碼
9.1
導入整個模塊
游戲過程有兩面,一面是玩家看到的屏幕上五光十色的游戲角色在運動、動作、產(chǎn)生、消亡。這些精靈們有各自的外觀形象,它們在屏幕上的活動也是連續(xù)的,真的好像有生命一樣。但這一切都是假象,精靈的形象不過是一些圖片,精靈不同的狀態(tài)對應不同的圖片,這些圖片可以根據(jù)游戲需要出現(xiàn)在指定位置。游戲的另一面才是其真容:沒有動作,更不連續(xù),一切都是數(shù)據(jù)的變化。所有的精靈都只是一組數(shù)據(jù),數(shù)據(jù)的改變意味著精靈出現(xiàn)了某種變化。游戲只是記錄每個精靈下一刻的數(shù)據(jù)狀態(tài),然后根據(jù)這些數(shù)據(jù)將對應的精靈形象(圖片)在恰當?shù)臅r間“繪制”在屏幕恰當?shù)奈恢谩R驗楫嬅娓滤俣确浅?欤谌丝磥懋嬅媸沁B續(xù)的。所以游戲開發(fā)除設計好各色精靈的外觀形象外,更重要的其實還是看不見的各種數(shù)據(jù)結(jié)構(gòu)的設計,使用什么樣的數(shù)據(jù)結(jié)構(gòu)記錄維護游戲精靈的狀態(tài),使用什么樣的算法去改變數(shù)據(jù)狀態(tài)是游戲內(nèi)在的靈魂。接下來通過一個簡單的小例子來練習使用Pygame開發(fā)游戲。9.3.2搭建游戲主框架代碼
9.1
導入整個模塊
Pygame庫中有多個模塊,可根據(jù)需要導入所需的模塊。其中,pygame.locals模塊定義了Pygame中預置的常量,如標志窗體退出事件的常量QUIT。另外,在游戲窗體退出后,整個程序的運行也要終止,所以這里還需要用到sys庫中的exit()函數(shù),如代碼9.3所示。9.3.2搭建游戲主框架代碼
9.1
導入整個模塊
代碼9.3搭建游戲主框架importPygamefrompygame.localsimportQUITimportsyspygame.init()#初始化
Pygamesurface=pygame.display.set_mode((640,480))#游戲窗體大小pygame.display.set_caption('Hello,Pygame!')#窗體標題whileTrue:#主循環(huán)foreventinpygame.event.get():#獲取發(fā)生的事件ifevent.type==QUIT:#如果有退出事件pygame.quit()#Pygame退出sys.exit()#程序停止執(zhí)行pygame.display.update()#刷新畫面9.3.2搭建游戲主框架代碼
9.1
導入整個模塊
運行這段代碼可以看到一個指定尺寸的Pygame窗體,其標題欄為指定的文字內(nèi)容,單擊窗口右上角的“關閉”按鈕結(jié)束程序,如圖9.3所示。9.3.2搭建游戲主框架代碼
9.1
導入整個模塊
這段程序只是一個基本的框架,并沒有什么具體的游戲情節(jié),但還是有幾個重要的信息。首先是對Pygame各模塊進行初始化,然后在調(diào)用display模塊的set_mode()函數(shù)產(chǎn)生主窗體界面時可以指定窗口尺寸。如果長寬尺寸都為0,則產(chǎn)生一個和屏幕一樣大小的窗體。程序?qū)et_mode()函數(shù)返回的代表窗體界面的Surface對象保存在一個變量中,以便在游戲細節(jié)中使用。接下來調(diào)用了display.set_caption()函數(shù)設置窗體標題欄信息。這里并沒有指明要設置的是哪一個Surface窗口,因為Pygame同一時間只有一個Surface對象是當前窗口,因此無須說明。再往下就是重點了,這里有個條件為True的循環(huán),不妨稱其為游戲的主循環(huán)。程序運行后該循環(huán)會不停地執(zhí)行。游戲進行時需要不停地刷新畫面,因此完成游戲刷新的代碼應該寫在這個循環(huán)中。9.3.2搭建游戲主框架代碼
9.1
導入整個模塊
當然循環(huán)不能成為死循環(huán),打破這個循環(huán)的是玩家關閉窗口事件。事實上游戲進行中可能發(fā)生很多事件(event),如窗口關閉事件、單擊鼠標事件、鼠標指針移動事件、鍵盤上的按鍵事件等。每個事件都有一個編號,如退出事件的編號為256。但使用編號不友好,故Pygame為這些事件都定義了名稱;如退出事件記為QUIT,又因其值不變,故一般稱為常量。發(fā)生的事件會被送到一個事件隊列中。通過調(diào)用pygame.event.get()函數(shù)可以獲取當前事件隊列中的所有事件,代碼遍歷發(fā)生的事件,看看其中有沒有退出事件(QUIT),如果有則退出游戲。退出時首先要退出Pygame,然后調(diào)用sys.exit()退出Python系統(tǒng)。程序循環(huán)中的最后一句update()負責把緩存中的畫面數(shù)據(jù)真正送到屏幕上,不斷更新界面顯示。9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
下面在游戲框架代碼的基礎上來真正做點什么,如讓一個卡通橘子臉在屏幕上不斷地變換兩種表情,而且還能跟隨方向鍵移動。要完成這個場景首先要預備好兩個橘子臉圖片,一個高興的表情,一個悲傷的表情。有了這樣兩個圖片,只需快速交替顯示它們就可以出現(xiàn)變臉的效果。而跟隨方向鍵移動時需要知道玩家按下了哪個鍵,并根據(jù)按鍵方向更新橘子臉的位置數(shù)據(jù),再根據(jù)最新位置數(shù)據(jù)在界面上重新繪制出橘子臉,這樣橘子臉就可以移動了。當然繪制前還要確認應該使用兩個表情中的哪一個。這就是整個代碼要完成的主要工作。接下來將對代碼的分析分成兩大部分,while主循環(huán)之前的預備工作,以及while主循環(huán)內(nèi)部要負責的工作。9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
1.預備工作階段除設置游戲窗體的大小、標題等常規(guī)任務外,預備工作階段最重要的任務是將兩個不同表情的橘子臉圖片讀入內(nèi)存,使用pygame.image.load()函數(shù)完成這個工作。因為有兩個圖片,為了訪問方便,將兩個圖片載入后保存在一個列表中。load()函數(shù)載入圖片后返回的也是一個Surface對象。也就是說,不論是窗體界面,還是代表游戲精靈的圖片,在Pygame看來都是Surface對象。因為它們都是游戲外表的一層皮,真正的游戲精靈是由無形的、看不見的數(shù)據(jù)來表示的。因此接下來需要一個數(shù)據(jù)結(jié)構(gòu)來表示真正的橘子臉精靈,這里選擇了Pygame提供的Rect類。顧名思義,這個類可以表示一個矩形,而橘子臉精靈顯然可以概括為一個矩形,由兩部分共同組成,看不見的Rect對象和看得見的表情圖片。Rect對象保存了橘子臉精靈的狀態(tài)數(shù)據(jù),表情圖片出現(xiàn)在游戲窗體的什么位置完全由Rect對象的數(shù)據(jù)決定,表情圖片只是外在的一層皮。9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
明白了大概任務,再來研讀預備工作的代碼就好理解了,細節(jié)如代碼9.4所示。與上一段游戲的大框架代碼相比,這里多導入了幾個locals模塊下的常量,如K_RIGHT等按鍵事件,主要用來實現(xiàn)橘子臉跟隨方向鍵移動。代碼9.4橘子臉游戲的預備階段importPygamefrompygame.localsimportQUIT,K_RIGHT,K_LEFT,K_UP,K_DOWNimportsys#預備工作pygame.init()WINDOW_WID,WINDOW_HEI=(640,480)surface=pygame.display.set_mode((WINDOW_WID,WINDOW_HEI))pygame.display.set_caption('Orangeface...')imgs=[]#保存橘子臉圖片的列表foriinrange(2):img=pygame.image.load('file\\Orange-'+str(i)+'.png')imgs.append(img)9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
代碼9.4橘子臉游戲的預備階段img_rect=pygame.Rect(0,0,32,32)face_change_limit=2000face_change_step=3face_id=0move_speed=1background=(255,255,255)#代表橘子臉的數(shù)據(jù)結(jié)構(gòu)#當face_change_limit按照face_change_step#的速度減為0時橘子臉會變換表情#按鍵移動的速度#背景色9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
在代碼文件所在位置的file文件夾下預備好兩個32像素×32像素的不同表情的圖片,為了方便,兩個圖片命名為Orange-0.png和Orange-1.png,這樣pygame.image.load()函數(shù)即可將它們載入。這兩個圖片被保存在imgs列表中,因此可以通過imgs[0]和imgs[1]訪問。有了橘子精靈的外在形象,還要有代表橘子精靈的內(nèi)在數(shù)據(jù)結(jié)構(gòu),因此通過Rect類定義一個矩形對象img_rect,它保存了矩形的左上角坐標及長寬共4項數(shù)據(jù)。img_rect對象會時刻記錄橘子精靈的位置,游戲一開始,橘子精靈出現(xiàn)在窗體的左上角,因此橫縱坐標都為0,長寬均為32像素,這是由圖片的尺寸決定的。后面在游戲的主循環(huán)結(jié)構(gòu)中會根據(jù)玩家按下不同的方向鍵來不斷更新img_rect的數(shù)據(jù),再根據(jù)img_rect不斷地繪制橘子精靈的外在圖片就可以實現(xiàn)移動效果了。9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
再接下來的幾行代碼初看似乎不易理解,其實也是完成一些循環(huán)之前的必要數(shù)據(jù)準備工作。因為橘子精靈不僅可以跟隨方向鍵移動,還在不停地變化表情。這就要求定期地更換顯示的表情圖片,一會兒是imgs[0],一會兒是imgs[1]。那么多長時間更換表情呢?這由兩個變量face_change_limit與face_change_step共同決定,變量face_change_limit每次減少face_change_step,當face_change_limit小于等于0時,就意味著該更換表情圖片了。因此在face_change_limit值一定的情況下,face_change_step的值越大,換表情的頻率就越快。變量move_speed定義了橘子精靈的移動速度,其實就是每次根據(jù)按鍵移動位置時,img_rect對象的坐標改變多少。改變得越大,一次按鍵移動的距離就越大。代碼的最后按照RGB顏色模型給出白色,這個顏色將被設置為游戲窗體的背景色。所有這些預備的變量都會在游戲主循環(huán)中用到,所以接下來看看主循環(huán)內(nèi)是如何利用這些變量實現(xiàn)預期效果的。9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
2.主循環(huán)階段游戲主循環(huán)的代碼實現(xiàn)如代碼9.5所示。在循環(huán)結(jié)構(gòu)中,首先通過surface.fill()方法將界面背景“填充”為指定顏色。每次循環(huán)都會執(zhí)行這行代碼,這意味著主窗口界面會不斷地被“填充”,之前界面上的畫面內(nèi)容就被白色覆蓋掉了,好比一面墻被重新粉刷,原來墻上的圖案就都不見了。因此橘子臉要重新繪制,每次繪制時都要判斷使用哪個表情圖片,是imgs[0]還是imgs[1]。這里就用到了face_change_limit與face_change_step兩個變量。face_change_limit每次循環(huán)會減少face_change_step,當face_change_limit由初始值2000降到小于等于0時,換表情的動作就該發(fā)生了。因此讓face_id加1,如果face_id原來是0,則現(xiàn)在為1;如果原來為1,則現(xiàn)在為2。之后將face_id除以2的余數(shù)賦給face_id本身??梢钥闯鼋?jīng)過這樣處理后,face_id的取值會在0和1之間交替變換,程序就是根據(jù)face_id去imgs列表中提取相應的表情的。因為對face_id值的處理并不是每次循環(huán)都發(fā)生,而是要face_change_limit從初始值減到0才發(fā)生一次。因此變量face_change_limit與face_change_step就控制了表情更替的速度,讀者可以在預備階段將face_change_step的值調(diào)大來試試效果。9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
代碼9.5橘子臉游戲的主循環(huán)階段whileTrue:foreventinpygame.event.get():ifevent.type==QUIT:pygame.quit()sys.exit()surface.fill(background)#重填窗體背景face_change_limit-=face_change_stepifface_change_limit<=0:#換臉發(fā)生face_change_limit=2000face_id+=1face_id%=29.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
代碼9.5橘子臉游戲的主循環(huán)階段surface.blit(imgs[face_id],img_rect)#根據(jù)最新位置繪制橘子臉#根據(jù)按鍵移動橘子臉
keys=pygame.key.get_pressed()#獲取被按下的鍵ifkeys[K_RIGHT]:
img_rect.x+=move_speed
ifkeys[K_LEFT]:
img_rect.x-=move_speed
ifkeys[K_UP]:
img_rect.y-=move_speed
ifkeys[K_DOWN]:
img_rect.y+=move_speed
#防止橘子臉移出窗體9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
代碼9.5橘子臉游戲的主循環(huán)階段ifimg_rect.x<0:img_rect.x=0ifimg_rect.x>WINDOW_WID-32:img_rect.x=WINDOW_WID-32ifimg_rect.y<0:img_rect.y=0ifimg_rect.y>WINDOW_HEI-32:img_rect.y=WINDOW_HEI-32pygame.display.update()9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
決定了使用的表情圖片,接下來的工作就是將表情圖片繪制在img_rect指定的位置。這是通過主窗口對象的blit()方法完成的,這個方法可以將游戲精靈的外在形象貼在窗體的指定位置。而img_rect對象記錄的位置是可以隨玩家操作方向鍵而變化的,循環(huán)內(nèi)接下來的代碼就是實現(xiàn)這個工作的。Pygame的key模塊下的get_pressed()函數(shù)可以獲知玩家按下了什么鍵。在該函數(shù)的返回結(jié)果中查看4個方向鍵是否被按下,并相應更新img_rect對象左上角的坐標。例如,如果玩家按下了向右的方向鍵,則keys[K_RIGHT]為真,因此img_rect的橫坐標增大,增大多少由move_speed決定,所以move_speed控制橘子臉的移動速度。最后還有一個問題需要考慮,橘子臉不能移到游戲窗口之外,因此每輪循環(huán)都要判斷img_rect中橫縱坐標的上下限。到此程序的邏輯就都實現(xiàn)了,將代碼9.4與代碼9.5合在一起運行,調(diào)整預備階段的一些變量值,觀察橘子臉的移動速度、換臉頻率等效果,可以加深對代碼的理解。如圖9.4所示是代碼運行后的結(jié)果。9.3.3完善游戲細節(jié)代碼
9.1
導入整個模塊
決定了使用的表情圖片,接下來的工作就是將表情圖片繪制在img_rect指定的位置。這是通過主窗口對象的blit()方法完成的,這個方法可以將游戲精靈的外在形象貼在窗體的指定位置。而img_rect對象記錄的位置是可以隨玩家操作方向鍵而變化的,循環(huán)內(nèi)接下來的代碼就是實現(xiàn)這個工作的。Pygame的key模塊下的get_pressed()函數(shù)可以獲知玩家按下了什么鍵。在該函數(shù)的返回結(jié)果中查看4個方向鍵是否被按下,并相應更新img_rect對象左上角的坐標。例如,如果玩家按下了向右的方向鍵,則keys[K_RIGHT]為真,因此img_rect的橫坐標增大,增大多少由move_speed決定,所以move_speed控制橘子臉的移動速度。最后還有一個問題需要考慮,橘子臉不能移到游戲窗口之外,因此每輪循環(huán)都要判斷img_rect中橫縱坐標的上下限。到此程序的邏輯就都實現(xiàn)了,將代碼9.4與代碼9.5合在一起運行,調(diào)整預備階段的一些變量值,觀察橘子臉的移動速度、換臉頻率等效果,可以加深對代碼的理解。如圖9.4所示是代碼運行后的結(jié)果。PART49.4小試牛刀9.4小試牛刀前面介紹了使用Pygame開發(fā)小游戲的基本流程,接下來在小試牛刀環(huán)節(jié)開發(fā)一款可玩性更強的小游戲。游戲中有一個小球在窗口中“游蕩”,玩家通過左右移動鼠標指針控制一個擋板,任務是不讓小球掉落到窗口下沿。隨著玩家擊打小球次數(shù)的增多,小球的移動速度會越來越快,游戲難度越來越高。當小球掉落游戲結(jié)束時,窗口中顯示玩家的擊球次數(shù)。如果玩家愿意,則單擊開始新的一局。游戲運行的效果如圖9.5所示。9.4.1游戲預備工作游戲需要準備小球與擋板的圖片素材,這個例子中使用的小球圖片有8像素,擋板的寬度為55像素。游戲代碼假設這些圖片素材位于file文件夾下。整個程序仍然分為主循環(huán)之前的預備工作和while主循環(huán)兩部分,先來看預備工作階段,細節(jié)如代碼9.6所示。代碼被空行分成幾個小段落,后面會詳細解釋。(代碼見書213頁9.6)9.4.2游戲主循環(huán)所有預備工作做完后,接下來是在while循環(huán)中不斷更新的工作。這些工作構(gòu)成了代碼9.7,循環(huán)內(nèi)的代碼也被空行分成了幾個段落,下面進行逐一分析。(代碼見書215頁9.7)PART59.5拓展實踐:使用模塊組織代碼當程序稍具規(guī)模后,可以根據(jù)功能將代碼分布在多個文件中,每個代碼文件就是一個模塊。程序由多個模塊組成,必然會出現(xiàn)在某個模塊中導入其他自定義模塊的情形。接下來就以一個具體的例子演練如何使用模塊組織代碼。9.5拓展實踐:使用模塊組織代碼在人類的民主活動中投票是一個重要的形式。常用的投票形式往往是選民人手一票,每個人將自己的一票投給自己支持的候選人,獲得票數(shù)多的候選人勝出。除此以外有沒有其他的投票模式呢?1770年,法國舉辦了一場選舉,選舉規(guī)則是每一個選民擁有一張選票,并按對每位候選人的認可程度,在選票上給所有候選人進行排序。作為選票結(jié)果的統(tǒng)計者,數(shù)學家波達設想的方法是將選票上的位次轉(zhuǎn)換為分值,這樣可把每一張選票上的候選人排名名次轉(zhuǎn)換成一串相應的數(shù)字。例如,在有4個候選人的選舉中,排在第1名的候選人因為擊敗了3個人所以得3分。同理第2名得2分,第3名得1分,第4名得0分。然后把所有選票上每個候選人各自得到的分數(shù)全部加在一起,最后累計得分最高者獲勝。這種方法被命名為波達計數(shù)法(Bordacount)。9.5.1多樣的投票模式再來看看同一時期的另一位法國數(shù)學家孔多塞(Condorcet)提出的一種計票方式。每個候選人都要和其他候選人進行兩兩的對決。在兩人對決時,每一票都屬于排名靠前的候選人,最終獲得選票多的人在兩人對決中勝出。只有在兩兩對決的過程中戰(zhàn)勝所有其他候選人的才是最終獲勝者??锥嗳嬈狈梢赃x出被大多數(shù)人擁護的選舉人,但也有可能因為沒人能戰(zhàn)勝所有其他人,從而導致沒有獲勝者。9.5.1多樣的投票模式假設有4位候選人,名字分別為A、B、C、D。關于這場選舉的投票數(shù)據(jù)被保存在一個文本文件中,文件的每一行代表一個投票人的投票結(jié)果。這個結(jié)果是投票人對4位候選人的一個排位順序,使用空格分隔,如下(數(shù)據(jù)有刪減)。9.5.2一個具體的投票問題下面按照多數(shù)計票法、波達計票法和孔多塞計票法3種投票方式來對同一個投票問題進行處理,看看結(jié)果有何不同??梢钥闯觯瑹o論使用何種計票方法,整個計票的工作大致如下:首先,讀入投票數(shù)據(jù)。其次,匯總投票數(shù)據(jù)。根據(jù)不同計票方法,匯總過程不盡相同。例如,對于多數(shù)計票法,每個選民的投票只有排在第一順位的候選人有效,而波達計票法則要將每個選民的排位換算為相應的分數(shù)??锥嗳嬈狈▌t關注候選人兩兩對決的勝負。無論采用哪種計票方法,最后都要根據(jù)匯總結(jié)果,給出選舉結(jié)論。因此計票工作可以分成幾個函數(shù)來完成。(1)負責讀入投票數(shù)據(jù)的read_vote_data()函數(shù)。(2)按照不同計票方法進行匯總的幾個函數(shù)。(3)根據(jù)匯總結(jié)果給出選舉結(jié)論的give_vote_result()函數(shù)。這些函數(shù)可以被分在不同的模塊中,按照不同計票方式進行匯總的函數(shù)可以統(tǒng)一放在一個模塊中,這個模塊文件可以命名為vote_methods.py。而read_vote_data()函數(shù)和give_vote_result()函數(shù)可以放在同一個模塊中,可以命名為vote_tools.py。主程序則寫在第三個代碼文件中,這樣整個程序被分為3個模塊,主程序所在的模塊需要導入另外兩個模塊。為了演示方便,這里假設所有的代碼文件都在同一個目錄下。9.5.2一個具體的投票問題先來看vote_tools模塊。這個模塊下有兩個函數(shù),分別是讀入選舉數(shù)據(jù)的read_vote_data()函數(shù)和根據(jù)計票匯總結(jié)果給出選舉結(jié)論的give_vote_result()函數(shù)。read_vote_data()函數(shù)的細節(jié)如代碼9.8所示。這個函數(shù)需要知道保存投票數(shù)據(jù)的文件名,而其返回值則是一個嵌套的列表,該列表的每一個元素是原數(shù)據(jù)文件中的一行按照空格拆分后的列表。代碼9.8模塊vote_tools中負責讀取數(shù)據(jù)的函數(shù)defread_vote_data(vote_file):"""讀取投票數(shù)據(jù)的函數(shù)參數(shù)vote_file:文件名9.5.3模塊vote_tools代碼9.8模塊vote_tools中負責讀取數(shù)據(jù)的函數(shù)返回值:嵌套列表"""vote_data=[]withopen(vote_file,"r")asf:forlineinf:one_vote=line.split()#按空格拆分vote_data.append(one_vote)returnvote_data9.5.3模塊vote_toolsgive_vote_result()函數(shù)的細節(jié)如代碼9.9所示。這個函數(shù)需要拿到匯總的結(jié)果aggregate_dic,這是一個字典,記錄了每個候選人的得票情況,或者是得分,又或者是該候選人戰(zhàn)勝的對手數(shù)量。也就是說盡管有多種計票方式,但每種計票方式最后匯總的結(jié)果都是一個字典,該字典會傳遞給give_vote_result()函數(shù)。因為孔多塞計票法的規(guī)則導致其有可能沒有獲勝者,所以處理細節(jié)略有不同。give_vote_result()函數(shù)還有一個參數(shù),用來判斷當前使用的計票方式是否為孔多塞計票法,該參數(shù)的默認值為假。give_vote_result()函數(shù)的返回值有兩項,一項是記錄最終獲勝候選人的列表winner,另一項是這些勝者的得票或得分情況。如果是孔多塞計票法,則winner有可能為空。9.5.3模塊vote_tools代碼9.9模塊vote_tools中負責給出選舉結(jié)論的函數(shù)defgive_vote_result(aggregate_dic,condorcet=False):"""根據(jù)匯總結(jié)果,給出最終投票結(jié)論"""winner=[]ifcondorcet:highest_vote=len(aggregate_dic)–1#戰(zhàn)勝所有其他候選人forcandidateinaggregate_dic.keys():ifaggregate_dic[candidate]==highest_vote:winner.append(candidate)break9.5.3模塊vote_tools代碼9.9模塊vote_tools中負責給出選舉結(jié)論的函數(shù)else:highest_vote=0forcandidateinaggregate_dic:#出現(xiàn)一個候選人比之前所有人的票數(shù)都高ifaggregate_dic[candidate]>highest_vote:winner.clear()winner.append(candidate)highest_vote=aggregate_dic[candidate]elifaggregate_dic[candidate]==highest_vote:#出現(xiàn)并列最高票數(shù)的候選人winner.append(candidate)returnwinner,highest_vote9.5.3模塊vote_tools下面再來看vote_methods模塊,這里保存著實現(xiàn)3種計票法細節(jié)的函數(shù),其中孔多塞計票法還需要一個輔助函數(shù),因此一共是4個函數(shù)。先來看實現(xiàn)多數(shù)計票法的函數(shù)細節(jié),如代碼9.10所示。其中,參數(shù)vote_data為模塊vote_tools下的read_vote_data()函數(shù)讀取數(shù)據(jù)文件后返回的嵌套列表。多數(shù)計票法返回的字典記錄著每個候選人的得票數(shù),也就是每個候選人位于第一順位的票數(shù)。9.5.4模塊vote_methods代碼9.10模塊vote_methods中的多數(shù)計票法defaggregate_by_plurality(vote_data):aggregate_dic={}#匯總每個候選人票數(shù)的字典forcandiateinvote_data[0]:#使用第一個投票人的數(shù)據(jù)初始化字典aggregate_dic[candiate]=0forvoteinvote_data:aggregate_dic[vote[0]]+=1returnaggregate_dic9.5.4模塊vote_methods代碼9.11實現(xiàn)了波達計票法,此時函數(shù)返回的字典記錄的是每個候選人的總得分。對于每一張選票,候選人的得分是排在其后的候選人的個數(shù)。將所有選票的得分相加即為該候選人的總得分。代碼中第二個for循環(huán)的vote變量代表每一張選票,實際上是一個列表。候選人在vote列表中的位次決定了其在這張選票上能得多少分。9.5.4模塊vote_methods代碼9.11模塊vote_methods中的波達計票法defaggregate_by_borda(vote_data):aggregate_dic={}forcandiateinvote_data[0]:aggregate_dic[candiate]=0candidates_count=len(vote_data[0])#候選人數(shù)量forvoteinvote_data:i=1forcandidateinvote:#排在幾個候選人之前,就得幾分aggregate_dic[candidate]+=candidates_count-ii+=1returnaggregate_dic#字典記錄每個候選人的得分9.5.4模塊vote_methods模塊vote_methods的最后是實現(xiàn)孔多塞計票法的代碼9.12,這段代碼包含兩個函數(shù)。第一個函數(shù)直接實現(xiàn)孔多塞計票法,其返回的字典記錄的是每個候選人戰(zhàn)勝其他候選人的數(shù)量,這個數(shù)量要達到所有候選人數(shù)量減1才算是贏家。因為需要頻繁進行兩個候選人之間的比較,所以代碼將其獨立成一個輔助函數(shù)onevsone(),輔助函數(shù)在完成兩個候選人之間的對決時,關注的是二者在每一張選票內(nèi)誰排在前面。因為兩兩對決就好比暫時只有兩個人參加的一個選舉,哪位候選人排在前面,該張選票就記給誰。(代碼見書220頁9.12)9.5.4模塊vote_methods主程序所在的模塊vote_main先導入前面介紹的兩個模塊,并為之起了簡短的別名。接下來是負責串場的main()函數(shù),它根據(jù)選舉數(shù)據(jù)文件的內(nèi)容,以及當前采用的計票方法來調(diào)用其他兩個模塊中的相應函數(shù)得出結(jié)果。代碼細節(jié)如代碼9.13所示。(代碼見書221頁)9.5.5導入自定義的模塊本章總結(jié)本章講解了Python模塊與包的概念,并通過一個具體的例子演示了使用模塊組織代碼;介紹了第三方庫的安裝方法,以及導入模塊的多種形式;還介紹了使用Pygame進行游戲開發(fā)的基礎知識。匯報人:WPS程序設計基礎第十章面向?qū)ο缶幊棠夸?1面向?qū)ο蠛喗?2類、對象與封裝03繼承與多態(tài)04Python生態(tài)系統(tǒng)之tkinter庫目錄05小試牛刀06拓展實踐:試一試面向?qū)ο缶幊?
理解封裝、繼承與多態(tài)等概念。.理解Python對象實例化的過程。.初步掌握類的定義與使用方法。.使用tkinter庫設計簡單的圖形界面。。學習目標PART110.1面向?qū)ο蠛喗?0.1面向?qū)ο蠛喗槠鋵嵲谇懊娓髡率褂肞ython庫的過程中已經(jīng)不知不覺地使用了面向?qū)ο缶幊痰囊恍└拍?。例如,Python內(nèi)置的open()函數(shù)可以返回一個代表磁盤文件的對象,這個對象有很多關于文件操作的方法、屬性,使用它來操控磁盤文件方便很多。又如,Python的字符串也因為擁有一套字符串的方法而變得鮮活起來。那么到底什么是面向?qū)ο蟮某绦蛟O計呢?它和結(jié)構(gòu)化程序設計有什么區(qū)別呢?在馮·諾依曼的計算機體系結(jié)構(gòu)下,程序的運行本質(zhì)就是內(nèi)存中數(shù)據(jù)的演化。程序的輸入是數(shù)據(jù)的初始狀態(tài),程序的輸出是數(shù)據(jù)的結(jié)束狀態(tài),而程序的運行過程是數(shù)據(jù)的演變過程。無論哪種程序設計理念,不過是提供了一種看待內(nèi)存中數(shù)據(jù)演變的視角。10.1面向?qū)ο蠛喗榻Y(jié)構(gòu)化編程將程序運行看成一個過程,有一個“上帝之手”在按照某種算法一步步地執(zhí)行。每執(zhí)行一步,內(nèi)存中的數(shù)據(jù)會跟著改變,直到最后一步完成,程序運行結(jié)束,此時內(nèi)存中數(shù)據(jù)的狀態(tài)就是程序的輸出結(jié)果。在這種理念中,數(shù)據(jù)是“魚肉”,算法為“刀俎”,“上帝之手”拿著“刀俎”來修改“魚肉”。作為魚肉的數(shù)據(jù)是被動地等待著被修改,刀俎和魚肉當然是分開的,即構(gòu)成程序的兩個要素算法和數(shù)據(jù)是分開的。這種理念有什么問題呢?問題在于實際上并沒有“上帝之手”,真正拿著刀俎的是程序員,而程序員是人,會犯各種錯誤。尤其當軟件要解決的問題越來越復雜時,拿著刀俎的程序員會感到越來越無力。面向?qū)ο蟪绦蛟O計就不同了,在面向?qū)ο蟮睦砟钪袛?shù)據(jù)和對數(shù)據(jù)的操作合體了,它們結(jié)合在一起形成對象。數(shù)據(jù)是對象的屬性,對數(shù)據(jù)的操作是對象的方法。數(shù)據(jù)和對數(shù)據(jù)的操作不再是魚肉和刀俎的關系,也沒有一個“上帝之手”在拿著刀俎操作魚肉。數(shù)據(jù)與對數(shù)據(jù)的操作現(xiàn)在構(gòu)成了一個有機的整體——對象。數(shù)據(jù)賦予對象血肉,方法賦予對象生命的活力。內(nèi)存中的對象就像一個個生命體,它們彼此之間可以溝通、可以交流合作,在程序員的指揮下共同完成程序運行的任務。在這過程中不斷地有對象產(chǎn)生、消亡,恰如人類社會,而程序員則好比是這場大戲的導演。10.1面向?qū)ο蠛喗槊嫦驅(qū)ο缶幊毯徒Y(jié)構(gòu)化編程最大的不同在于面向?qū)ο缶幊谈诵曰?,使程序的結(jié)構(gòu)更接近人類社會的結(jié)構(gòu)。計算機的出現(xiàn)帶來了一個虛擬的世界,虛擬世界中的很多看似很深奧的理論都來源于真實世界,甚至就是簡單的生活常識,如計算機系統(tǒng)中的緩存系統(tǒng)。面向?qū)ο罄砟钜彩翘摂M向真實學習的結(jié)果。在真實的人類社會中,不論是一個個自然人,還是由自然人組成的團體、企業(yè)、機關,所有這些形成了一個極其復雜的系統(tǒng),但這個系統(tǒng)卻能有條不紊地運行著。人類社會是如何做到這一點的呢?答案是兩個字:對象。不管是自然人,還是團體、企業(yè)、機關等,都是一個個具體的對象。每個對象都有自己的一些特征和功能。一個對象的特征決定了它自身的一些基本屬性,存儲了與它自身相關的一些信息;一個對象的功能則決定了它能做什么。例如,自然人有名字、身高、體重等特征,還有一些功能,也就是能力。10.1面向?qū)ο蠛喗槔?,一個人是醫(yī)生,他就有看病的能力。每個對象調(diào)用其他對象提供的功能時不必知道該功能的實現(xiàn)細節(jié)。學生去教室上課,不必操心教室是如何安排才不會沖突的;打開教室的電燈,不必知道電來自哪個發(fā)電廠;預訂飛機票,只需向航空售票系統(tǒng)說明需求,不用操心票是如何定下來的;自動柜員機是如何正確地出鈔的,它內(nèi)部的點鈔模塊是如何實現(xiàn)的,這些作為使用者都無須知道。實際上使用者只要知道柜員機的接口是什么樣子的就可以了,這里的接口就是柜員機的插卡口、出鈔口、屏幕等。從以上描述可以看出,面向?qū)ο蟮幕纠砟钍敲總€對象都把自己的實現(xiàn)細節(jié)封裝起來,只把接口展現(xiàn)給外部世界,其他對象在和這個對象交流時只需知道如何使用該對象的接口即可。這就是面向?qū)ο缶幊痰囊粋€基本理念——封裝。10.1面向?qū)ο蠛喗椴粌H如此,人類社會還為那些有著相同特征或功能的對象歸了類,如學校就是一類對象。社會上有千千萬萬所學校,每所學校都是一個具體的對象,但它們有一些共同的特征和功能,所以人們將它們歸為一類,定義為學校,這樣學校就成為一個類(class)。學校又可細分為小學、中學、大學等,即一個父類又分出子類,子類會繼承父類的特征。某某大學可以被認為是一所(isa)大學(子類),也可以籠統(tǒng)地被認為是一所學校(父類)。當已知某單位是一所學校時,就意味著這個單位應該有教室、教師、學生等所有學校都有的共性的東西,但此時并不能確定這個單位一定是一所大學,所以還不能說這個單位一定有系或下屬學院等大學這個子類才有的特征。10.1面向?qū)ο蠛喗檎且驗橛辛祟悾F(xiàn)實世界才有序。雖然事物眾多,形形色色,但各屬其類。人們到一個陌生的城市也能輕車熟路地使用新城市的公交系統(tǒng),因為公交系統(tǒng)就是一個類,這個類有什么樣的特征和功能是比較明確的。各城市的公交系統(tǒng)不過是這個類的一個個具體的對象而已。人們通過自己城市的公交系統(tǒng)認識的不僅僅是一個公交系統(tǒng),而是一類對象。當然,不同城市的公交系統(tǒng)在具體實現(xiàn)一些功能時可以有自己的特色。例如,票價問題,小的城市可以無人售票,不論路途遠近,一律一元;大的城市可以按路途遠近分段制定票價,同樣的收費行為卻有不同的實現(xiàn)細節(jié)。又如,自然界中存在大量的動物(父類),動物又分為爬行動物、哺乳動物、兩棲動物等(子類)。一個動物應該有一種能力,那就是移動。但不同子類的動物在實現(xiàn)父類的這種能力時,實現(xiàn)細節(jié)又是多樣的,有的爬,有的游,有的奔跑,有的飛翔。父類定義的同一種行為子類卻有不同的實現(xiàn)方式,這種現(xiàn)象在面向?qū)ο缶幊讨蟹Q為多態(tài)。10.1面向?qū)ο蠛喗榭偨Y(jié)下來,面向?qū)ο缶幊虖默F(xiàn)實世界汲取了3個主要思想:封裝、繼承和多態(tài),并將這些思想搬到了計算機程序的虛擬世界中。當一個程序沒有運行時,它就是躺在磁盤文件里的一堆代碼。一旦程序運行起來,就會在內(nèi)存中生成眾多的變量。程序要達成目標,就要對這些變量中的數(shù)據(jù)做一系列的操作。面向?qū)ο缶幊滩贿^是以對象的視角來看待內(nèi)存中的變量而已。也就是說,如果將一個程序的內(nèi)存空間看成一個虛擬社會,那么程序運行時的眾多變量好比虛擬社會中的一個個對象。每個對象(變量)都有其自身的一些特征(屬性)和功能(方法)。虛擬社會中的眾多對象同樣也被劃分成不同的類型,這就是變量的數(shù)據(jù)類型。這些對象中有比較簡單的,如數(shù)值型、字符串、列表、字典等,好比現(xiàn)實世界中的自然人對象;也有比較復雜的,它們的類型是程序員根據(jù)需要定義的類,好比現(xiàn)實世界中的學校、銀行、超市等人類創(chuàng)造的機關、單位。在面向?qū)ο缶幊讨?,變量不再僅僅是存放待處理數(shù)據(jù)的容器,而是一個個活生生的對象,不僅有數(shù)據(jù)屬性還有能對數(shù)據(jù)進行操作的方法。這些方法、屬性使這些變量有了生命,可以和其他變量交互,每個變量可以為程序中的其他變量服務,而且調(diào)用這種服務無須知道服務實現(xiàn)的細節(jié)。隨著程序的運行,內(nèi)存空間中的這些變量生生滅滅,當程序退出時,這個虛擬的社會就消亡了。表10.1列出了現(xiàn)實世界和虛擬世界的對照關系。10.1面向?qū)ο蠛喗楸?0.1現(xiàn)實世界和虛擬世界的對照關系現(xiàn)實世界虛擬世界(內(nèi)存空間)千千萬萬的事物千千萬萬的變量事物有類別(動物、植物、微生物等)變量有類型(數(shù)值、字符串、列表、自定義的類等)同一類別的事物有共同的特點同一類型的變量有共同的特點除了自然界本就存在的事物,人類還發(fā)明、定義了很多事物,如學校,醫(yī)院、各種交通工具等眾多的人類社會的對象除了程序語言本就定義好的一些變量類型,程序員還可以根據(jù)具
體的需要發(fā)明、定義一些特別的變量類型。程序員可以先定義一個類,在定義中說明這個類有什么樣的特征
和功能;然后根據(jù)這個類去創(chuàng)建一個個具體的對象供程序使用PART210.2類、對象與封裝
10.2.1定義一個類如前所述,類定義了一個新的數(shù)據(jù)類型,將數(shù)據(jù)及相關操作封裝在了類的定義中,根據(jù)類生成的具體對象都擁有類定義所規(guī)定的屬性特征和能力。在Python中使用class關鍵字定義類,類名一般使用首字母大寫的形式,類名之后跟一個冒號,之后是實現(xiàn)類的內(nèi)部細節(jié)的代碼。下面的代碼10.1定義了一個Animal類。代碼10.1使用class關鍵字定義類classAnimal:definit(self,name,weight):=nameself.weight=weightdefmove(self):print(f"我是{},我真的在動,不騙你。")a1=Animal('老鷹',10)#由Animal類生成具體的對象10.2.1定義一個類代碼10.1使用class關鍵字定義類a2=Animal('老虎',280)print(f'a1是{},體重{a1.weight}千克。')print(f'a2是{},體重{a2.weight}千克。')a1.move()Animal.move(a2)10.2.1定義一個類從代碼10.1可以看出,類的定義就是要闡明這類事物所擁有的屬性和方法(能力)。屬性一般需要說明初始值,這是通過特殊的__init__()方法實現(xiàn)的。類的方法其實就是定義在類中的函數(shù),同樣需要有參數(shù)、函數(shù)體等。特別一點的是,Python類的方法都有一個特殊的參數(shù)self,而且是方法的第一個參數(shù)。self參數(shù)指由這個類生成的對象自己,要理解這一點需要了解對象的創(chuàng)建過程。下面來看由類生成具體對象的過程細節(jié)。10.2.2對象實例化過程以代碼10.1為例,定義好Animal類之后,就可以使用a1=Animal('老鷹',10)這種形式去生成一個具體的Animal對象并保存在a1變量中。在這個語句背后其實有一個比較復雜的過程。Python為每一個類指定兩個特殊的方法,一個是__new__(),在代碼10.1中沒有出現(xiàn)。另一個是代碼10.1中出現(xiàn)的__init__()方法。即使在定義類時沒有寫出這兩個方法,它們在類中也是存在的。這兩個方法的名稱比較古怪,前后各有兩個下畫線。Python語言有很多類似這種名稱的方法,它們都有特殊用途,因此不建議程序員將自己代碼中的方法如此命名。這兩個方法負責對象的誕生,具體來說,__new__()方法負責從無到有將一個對象創(chuàng)建,因此__new__()方法不需要self參數(shù),因為self參數(shù)指代對象本身,而在調(diào)用__new__()方法時,對象還沒有誕生,要等__new__()執(zhí)行完畢后對象才會誕生。方法__new__()執(zhí)行完畢后會緊跟著執(zhí)行類的__init__()方法,通過執(zhí)行__init__()方法中的代碼來對剛剛誕生的對象的屬性進行初始化。因此每一個對象的實例化過程都要執(zhí)行類的兩個方法:__new__()和__init__()。10.2.2對象實例化過程在代碼10.1中,雖然沒有書寫__new__()方法,但當程序執(zhí)行a1=Animal('老鷹',10)時,仍然會調(diào)用Animal類的__new__()方法造出一個具體的對象,之后就會調(diào)用__init__()方法初始化這個對象。Animal類的__init__()方法規(guī)定每一個Animal對象應該有兩個屬性,初始化對象時會接收兩個參數(shù)值name、weight,然后將它們賦給對象本身的屬性name和weight。為了將上述這個過程描述清楚就要用到self參數(shù)了。在代碼=name中,雖然左右各有一個name,但這兩個name的含義截然不同。右側(cè)的name是__init__()方法收到的參數(shù)值,而左側(cè)的則是“對象的name屬性”的意思,這里self特指剛剛誕生的這個對象。因此這種寫法相當于聲明了Animal對象都有name屬性。而且name與weight兩個屬性要在對象剛剛誕生后就立刻賦值,這就意味著在實例化Animal對象時必須寫成a1=Animal('老鷹',10)的形式,而不能寫成a1=Animal(),因為實例化過程對應__new__()和__init__()兩個方法,在執(zhí)行__init__()方法時需要傳遞兩個參數(shù)值。10.2.2對象實例化過程雖然從執(zhí)行__init__()方法時刻開始后面所有的類方法都需要有一個self參數(shù),但通常這個參數(shù)并不需要程序顯式地給它賦值。因為一般情況程序是使用對象名來調(diào)用對象的方法的,如a1.move(),其含義當然是調(diào)用a1對象的move()方法,不可能是其他的對象。所以此時Python會自動將a1傳遞給move()方法的self參數(shù)。但Python也允許直接通過類名來調(diào)用對象的方法,如代碼10.1中最后的10.2.2對象實例化過程Animal.move(a2),此時就需要顯式地為move()方法的self傳遞參數(shù)值,也就是a2,這樣move()方法中的就是a2的name,因此輸出的名稱是老虎。10.2.3訪問控制面向?qū)ο缶幊汤砟畹囊粋€基本想法是封裝,程序?qū)⒑芏嗉毠?jié)封裝在類中,其中一些細節(jié)是不需要也不希望外界知道的。外界只需調(diào)用對象公開展示的一些方法、屬性即可,就像生活中使用的自動柜員機一樣。這就要求在類的定義中能表明哪些屬性、方法是公開給外界調(diào)用的,哪些屬性、方法是類在自己內(nèi)部使用的,不希望外界知道,更不希望外界直接使用這些私有的屬性和方法。為了達到對類的屬性、方法的訪問控制,諸如Java和C#等面向?qū)ο笳Z言中有一套類似private、protect、public的訪問控制關鍵字,使用這些關鍵字可以聲明類的哪些方法是公開的(Public),可以給外部使用者調(diào)用;哪些是私有的(Private),只能類在自己內(nèi)部使用。但Python沒有這幾個關鍵字,而是通過給方法、屬性的名稱前加一個或兩個下畫線的方式來暗示這些方法或?qū)傩允撬接械?,沒有特殊理由不要在類的外界直接訪問它。10.2.3訪問控制下面的代碼10.2定義了一個歷史名人類,將有關的姓名、年份及備注等信息封裝到這個類中。其中,年份屬性以1個下畫線開頭,而備注屬性以2個下畫線開頭,以此暗示這兩個屬性不希望在類的外部直接訪問,類提供了專門訪問這兩個屬性的方法,分別是get_note()方法和get_year()方法。其中,get_year()方法會根據(jù)年份數(shù)值的正負進行一些必要的處理,這樣可以避免展示負數(shù)年份。這也是不希望外界直接使用年份屬性的一個原因。10.2.3訪問控制代碼10.2使用下畫線暗示私有屬性classHistoryPerson():definit(self,name,year,note=""):=nameself._year=year#1個下畫線開頭self.note=note#2個下畫線開頭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()方法訪問了對象的相應屬性,但最后兩行代碼違背了下畫線的暗示,嘗試直接訪問年份與備注兩個屬性。根據(jù)實際運行的結(jié)果可知,1個下畫線開頭的年份屬性在類的外部直接訪問也是可以的,但訪問2個下畫線開頭的備注屬性卻會報錯。這就是以1個下畫線和2個下畫線作為暗示的區(qū)別,Python解釋器不對1個下畫線開頭的屬性做任何特殊處理,這個下畫線是只給暗示的。但對于以2個下畫線開頭的屬性,Python解釋器會做一些控制,雖然這個控制其實很容易繞過去??傮w來說,Python對于屬性、方法的訪問控制沒有強制要求,而是交給程序員自行決定。PART310.3繼承與多態(tài)10.3繼承與多態(tài)面向?qū)ο蟮牧硗鈨蓚€理念是繼承與多態(tài)。它們都可以有效地消除代碼中重復的部分,將相同、類似的代碼塊提取出來放到更高一層的父類中去。先來看看繼承,它可以定義一種“isa”關系。10.3.1繼承的基本形式Python中所有的類都有一個共同的祖先:object類。注意這是一個類,類名為object。Python中所有的類都是從object類派生而來的。當一個類在定義時沒有明確指明自己的父類時(如代碼10.1中的Animal類),其父類就是object類。那么如何在定義類時明確指明其父類呢?代碼10.3在Animal類存在的情況下定義了幾個更細分的子類。10.3.1繼承的基本形式代碼10.3類的繼承#父類classAnimal:definit(self,name,weight):=nameself.weight=weightdefmove(self):print(f"我是{},我真的在動,不騙你。")#子類classFish(Animal):pass#雖然只有一個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類的繼承#實例化子類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('你好,我是一個人。')elifisinstance(ani,Bird):ani.sing()ani.move()print(f"我是Animal類的實例嗎:{isinstance(ani,Animal)}")print()代碼10.3的運行結(jié)果如下。我是小丑魚,我真的在動,不騙你。我是Animal類的實例嗎:True我是一只小小鳥,想要飛卻飛不高。我是小小鳥,一只鳥,我可以振翅高飛。我是Animal類的實例嗎:True我是響尾蛇,我真的在動,不騙你。10.3.1繼承的基本形式代碼10.3的運行結(jié)果如下。我是小丑魚,我真的在動,不騙你。我是Animal類的實例嗎:True我是一只小
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024年中國天然金剛石洗石筆市場調(diào)查研究報告
- 上海工商職業(yè)技術學院《建筑給排水工程》2023-2024學年第一學期期末試卷
- 有關化學方面課程設計
- 機械設計基礎課件 模塊1 相關力學基礎知識
- 昆蟲主題美育課程設計
- 智慧單車課程設計意圖
- 幼兒園雪娃娃課程設計
- 幼兒紗巾活動課程設計
- 山東xx區(qū)域性養(yǎng)老服務中心項目可行性研究報告
- 巖土錨固課程設計
- 課件-5.1認識人工智能
- 婦產(chǎn)科學智慧樹知到課后章節(jié)答案2023年下浙江大學
- 房地產(chǎn)樓盤介紹ppt
- 游戲:看表情符號猜成語PPT
- 精益日常管理DM
- 2022年廣州市高中信息技術學業(yè)水平上機考試
- 材料、配件及設備進場驗收檢查記錄
- 六年級上冊英語-選擇題-人教pep(含答案)
- 《散文創(chuàng)作與研究》(10543)自考考試復習題庫(含答案)
- 穩(wěn)定系數(shù)計算表
- 2023年藥學考試-執(zhí)業(yè)藥師(中藥)考試歷年真題集錦后附答案
評論
0/150
提交評論