本文翻自:Allen B. Downey ——《Think Python》 原文連接:http://www.greenteapress.com/thinkpython/html/thinkpython005.html 翻譯:Simba Gu
[自述:感受從這一章開始算是有點「乾貨」了,很容易激起初學者的興趣,想當年上學的時候就是老師隨便寫的一個循環語句讓兩個大於號「>>」沿着屏幕繞圈就像貪吃蛇同樣,吸引了我而且讓我入了寫程序這個「坑」]
本章示例代碼能夠從這裏下載:
http://thinkpython.com/code/polygon.pyhtml
爲了配合本書,我寫了一個Swampy包,你能夠從這裏下載並按照上面的說明安裝到你的系統:http://thinkpython.com/swampy
包是一個模塊的集合,TurtleWorld就是Swampy裏面的一個模塊,它提供了一些引導小海龜在屏幕上畫線的函數集。你只要在系統中安裝了Swampy包就能夠導入TurtleWorld模塊:
python
from swampy.TurtleWorld import *
若是你下載了Swampy包,可是沒有安裝,你能夠在Swampy的根目錄調試程序,或者把該目錄添加到Python能夠搜索的路徑中去,而後再這樣導入TurtleWorldlike :
編程
from TurtleWorld import *
安裝過程和Python搜索路徑的設置,取決於你的系統,本書就不做具體描述,若有疑問請參考此連接:http://thinkpython.com/swampy
創建一個文件mypolygon.py,輸入下面的代碼:
小程序
from swampy.TurtleWorld import * world = TurtleWorld() bob = Turtle() print bob wait_for_user()
第一行代碼表示從swampy包導入TurtleWorld 模塊。接下來一行創建一個TurtleWorld 對象和Turtle對象分別賦值給變量world和bob。打印bob變量你會看到相似這樣的結果:函數
<TurtleWorld.Turtle instance at 0xb7bfbf4c>
這表示bob變量引用了TurtleWorld 模塊的一個實例 Turtle。從上下文中能夠看出,「實例」表示集合的一個成員,這裏的Turtle實例能夠是一組或者集合中的一個。
這裏的 wait_for_user 告訴TurtleWorld 等待用戶下一步操做,儘管在這個案例中除了等用戶關閉窗口以外並沒有其它。
TurtleWorld 提供了一些turtle轉向的函數:fd和bk用於前進和後退,lt和rt用於左右轉彎。而且每個Turtle對象都有一支「筆」,能夠落下或提起,若是筆落下,當Turtle對象移動的時候就會留下痕跡。函數pu和pd分別表示「提筆」和「落筆」。
下面的代碼能夠畫一個直角(代碼放在創建的bob對象和 wait_for_user 函數之間): 學習
fd(bob, 100)
lt(bob)
fd(bob, 100)
第一行代碼讓bob向前移動100,第二行讓它向左轉彎。當你運行程序的時候,你就能夠看到bob向東向北移動並留下了運行軌跡。
請修改代碼畫一個正方形。在實現此功能以前請不要繼續本章內容!測試
你可能寫了這樣的代碼(此處省略了創建TurtleWorld 對象和調用wait_for_user 函數)字體
fd(bob, 100) lt(bob) fd(bob, 100) lt(bob) fd(bob, 100) lt(bob) fd(bob, 100)
咱們還能夠用for循環語句來實現重複的功能。請添加如下代碼到mypolygon.py腳本文件並運行:spa
for i in range(4): print 'Hello!'
你應該能夠看到下面的結果:翻譯
Hello!
Hello!
Hello!
Hello!
這個例子裏面用到了for語句,後面咱們還會看到更多。可是這足以讓你從新編寫畫正方形的程序,下面就是for語句實現的代碼:
for i in range(4): fd(bob, 100) lt(bob)
for語句的語法相似函數定義,它有一個以冒號結尾的頭和縮進的正文,正文內能夠包含任意數量的語句。
for語句有時被稱爲循環,由於執行流程通過正文處理以後又回到循環的頂部。在這個案例中,正文內容被執行了4次。
這裏的代碼跟以前畫正方形的代碼有些不一樣,由於在畫出了正方形以後又進行了一次轉彎,這樣會花費額外的處理時間,可是若是是重複的動做,這樣會簡化代碼,並且for循環的這個版本讓Turtle回到原點以後也恢復了初始的方向。
下面是TurtleWorld系列的一些練習,這些原本是爲了好玩,可是其中也不乏一些編程思想。當你在練習的時候請思考一下重點是什麼。
本書提供了下面練習的解決方案,你能夠先嚐試一下,不要直接抄答案。
1. 編寫一個名爲square的函數,它傳遞一個名爲 t 的turtle對象參數,實現用turtle對象畫一個正方形。編寫一個函數調用,將bob做爲參數傳遞給square,而後再次運行程序。
2. 在square函數添加另外一個名爲length的參數。修改函數內容,實現所畫正方形的邊長度爲length,而後修改函數調用,加入第二個參數,再次運行程序。使用必定長度範圍的值來測試程序。
3. 默認狀況下,lt和rt函數進行90度旋轉,但您能夠提供第二個參數,指定角度的數量。例如,lt(bob, 45) 可讓bob向左旋轉45度。複製一個square函數,把它的名字改爲polygon。再添加另外一個名爲n的參數並修改polygon函數主體,使其繪製一個n邊正多邊形。提示:n邊正多邊形的外角是360/n 度。
4. 編寫一個名爲circle的函數,該函數以turtle對象 t 和半徑 r 爲參數,經過調用具備適當長度和邊數的多邊形來繪製一個近似圓。用必定範圍的 r 來測試函數。
5. 製做一個更通用的circle 函數,添加一個額外的參數angle,用來決定畫一個圓弧的哪一個部分。以angle爲單位,當angle=360時,circle 函數就會畫一個完整的圓。
上文中的第一個練習要求將畫正方形圖形的代碼放入函數定義中,而後調用函數,並將turtle做爲參數傳遞。解決方案以下:
def square(t): for i in range(4): fd(t, 100) lt(t) square(bob)
在最內層的語句,fd 和 lt 縮進兩次以表名它們位於for循環中,而for循環位於函數定義中。函數調用行square(bob)與左側空白齊平,所以這是for循環和函數定義的結尾。
在函數內部,t 表示Turtle對象bob,所以 lt(t) 與 lt(bob) 具備相同的效果。那這裏爲何不直接調用參數bob呢?這是由於這裏的 t 能夠是任何Turtle對象,而不只僅是bob,由於你能夠建立另外一個Turtle對象並將它做爲參數傳遞給square函數:
ray = Turtle()
square(ray)
在函數中包裝一段代碼稱爲封裝。封裝的好處之一是它能夠將一個名稱附加到代碼上,做爲一種文檔。另外一個優勢是,若是重用代碼,調用函數兩次比複製和粘貼主體更簡單方便!
接下來是給square函數添加一個參數length。實現代碼以下:
def square(t, length): for i in range(4): fd(t, length) lt(t) square(bob, 100)
向函數添加參數稱爲泛化,由於它使函數更通用:在之前的版本中,正方形的大小是相同的;在這個版本中,它能夠是可變的。
下一步也是泛化。polygon 函數是畫出任意數量的正多邊形,而不是正方形。實現代碼以下:
def polygon(t, n, length): angle = 360.0 / n for i in range(n): fd(t, length) lt(t, angle) polygon(bob, 7, 70)
這段代碼將繪製一個邊長度爲70的7邊形。若是函數有多個數值參數,那將會很容易忘記它們是什麼,或者它們應該處於什麼順序。在參數列表中包含參數的名稱是合法的,有時是頗有幫助的:
polygon(bob, n=7, length=70)
這些被稱爲關鍵字參數,由於它們包含參數名做爲「關鍵字」(不要與Python關鍵字,如while和def等混淆)。
這種語法使程序更具可讀性,還提醒了參數和參數是如何工做的:當您調用一個函數時,實參數被分配給形參。
下一步就是寫以半徑 r 爲參數的circle函數。這裏有一個簡單的解決方案,就是用polygon 函數畫一個50面的多邊形。
def circle(t, r): circumference = 2 * math.pi * r n = 50 length = circumference / n polygon(t, n, length)
第一行計算圓的周長,公式爲2π*r。由於咱們使用到了pi值,因此須要導入math模塊。按照慣例,一般import語句都位於腳本的開頭。
n是畫一個圓須要的近似的線的段數,因此length是每一個線段的長度。所以,polygon 函數繪製了一個50邊的多邊形,它近似於一個半徑爲 r 的圓。
這個解決方案有一個限制就是 n 是常數,這意味着對於很大的圓,每個線段太長,對於小的圓來講又浪費時間畫很小的線段。所以,一個解決方案是把n做爲參數來泛化這個函數。這將給用戶(不管誰調用circle函數)更多的控制,但界面將會變得有些亂。
函數的接口是如何使用它:參數是什麼?函數能夠作什麼?返回值是多少?若是接口「儘量簡單,但不簡單」,那麼它就是「精煉」的。(愛因斯坦)
在本例中,變量 r 屬於接口,由於它指定了要繪製的圓。n 則不是,由於它是函數內部如何渲染圓的局部變量。
所以,與其讓界面混亂,還不如根據周長選擇合適的 n 的值:
def circle(t, r): circumference = 2 * math.pi * r n = int(circumference / 3) + 1 length = circumference / n polygon(t, n, length)
如今畫線的段數是(大約)circumference/3,因此每一個段的長度是(大約)3,每一段線的長度足夠小,這樣畫出來的圓看起來才平滑,只要線的段數大到足夠有效,就能夠畫出任何大小的圓。
當我在寫circle函數的時候我能夠重用polygon函數,應爲一個許多邊的多邊形就是一個近似的圓。可是圓弧卻不能重用circle和polygon函數。
有一個可選的方法就是複製一份polygon函數,再改爲 arc 函數。改完以後大體以下:
def arc(t, r, angle): arc_length = 2 * math.pi * r * angle / 360 n = int(arc_length / 3) + 1 step_length = arc_length / n step_angle = float(angle) / n for i in range(n): fd(t, step_length) lt(t, step_angle)
這個函數的後半部分看起來像polygon函數,可是咱們不能在不改變接口的狀況下重用polygon函數。咱們能夠泛化polygon函數以一個角度做爲第三個參數,但polygon將再也不是一個合適的函數名字! 咱們能夠用一個更通用的函數名稱polyline:
def polyline(t, n, length, angle): for i in range(n): fd(t, length) lt(t, angle)
所以能夠把polyline函數從新改寫polygon函數和arc 函數:
def polygon(t, n, length): angle = 360.0 / n polyline(t, n, length, angle) def arc(t, r, angle): arc_length = 2 * math.pi * r * angle / 360 n = int(arc_length / 3) + 1 step_length = arc_length / n step_angle = float(angle) / n polyline(t, n, step_length, step_angle)
最後,咱們能夠用arc函數重寫circle函數:
def circle(t, r): arc(t, r, 360)
這個過程——從新安排程序以改進功能接口並促進代碼重用能夠稱爲「重構」。在本例中,咱們注意到在arc和polygon函數中有相似的代碼,所以咱們將其分解爲polyline函數。
若是咱們提早規劃代碼,咱們可能會首先編寫polyline函數並避免重構,但一般在項目開始的時候,您對程序設計中所須要的接口還不夠了解。只有在開始編寫代碼以後,您纔會更好地理解問題。某種程度上來講,當你開始重構的函數的時候標誌着你已經學會了一些東西了。
開發計劃是一個編寫程序的過程。咱們在本案例研究中使用的過程是「封裝和泛化」。這項工做的步驟以下:
首先編寫一個沒有函數定義的小程序。
一旦程序能夠正常運行,再把它封裝在一個函數中,而且給函數起個名字。
經過添加適當的參數來拓展該函數。
重複步驟1–3,直到你有一個函數的集合。複製並粘貼工做代碼,以免重複輸入(和從新調試)。
經過重構尋找改進程序的機會。例如,若是您在幾個地方有相似的代碼,考慮將其分解爲適當的通用函數。
這個過程是有一些缺點的——咱們在本書的後面會有替代方案——若是你不知道如何將程序劃分爲函數,這也不影響你繼續本書的學習。
docstring是函數開頭的一個字符串,用於解釋接口(「doc」是「documentation」的縮寫)。這裏有一個例子:
def polyline(t, n, length, angle): """Draws n line segments with the given length and angle (in degrees) between them. t is a turtle. """ for i in range(n): fd(t, length) lt(t, angle)
這個docstring是一個用三引號括起來的字符串,也稱爲多行字符串,由於三元引號容許字符串跨越多行。
它很簡潔,可是它包含了一些函數所須要的重要信息。它簡明地解釋了函數的做用(沒有詳細介紹它是如何完成的)。它解釋了每一個參數對函數行爲的影響,以及每一個參數應該是什麼類型(若是不是很明顯的話)。
編寫這種文檔是接口設計的一個重要部分。設計良好的接口應該很容易解釋;若是您在解釋您的某個函數時遇到了困難,這意味着這個接口還能夠再改進。
接口就像函數和調用者之間的契約。調用方贊成提供某些參數,該函數贊成執行某些工做。
例如,polyline 函數須要四個參數:t 必須是Turtle對象,n是線段的數目,因此n必須是一個整數;length應該是一個正數;angle必須是一個數字,表示角度的意思。
這些需求被稱爲先決條件,由於它們應該在函數開始執行以前準備好。相反,函數末尾的條件是後置條件。後置條件包括函數的預期效果(好比畫線段)和任何副加做用(如移動Turtle或在TurtleWorld中進行其餘修改)。
先決條件是調用方的責任。若是調用方違反了一個(適當的文檔化的)先決條件,而且函數不能正常工做,那問題就在函數調用的地方,而不是函數裏面。
實例:
一個集合中的成員。本章中的TurtleWorld是TurtleWorld集合的成員。
循環:
程序中能夠重複執行的部分。
封裝:
將語句序列轉換爲函數定義的過程。
歸納:
用適當的通用(如變量或參數)替換沒必要要的特定對象(如數字)的過程。
關鍵參數:
包含參數名稱做爲「關鍵字」的參數。
接口:
描述如何使用一個函數,包括參數的名稱和描述以及返回值。
重構:
修改工做程序的過程,以改進函數接口和代碼的其餘質量。
開發計劃:
編寫程序的過程。
文檔字符串:
在函數定義中顯示的用於記錄函數接口的字符串。
先決條件:
函數啓動前調用方應該知足的需求。
後置條件:
函數結束前應該知足的需求。
練習 1
從http://thinkpython.com/code/polygon.py下載本章下載本章中的代碼。
爲polygon、arc 和circle函數編寫適當的文檔。
繪製一個堆棧圖,顯示執行圓時程序的狀態(bob,radius)。您能夠手工進行算術或向代碼中添加打印語句。
第4.7節中弧的版本不是很精確,由於圓的線性近似老是在真圓以外。所以致使Turtle離正確的目的地還差了幾個單位。我給出的解決方案減少此錯誤影響的方法。請閱讀代碼,看看它對您是否有意義。若是你畫一個圖表,你可能會看清除它是如何工做的。
圖4.1
練習 2
編寫一組適當的通用函數,能夠繪製如圖4.1所示的圖案。
解決方案:
http://thinkpython.com/code/flor.py
http://thinkpython.com/code/polygon.py
圖4.2
練習 3
編寫一組適當的通用函數,能夠繪製如圖4.2所示的形狀。
解決方案:http://thinkpython.com/code/pie.py
練習 4
字母表中的字母能夠用必定數量的基本元素構成,如垂直線和水平線以及一些曲線。設計一種字體,它能夠用最少的基本元素繪製,而後編寫繪製字母的函數。
您須要爲每一個字母編寫一個函數,並命名爲draw_a, draw_b, ...等,並將您的函數放入名爲letters.py 的文件。你能夠從能夠從 http://thinkpython.com/code/typewriter.py 下載一個下載一個「Turtle打字機」來幫助你測試你的代碼。
解決方案:
http://thinkpython.com/code/letters.py
http://thinkpython.com/code/polygon.py
練習 5
請閱讀請閱讀 http://en.wikipedia.org/wiki/Spiral 上的相關文章;而後編寫一個繪製阿基米德螺旋(或其餘種類的程序)
解決方案:
http://thinkpython.com/code/spiral.py.
#英文版權 Allen B. Downey #翻譯中文版權 Simba Gu #轉載請註明出處