Python 程式設計

第 5 章    小烏龜繪圖模組

∗ Turtle 繪圖模組

▸ 烏龜繪圖 (Turtle graphics) 是 Logo 程式語言中的一個向量繪圖技術,開發於 1960 年代, 非常適合初學者學習,目前大多數的程式語言都有此套件

▸ 烏龜繪圖的機制:想像有一隻烏龜,可以依使用者的指令到處遊走,牠的尾巴就像畫筆一般,如果放下就會將行走軌跡畫下來, 尾巴如果提起,就不會畫軌跡

▸ Turtle 模組利用 python3-tk 套件來繪圖,安裝:

✶ Ubuntu:$ sudo apt install python3-tk
✶ Windows:預設已安裝

(1) Turtle 程式範例

∗ 繪圖螢幕座標系統:

coordinate

∗ 小烏龜畫圖形

ch5/myTurtle.py (首先建立 python/ch5 目錄)

import turtle

# Create screen and turtle objects
screen = turtle.Screen()
screen.setup(500, 400)
myTurtle = turtle.Turtle()

# Move the turtle
myTurtle.forward(150)
myTurtle.left(90)
myTurtle.forward(75)

# Exit
screen.exitonclick()

myTurtle

▸ 第 1 行:匯入 turtle 模組,本範例使用 turtle 模組裡的 Screen()Turtle() 兩個方法

▸ 第 4 行:利用 turtle 模組裡的 Screen() 方法產生一個螢幕物件, 並將其指派給變數 screen

✶ Python 模組方法的使用方式是利用 <module>.<method>() 的點號語法, 例如本範例中的 turtle.Screen()

▸ 第 5 行:利用 screen 物件的 setup() 方法來設定螢幕的寬度與高度 (本例中,寬高分別是 500 與 400 像素)

▸ 第 6 行:利用 turtle 模組裡的 Turtle() 方法產生烏龜物件, 並指派給 myTurtle 變數

▸ 第 9 行:利用 myTurtle 物件的 forward() 方法讓小烏龜向前走 150 像素距離

✶ 註:烏龜物件產生時,預設方向為面向東方

▸ 第 10 行:利用 myTurtle 物件的 left() 方法讓小烏龜左轉 90 度

▸ 第 11 行:向前走 75 像素距離

▸ 第 14 行:利用 screen 物件的 exitonclick() 方法在使用者點擊螢幕時結束程式

注意:不可將檔案命名為 turtle.py, 否則會和內建的 turtle 模組名稱衝突

∗ 其他 screen 與 turtle 物件方法

▸ 設定螢幕背景顏色

screen.bgcolor('lightBlue')    # 淡藍色
screen.bgcolor('#add8e6')

▸ 設定畫筆顏色

myTurtle.color('blue')    # 藍色

▸ 設定畫一個封閉幾何圖形的外框及底色,例如藍框紅底、半徑為 30 的圓:

myTurtle.color('blue', 'red')
myTurtle.begin_fill()
myTurtle.circle(30)
myTurtle.end_fill()

▸ 設定畫筆尺寸

myTurtle.pensize(3)    # 3 像素寬

▸ 提筆與落筆

myTurtle.penup()      # 提筆
myTurtle.pendown()    # 落筆

▸ 走到某個位置

myTurtle.goto([120, 150])    # 走到 (120, 150) 位置,位置資料為串列
myTurtle.goto(120, 150)      # 位置參數為兩筆資料

▸ 練習 5-1

∗ Tk 顏色名稱與數值

http://www.tcl.tk/man/tcl8.4/TkCmd/colors.htm

▸ 練習 5-2

像電腦科學家一樣的思考,回答以下問題

▸ 小烏龜產生時是面向東方,經過許多轉彎後,如果所有轉彎的角度加起來是 360 的倍數,請問小烏龜面向何方?

▸ 如果要畫一個封閉的幾何圖形,總共需要轉幾度?

(2) For 迴圈

∗ 利用小烏龜繪製一個方框

▸ 規劃行走模式:向前走 --> 左轉 --> 向前走 --> 左轉 --> 向前走 --> 左轉 --> 向前走,程式如下:

ch5/drawSquare.py
import turtle

# Create screen and turtle objects
screen = turtle.Screen()
screen.setup(500, 500)
myTurtle = turtle.Turtle()

# Move the turtle: draw a square
myTurtle.forward(100)
myTurtle.left(90)
myTurtle.forward(100)
myTurtle.left(90)
myTurtle.forward(100)
myTurtle.left(90)
myTurtle.forward(100)

# Exit
screen.exitonclick()
drawSquare

▸ 以上程式其實頗為繁瑣,繪製四邊形需要 7 個指令,如果要繪製八邊形就要 15 個指令, 如此程式不僅變得更長,看起來更是雜亂,試想,如果要繪製 100 邊形,光複製近 200 行程式指令就手軟了

▸ 解決方案:重複的程式只要寫一次,然後自動重複執行許多次

✶ 此種功能稱為「迴圈」(Iteration, Loop),Python 語言有 forwhile 兩種迴圈指令,都屬於複合指令 (Compound statement)

∗ 複合指令

▸ Python 有一些指令可以包含其他指令,稱為複合指令 (Compound statement),例如 for, while, if, else, elif, def, class

▸ 以 for 複合指令為例,其功能是重複執行一群指令,語法如下:

for <variable> in <sequence>:
    <body>
<variable>:迴圈變數 (Loop variable),在每次迴圈執行時會循序取用一個值
in:在 ... 之中
<sequence>:一個序列,內含許多值,例如 Python 的串列 [1, 2, 3, 4],以中括號包住 4 個整數值
<body>:複合指令的本體,是每次迴圈要執行的一群指令

▸ 複合指令的結尾要有冒號

▸ 複合指令的本體需縮排一層,每層縮排為 4 個空格 (不要使用定位鍵來縮排)

Python 的縮排非常重要,縮排錯誤可能會造成程式執行錯誤,或執行結果錯誤。 縮排強迫程式設計師必須將程式結構化,以提升閱讀性 (Readability),此與其他語言非常不同

✶ 例如以下兩段程式結果是不同的:
for num in [1, 2, 3, 4]:
    print(num)
print('End')

for num in [1, 2, 3, 4]:
    print(num)
    print('End')
# 第一段程式的 print('End') 指令不屬於 for 指令的本體,因此僅會執行一次; 第二段程式的 print('End') 指令屬於本體,因此會執行 4 次

▸ 不受縮排限制來對齊指令

✶ 某些指令較長,需要額外對齊較整潔,可以利用括號包住,就可以不受縮排的限制,例如:
overtime = (overtimeMorning +
            overtimeAfternoon +
            overtimeEvening +
            overtimeNight)

∗ 迴圈 (Iteration)

▸ 電腦程式的一項重要功能就是能夠重複執行某些指令,這樣的重複功能稱為迴圈

▸ 例如:我們想邀請一些朋友來參加我們的派對,打算寄給每一個人一封電子信件,每封信內容相同,但抬頭不同, 可利用以下迴圈程式印出個別訊息:

ch5/party.py
for name in ['張三', '李四', '王五', '趙六']:
    print(f'Hi {name},歡迎參加我們在星期六所舉辦的派對')
✶ 第 1 行 for name in ['張三', '李四', '王五', '趙六']
# ['張三', '李四', '王五', '趙六'] 是Python 的串列, for 迴圈每次循序取出串列裡的一個項目,並且指派給迴圈變數 name
# 第 1 個迴圈 name 的值是 張三
# 第 2 個迴圈 name 的值是 李四
# 第 3 個迴圈 name 的值是 王五
# 第 4 個迴圈 name 的值是 趙六
✶ 第 2 行印出訊息:此指令是 for 複合指令的本體,因此縮排 4 個空格
✶ 每次迴圈執行完畢,程式都會檢查是否還有資料要取用,如果有,就繼續迴圈,如果沒有 (這是迴圈結束條件),就停止迴圈
✶ 執行結果:
Hi 張三,歡迎參加我們在星期六所舉辦的派對
Hi 李四,歡迎參加我們在星期六所舉辦的派對
Hi 王五,歡迎參加我們在星期六所舉辦的派對
Hi 趙六,歡迎參加我們在星期六所舉辦的派對

∗ For 迴圈的執行流程

▸ 電腦程式在執行時,會決定下一個要執行指令是那一個指令,這樣的控制稱為控制流程 (Control flow) 或者執行流程 (Execution flow)

▸ 程式由上往下循序執行,這種型態稱為循序控制流程 (Sequential control flow)

▸ For 指令是重複控制流程 (Repetitive control flow),如下圖所示:

controlFlow
✶ 首先判斷是否序列裡的所有項目都已輪過
✶ 若是,結束迴圈
✶ 若否,將序列裡下一個項目指派給迴圈變數
✶ 執行迴圈本體程式

∗ 利用 for 迴圈簡化小烏龜繪製方框程式

▸ 利用小烏龜畫一個方框,相同的事要重複 4 次:「向前走,轉 90 度」

▸ 先前畫方框的程式共有 8 行,現在改為 for 迴圈,程式僅有 3 行:

ch5/drawSquare2.py (Modify drawSquare.py)
...

# Move the turtle: draw a square
for i in [1, 2, 3, 4]:
    myTurtle.forward(100)
    myTurtle.left(90)

...
for i in [1, 2, 3, 4]:建立一個循序使用串列裡的元素的迴圈, 迴圈每次會取用一個項目,因此會執行 4 次:
圈數 變數 i 的值 執行程式
1 1
myTurtle.forward(100)
myTurtle.left(90)
2 2
myTurtle.forward(100)
myTurtle.left(90)
3 3
myTurtle.forward(100)
myTurtle.left(90)
4 4
myTurtle.forward(100)
myTurtle.left(90)

▸ 由於迴圈變數在本體中並未用到,只是用來當計數器,因此序列的內容也就不重要,只要串列裡成員的數量為 4 即可, 因此下列兩種迴圈都有相同效果:

for i in ['a', 'b', 'c', 'd']:    # 循序取串列裡的元素
    myTurtle.forward(100)
    myTurtle.left(90)

for i in 'abcd':                  # 循序取字串裡的字元
    myTurtle.forward(100)
    myTurtle.left(90)

像個電腦科學家一樣的思考

▸ 其實少寫幾行程式並非迴圈的主要貢獻,迴圈讓我們學到程式的一個重要功能:重複執行相同的指令

▸ 迴圈在程式中是很常見的結構,可用此結構解決許多問題

▸ 試試看,每次迴圈給不同的畫筆顏色:

ch5/colorSquare.py
import turtle

# Create screen and turtle objects
screen = turtle.Screen()
screen.setup(500, 500)
myTurtle = turtle.Turtle()

# Move the turtle: draw a square
for color in ['red', 'blue', 'green', 'orange']:
    myTurtle.color(color)
    myTurtle.forward(100)
    myTurtle.left(90)

# Exit
screen.exitonclick()
colorSquare

▸ 練習 5-3

(3) Range 函式

∗ 產生數字序列

▸ 先前的 for 迴圈範例使用 [1, 2, 3, 4] 數字串列, 針對此,Python 提供了一個簡單的內建函式 range(),用來產生數字序列 (並非「串列」),語法如下:

range(<stop>):產生 0 ~ <stop>-1 的數字串列 (從 0 開始,總共 <stop> 個數字)
# 例如:range(6) --> 0, 1, 2, 3, 4, 5
range(<start>, <stop>):產生 <start>, <start>+1, ..., <stop>-1 的數字串列 (從 <start> 開始, 總共 <stop>-<start> 個數字)
# 例如:range(4, 10) --> 4, 5, 6, 7, 8, 9
range(<start>, <stop>, <step>):產生 <start>, <start>+<step>, ... 的數字串列
# 例如:range(4, 10, 2) --> 4, 6, 8

注意:range() 函式所產生的序列可直接提供 for 迴圈使用, 但如果要真的產生串列,則必須使用 list(range(...)) 方式,例如:

numList = range(2, 8)
print(numList)    # range(2, 8)
numList = list(range(2, 8))
print(numList)    # [2, 3, 4, 5, 6, 7]
range() 函式回覆的是 range 類別,而非 list 類別,不用 list 類別主要理由就是效能:range 類別在記憶體裡只存三筆資料: start, stop, step, 而不是儲存整個串列,如此,不僅不浪費記憶體,速度也快多了

∗ 小烏龜畫方框迴圈程式改用 range() 函式:

ch5/drawSquare2.py
...

# Move the turtle: draw a square
for i in range(4):
  myTurtle.forward(100)
  myTurtle.left(90)

...

(4) 像個電腦科學家一樣思考

▸ 既然小烏龜模組可以繪製幾何圖形,我們應該更深入了其解繪圖功能

∗ forward() 及 left() 方法可以有負值參數

▸ 例如:

myTurtle.forward(-100)    # 後退 100
myTurtle.left(-30)        # 右轉 30 度

▸ 也有 backward()right() 方法可用

myTurtle.backward(-100)    # 前進 100
myTurtle.right(-100)       # 左轉 100 度

▸ 因為圓為 360 度,因此 myTurtle.right(100) 等於 myTurtle.left(260)

∗ 小烏龜可以有自己的形狀 (shape)

▸ Turtle 模組提供以下形狀: arrow, blank, circle, classic, square, triangle, turtle

▸ 例如設定為烏龜形狀: myTurtle.shape('turtle')

∗ 小烏龜行走的速度 (Speed) 可以調整

▸ 速度範圍:1 ~ 10 (最慢 ~ 最快),0 則是最快 (沒有動畫)

▸ 設定速度:myTurtle.speed(8)

∗ 小烏龜可以蓋章:

myTurtle.stamp()

∗ 範例:畫螺旋形及星形

spiral

ch5/spiral.py
import turtle                   # 匯入小烏龜模組

screen = turtle.Screen()        # 產生一個螢幕
screen.setup(500, 400)          # 設定螢幕寬高
screen.bgcolor('lightgreen')    # 設定螢幕背景

myTurtle = turtle.Turtle()      # 產生一個小烏龜物件
myTurtle.color('blue')          # 設定烏龜顏色
myTurtle.shape('turtle')        # 設定烏龜形狀

myTurtle.penup()                # 提筆
for step in range(5, 60, 2):    # 迴圈:從 5 開始,每次跳 2 步,直到 60
    myTurtle.stamp()            # 蓋章
    myTurtle.forward(step)      # 向前 step 步
    myTurtle.right(24)          # 右轉 24 度

screen.exitonclick()            # 使用者點擊螢幕時結束程式

star

ch5/star.py
import turtle

screen = turtle.Screen()
screen.setup(400, 300)

myTurtle = turtle.Turtle()
myTurtle.color('red', 'yellow')    # 多邊形外框紅色,內部區域黃色
myTurtle.speed(10)                 # 設定速度
myTurtle.backward(100)             # 後退 100 (讓圖形置中)

myTurtle.begin_fill()    # 開始填色
for i in range(36): 
    myTurtle.forward(200)
    myTurtle.left(170)
myTurtle.end_fill()      # 結束填色

screen.exitonclick()

∗ 常用小烏龜物件方法彙整

方法參數說明
forward(<x>)distance向前走一段距離 <x>
backward(<x>)distance向後走一段距離 <x>
right(<a>)angle順時針旋轉角度 <a>
left(<a>)angle逆時針旋轉角度 <a>
up(), penup()提筆
down(), pendown()落筆
color(<c>)color name設定筆的顏色 <c>
pensize(<s>)size設定筆的尺寸 <s>
fillcolor(<c>)color name設定填滿多邊形的顏色 <c>
heading()回覆目前的方向
setheading(<a>)angle設定目前方向為 <a> 角度
position()回覆目前的位置
goto(<x>, <y>)
goto([<x>, <y>])
x, y前往 (<x>, <y>) 位置
speed(<s>)speed設定速度 <s> (0 ~ 10)
begin_fill()多邊形開始填色
end_fill()多邊形結束填色
circle(<r>)radius畫一個圓,半徑為 <r>
write(<s>)string在目前位置寫文字 <s>
dot()在目前位置留下一個點記號
stamp()在目前位置留下一個烏龜形狀記號
shape(<s>)shap name設定烏龜形狀 <s>
('arrow', 'turtle', 'circle', 'square', 'triangle', 'classic')

▸ 完整資料請參考: https://docs.python.org/3/library/turtle.html

▸ 練習 5-4, 5-5

上一章       下一章