這是一個為為沒有程式經驗的數學系學生學習Python 作為數值計算使用的筆記,希望用最少的內容,幫助同學可以有點程式技巧得以進行數值計算。本文件是用Txt2tags 產生,Txt2tags 語法的親民,讓我可以一口氣把這份文件用最短的時間打出來。


  1. 前言
  2. 建制Python 學習環境
  3. Python 初步
  4. Python的重要型別
  5. Python 的流程控制
  6. 小試身手
  7. 副程式與套件
  8. 檔案的操作
  9. 幾個常用的函式與招式
  10. Python繪圖
  11. Python 風格
  12. 遠端操作技巧
  13. 結語

1. 前言

經過一年半的數值分析教學,我注意到沒有程式背景與經驗的學生根本很難體會課堂中的重點,因為數值分析著重在如何使用電腦程式來處理數學問題,解題的過程必須配合電腦程式的流程,這和平常手寫計算的思考方式有很大的不同。譬如,解聯立方程式,最常使用的手寫計算方式就是代入消去法。對人類來說,要代入一個算式然後化簡很直覺。可是要讓電腦知道哪個符號是變數,哪些是已知數,哪些是未知數,算式要如何讓電腦理解,就不是一個直覺做的到的事。因此我們需要把思緒降階,要配合電腦的呆板,也要利用電腦的快速,這就是學數值分析第一個要調整的態度。無可避免地,若沒有寫程式的經驗,就很難體會到配合電腦的計算邏輯這種思緒降階的這種想法。為了學數值也不太可能花費太多時間去學習一套完整的程式語言,正因為如此我們希望能將程式語言的部份降到最低,讓學生可以用最短的時間學到適合修習數值分析的語言程度。希望這份文件對數學系的學生能有學習上的幫助。

程式語言的種類非常的多,為甚麽使用Python 作為主要的程式呢?其原因如下:

  1. Python 是免費的
    • 其實最容易上手的高階電腦語言並不是Python,Matlab 或是Octave 都是很好的選項,但是在價格上Matlab 這套軟體就不是人人負擔得起的軟體。在貧富差距日益加大的年代,國立大學的教學內容還是以自由軟體為主,才能避免增加學生學習的負擔。
  2. Python 適用於所有作業系統
    • 在幾個主要的作業系統上,Python 都有相對應的程式,最強的地方在於同一個code 可以在不同的平台上編譯成對應該平台使用的執行檔,這是我最推薦這套軟體的原因。
    • 許多數學系的學生對於撰寫gui 程式會有心理障礙,在選擇用哪一種語言來寫gui 也會擔心軟體的發展過於迅速,所學的語言又有被淘汰的風險。Python 有幾款套件,可以很輕鬆地把寫好的程式,變成適用於不同平台的gui 程式,這也是Python 很值得推薦的地方。
  3. Python 有完整的社群
    • 由於網路的發達,許多知識都可以透過部落格或是討論區的交流得到解答,Python 由於有強大的網路社群,因此在學習Python 語言時只要能善用搜尋器,就有很大的機率可以找到可以解決問題得答案。
    • Python 的強大社群充分發揮自由軟體分享的精神,Python 有許多適合不同領域的人使用的套件,例如統計、數值、財務與生物資訊等。透過安裝這些套件,便可以很方便的使用到這些前人所建制的函式。
  4. Python 是學數學的人開發的
    • 由於Python 的原創者是數學背景出生,因此Python 語言設計的邏輯,對於念數學的學生來說再自然也不過了。從Python 對迴圈處理的方式,我們就可以看到集合論的影子。既然什麼東西都可以變成集合,Python 在處理物件的元素上就比其他語言有彈性。例如,一個串列的第一個元素是整數,第二個元素可以是字串,第三個元素可以是另外一個串列。這樣的彈性,讓Python 語言顯得特別自由。然而這也是Python 的一個隱憂,就是不容易挑錯。
  5. Python 適合大資料計算
    • 由於Python 的型別非常具有彈性,這種彈性使得Python 不要求一個高維度變數裡面的元素需要有相同的型別,因此在處理大量資料時,不需要因為型別的改變,而開設許多讓每一個矩陣內的元素都是相同型別的龐大矩陣,造成記憶體的浪費與計算效能的降低。這部份就不是Octave 可以勝任的。

總之,Python 是一個值得推薦給數學系學生學習的當代程式語言,若你要嘗試大資料計算的問題,學Python。

2. 建制Python 學習環境

以下我們要介紹如何用最快最省的方式建制Python 的學習環境。

2.1. 推薦Linux 作業系統

Python 雖然是一個跨平台的程式,在幾個主要的作業系統上都可以找到對應的安裝程式,但是因為版本新增迅速的緣故,有時候會因為版本衝突造成使用上的不便。因此我們推薦使用Linux 作業環境,特別是Ubuntu 系統(若你的電腦硬體比較落後,建議使用Lubuntu),因為Ubuntu 的社群龐大,安裝軟體的介面也很簡單,經過軟體安裝的管理系統所搜尋到的程式比較不會有相容性的問題。再來,Linux 作業系統比微軟作業系統漂亮多了,並且在沒有病毒侵擾的環境下,更可以安心學習。重點是它免費,請不要再說什麼買不起的爛道理了。安裝Linux 已經不是電腦工程師的專利,如果你懂得如何切割硬碟,先安裝好你舊有習慣的系統後,再安裝Linux 就可以享受雙開機的作業環境。如果你什麼都不懂,直接在你的作業系統下放入Ubuntu 的光碟,光碟的自動程式會引導你如何安裝雙開機的系統,只是以後的開機畫面會比較醜陋而已。

2.2. 安裝Python

基本上Mac OS 和多數的Linux 系統(當然包含Ubuntu 及 Lubuntu) 都內建Python,所以沒有安裝上的問題。除非你需要特別安裝特定版本的Python 程式,這時你可以使用Synaptic 套件管理程式。打開Synaptic 程式會要求你輸入super user 的密碼,這裡就看到為甚Linux 作業系統沒有病毒的理由,就是要安裝一個程式,除非你允許,不然不可能安裝。當然也就不會有非執行檔莫名其妙變成執行檔的機會。利用Synaptic 程式內的搜尋,打上Python 就會看到一堆和Python 有關的東西,每一個安裝程式後面都會有套件的描述,如果不清楚就把套件名稱打在Google 上,就可以得到更豐富的資訊。要安裝或是移除該套件,就把前面的核取方塊點一下,圖形介面會很清楚地告訴你怎麼操作。

2.3. 安裝bpython

使用python 的IDLE 介面固然方便美觀,但是速度上差了一些,可能是我的電腦都特別老舊的緣故,當我們想要查詢一些物件時,IDLE 的反應有點慢,對程式撰寫很干擾,因此建議安裝bpython 套件。同樣地,我們在Synaptic 套件上打上bpython,沒什麼意外第一個就是了。選取之後Synaptic 會說缺少一些其他相關套件,問你要不要裝?和多數安裝程式一樣,確定下去就是了。安裝好bpython 之後,我們就可以在terminal 底下打上bpython 來叫出Python 程式,在bpython 底下不但物件顯示的反應速度較快,它也具有和IDLE 一樣用不同顏色提醒你程式中變數或保留字的特性,非常方便。

2.4. 安裝數學相關重要套件

我假設這份文件是給數學系要學數值分析的同學使用,因此我們至少會需要一些套件,來減少我們入門的壓力。第一個套件是numpy,在Synaptic 裡面打上numpy 會找到許多套件,選取python-numpy 這一項安裝。再來是python-scipy,這套件和numpy 是相輔相成的,裡面有許多線性代數會用到的函式,通常都會一起裝上。再來是繪圖使用的python-matplotlib。裝上它就可以秀圖了。如果你想做像Mathematica 這類數學符號的計算,你可以安裝python-sympy。如果你想要寫Python 的gui 程式,就安裝wxpython,我們搜尋python-wxversion 就可以裝好wxpython。裝到這裡就差不多了,要學習的東西太多,這篇文章也涵蓋不了。若全都用上,也都學會,應該也夠唬人了。裝到這裡,一個Python 城堡差不多也建制完成。

3. Python 初步

3.1. 加減乘除之外

Python 是一個高度直譯式的語言,所以變數不需要特別宣告。加減乘除和多數的語言一樣,使用除號"/"時要特別小心,特別是整數除整數時,答案會是整數的商數。例如

>>>3/2
1

若要避免這類的計算錯誤,我們可以使用

>>>3.0/2
1.5

就是在整數後面加個.0 即可。和取商數經常一起使用的另外一個算子是取餘數,Python 中取餘數的算子是"%" 符號,例如

>>> 5%3
2

Python 的指數運算使用的是**,例如2的3次方寫作2**3

>>>2**3
8

Python 的"^" 另有用途,是所謂的位元運算子中的互斥或(XOR),例如

>>>3^5
6

因為3 的二進位表示法為011,5 的二進位表示法為101,他們的位元互斥或便是110, 因此答案是6。

3.2. 到處都是的物件導向

Python 幾乎所有東西都是物件導向,什麼是物件導向?說不清楚也不想弄清楚的人只要記住三件事,就是物件帶有屬性與功能,屬性顧名思義就是物件特有的一些特徵,如長度,大小,顏色,老爸是誰老媽是誰等等用來描述物件的東西。物件帶有的功能可以視為函數,可以做點專職的事。要叫出物件的屬性或是函數,就在該變數後面加個點(.)就是了。要記住變數的屬性和功能不是一件簡單的事,還好在Python 的IDLE 裡面,只要打上變數名稱再打上一個"." 然後耐心的等一下下,就會自動把該變數的屬性和函數給叫出來。如果還是對物件很恐懼,就把物件想像成包子的餡,用"."戳一下,瞧!露餡了。

3.3. 串列型別

在Python 中出現頻率最高的變數型別就是串列(list),例如:

>>> x = [1,2,3,4,5]
>>> x
[1,2,3,4,5]
>>>

x 是一個長度為5 的串列,操作串列最常用到的技巧是加掛(append),例如:

>>> x.append(6)
>>> x
[1,2,3,4,5,6]

x 就多了一個加掛的元素6 。我們看到x.append() 就是利用x 本身帶著的append 函數進行加掛資料的動作。除了在IDLE 裡面打上"." 之後慢慢等x 的屬性與功能跳出來,我們也可以用dir() 這個函數來看x 的物件。

>>> dir(x)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', 
'__delslice__', '__doc__', '__eq__', '__format__', '__ge__', 
'__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', 
'__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', 
'__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', 
'__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', 
'__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 
'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

我們看到除了剛才介紹的append 之外,還有很多看了名稱就差不多猜到功能的函數,之後我們遇到了再一一介紹。

3.4. 從零開始

Python 的指標(index)是從零開始計算,這點和c++ 一樣,處理代數問題時很好用,但處理向量和矩陣時要記得轉換一下。以上述的x 為例,要叫出x 的第一個元素,我們用

>>> x[0]
1

我們可以用len 這個函數來取得串列的長度

>>> len(x)
6

如果我們忘記在Python 中的index 是由0 開始起算,當我們用了超過他該有的長度時,會出現錯誤訊息。

>>> x[6]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
IndexError: list index out of range

這裡的6 已經用到x 的第七個元素,所以超過了x 該有的範圍,所以出錯。

3.5. 基本串列操作

如果要設定一個空的串列,我們使用

>>> x = list()
>>> x
[]

通常在我們開始要紀錄一個長度會隨迴圈次數增加的向量時,我們會用這個手法來給這個變數一個起始。雖然這個串列是一個空的串列,但他仍有串列的屬性與功能,因此我們可以使用x.append() 這個函數把新的元素加掛在此串列上。

我們也常常需要一個固定長度並且有順序的串列,最常用的就是1,...,n。這時我們使用range() 這個指令來製造串列。例如:

>>> x = range(5)
>>> x
[0, 1, 2, 3, 4]

如果我們需要的等差數列,起始值為3 ,公差為2 ,結束在11,那麼可以調整range() 的參數如下:

>>> range(3,12,2)
[3, 5, 7, 9, 11]

這時range() 裡面有三個參數,第一個是起始值,第二個是結束的值,第三個是公差。請特別注意,結束的值一定不會出現在range() 的結果中,所以,我們希望結束在11 ,控制結束的參數就要11+1 = 12

活用Python 串列的指標操作,可以省去許多需要使用for 迴圈的場合,讓程式變得更精簡。例如:

>>> x = range(1,20,2)
>>> x
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
>>> x[1:7:2]
[3, 7, 11]
>>> x[0::3]
[1, 7, 13, 19]
>>> x[:]
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
>>> x[:8]
[1, 3, 5, 7, 9, 11, 13, 15]

我們看到x[1:7:2] 代表從x 的第2 個元素開始,以間距2 個單位取值,結束於第7 個元素之前(15)。所以,是3 然後跳7 然後跳11 ,原本應該要跳到15 ,但是15 是第7 個元素,結束的元素不會出現,所以到11 為止。

x[0::3] 結束參數不打就代表結束到無窮遠,所以最後一個元素有機會被選到。

x[:] 代表不設定起始,也不設定結束,所以就是x 的所有元素。

x[:8] 代表部設定起始,設定結束於第8 個元素之前,並且不設定間隔(使用預設間隔1 )

我們可以使用pop 把特定的資料丟出,特別在一個串列只會用到一次,用完就丟,又不想花時間詳細計算剩下多少個元素的時候使用,例如唐飛。喔!sorry,是例如下列範例:

>>> x
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
>>> x.pop()
19
>>> x
[1, 3, 5, 7, 9, 11, 13, 15, 17]
>>> x.pop(1)
3
>>> x
[1, 5, 7, 9, 11, 13, 15, 17]

如果pop() 不帶參數,預設就是最後一個元素。如果帶參數,就是把對應該參數的元素丟出來。

我們也可以用remove() 函數把元素移除,和pop() 的差別,在於pop() 丟出的元素可以放入另外一個變數當中,但remove() 只是單純地把元素從串列中移除。如果要移除的元素,沒有出現在串列當中,會出現錯誤訊息。範例如下:

>>> x.remove(2)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
ValueError: list.remove(x): x not in list
>>> x.remove(7)
>>> x
[1, 5, 9, 11, 13, 15, 17]

最後,我們可以用index() 這個函數來搜尋某元素存在的位置。範例如下:

>>> x.index(9)
2 

3.6. for 迴圈

Python 的for 迴圈的寫法很數學,我們以1 加到100 為例,說明如何使用Python 的for 迴圈。

>>> x = 0
>>> for i in range(1,101):
...     x+=i
...     
... 
>>> print x
5050

因為我們要計算的是x = ∑i=1100 i 。所以,先給一個不會影響答案的值,作為x 的起始值,然後在把每次的i 值加到x 裡。Python 的for 迴圈用for i in 一個有序的型別開始,因為range(1,101) 是串列,而串列又是一個有序的型別,所以,i 會依續從串列中取值。這裡用到in 這個語法,很有數學的味道。並且for 敘述以: 開始,就好像在說話一樣,接下來就是屬於這個迴圈的內容。Python 並沒有用end 的敘述來紀錄迴圈內容的結束,而是用縮排的方式來定義迴圈的層級。在IDLE 裡面,我們會看到自動縮排的效果。如果我們要用兩層的迴圈,第二層的迴圈就要再退縮一次。以上述的程式為例,我們還看到x+=1 指令和C++ 一樣,意思就是 x = x + 1。要結束該迴圈,連續兩次換行沒有輸入任何符號,IDLE 就自動跳出,所以print x 這行就不在迴圈裡。

4. Python的重要型別

Python 有幾個重要的型別,除了一般程式都會有的整數,浮點數,字元,字串以及上一章認識的串列之外,經常使用的還有tuple, set 以及 dict,我們依序介紹這三個重要的型別。

4.1. tuple 型別

Python 值組型別(tuple) 的特性幾乎和串列(list) 一樣,list 是以[] 來輸入,tuple 是以() 來輸入。tuple 與list 的差別在於list 的內容可以修改,tuple 的內容不能修改。

>>> x = [1,2,3]
>>> x[0] = 4
>>> x
[4, 2, 3]
>>> y = (1,2,3)
>>> y[0] = 4
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> y
(1, 2, 3)

我們看到,要更改tuple 的值就被擋下了。因為tuple 有不能修改的特性,所以在寫程式時,如果有不希望被變動的有序資料,就很適合用tuple 來紀錄。

4.2. set 型別

Python 集合型別(set) 處理的當然是有限可數集合囉!讀者應該有注意到Python 用[] 表示串列,用() 表示值組,而集合是用set([]) 來表示。因此,要新增一個空集合,我們除了可以用A = set() 之外,我們也可以用 A = set([]) 來表示。要直接給定一個集合,set([]) 這個符號已經暗示,我們可以用類似輸入串列的方式來建制一個集合。

>>> A = set([3,5,1,8])
>>> A
set([8, 1, 3, 5])

我們注意到,輸入A 的集合元素,我們的順序是3, 5, 1, 8 。然而當我們顯示A 集合時,結果卻是,8, 1, 3, 5。這樣的結果其實是猜的出來的,因為集合強調的是存在與否,集合內的元素並沒有順序的概念。正因如此,集合沒有像串列一樣有append() 後掛的函數,類似的函式是add()。我們可以用

>>> A.add(2)
>>> A
set([8, 1, 2, 3, 5])

把新的元素加到A 集合裡面。關於集合的運算,譬如交集,聯集,差集等當然都會放在集合物件裡面,利用dir(A) 我們可以很容易地看出這些函數如何呼叫以及使用。例如:

>>> A
set([8, 1, 2, 3, 5])
>>> B
set([1, 2, 3, 4])
>>> C = A.intersection(B)
>>> C
set([1, 2, 3])
>>> C = A.union(B)
>>> C
set([1, 2, 3, 4, 5, 8])
>>> C = A.difference(B)
>>> C
set([8, 5])

其中要特別注意的是集合也有pop() 這個函式,只是在使用時,會任意地丟出一個元素。

4.3. dict 型別

Python 的字典型別(dict) 對於建制一個臨時的資料結構非常好用,它有點像database,並且也真的可以很方便的從網頁的database 讀取資料到dict 型別變數中或是由dict 變數中寫資料到database 裡。

要創建一個dict 型別,我們用{} 來建立dict 資料,例如:

>>> A = {'Bob':3,'Alice':2,'John':5}
>>> A
{'Bob': 3, 'John': 5, 'Alice': 2}
>>> A.keys()
['Bob', 'John', 'Alice']
>>> A.values()
[3, 5, 2]
>>> A['Bob']
3
>>> 'John' in A
True
>>> 'Glophy' in A
False

從第一行指令我們看到,字典裡面的元素是成對的,":" 之前的稱為鑰匙(key),":" 後面的稱為值(Value),當我們要看這個字典裡有哪些key 時,我們用keys() 這個函數,若要看裡面有哪些值時,我們用values() 這個函數。我們可以用"屬於(in)" 這個指令來檢查該鑰匙是否在這個字典變數中。因為字典裡的元素是成對的,為了方便輸入,我們可以借用zip() 函數的特性來達到快速建立字典的內容。舉例如下:

>>> A = ['Bob','John','Alice']
>>> A
['Bob', 'John', 'Alice']
>>> B = [30,40,50]
>>> B
[30, 40, 50]
>>> zip(A,B)
[('Bob', 30), ('John', 40), ('Alice', 50)]
>>> D = dict(zip(A,B))
>>> D
{'Bob': 30, 'John': 40, 'Alice': 50}

我們看到zip() 函數會點對點的把兩個有序的資料型別,一一對應造成一個新的串列,然後把這種成對的串列用dict() 函數轉換成字典型別,就製作成字典資料了。zip() 會自動判斷兩筆資料的長度,然後取短的來製作。例如:

A = ('Bob','John','Alice','Glophy')
>>> zip(A,B)
[('Bob', 30), ('John', 40), ('Alice', 50)]

4.4. 變換自如的型別

從介紹集合型別看到set([]) 這個符號,和使用A = set() 來做為設定起始空集合的用法,加上使用dict(zip(A,B)) 來製作字典資料,我們似乎嗅到一個味道,list(), tuple(), set(), dict() 這些函數不只有定義型別的作用,也具有轉換型別的作用。例如,方才的zip(A,B) 的結果是一個串列,但加了dict(zip(A,B)) 之後原本的串列型別就變成字典。同樣的[] 是串列,但set([]) 就是集合。Python 基本上只要資料能配合新的型別使用,它都能盡可能地換來換去。因此,若有一個tuple 資料突然要修改了,就先用list() 函式轉換成list 型別,然後修正資料後再用tuple() 函數變回來即可。舉例來說:

>>> A = tuple([3,4,5])
>>> A
(3, 4, 5)
>>> A = list(A)
>>> A[2]= 1
>>> A
[3, 4, 1]
>>> A = tuple(A)
>>> A
(3, 4, 1)

然而,這種資料的轉換還是要很小心,特別是從字典型別轉成串列型別時。例如:

>>> A = ['Bob','John','Alice']
>>> B = [30,40,50]
>>> D = dict(zip(A,B))
>>> D
{'Bob': 30, 'John': 40, 'Alice': 50}
>>> E = list(D)
>>> E
['Bob', 'John', 'Alice']
>>> F = tuple(D)
>>> F
('Bob', 'John', 'Alice')

字典中的鑰匙被保留了,但值不見了。

4.5. array 型別

array 型別不是Python 預設的型別,雖然Python 有許多很有彈性的型別可供使用,但是在處理矩陣計算時,我們並不想去特別地換算矩陣第(i,j) 個元素是對應到串列的哪一個位址,因此我們會直接利用已經開發好的套件,讓使用的過程更符合數學上的直覺。numpy 這個套件就是專門給數值計算使用的套件。假設您已經裝過這個套件,就可以使用到裡面的函式。這裡我直接把array 當作一個重要的型別來介紹。

>>> from numpy import *
>>> A = zeros([3,4])
>>> A
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> B = ones([2,3])
>>> B
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])
>>> A.shape
(3, 4)
>>> C = array([1,2,5,7,8])
>>> C
array([1, 2, 5, 7, 8])

不難看到,numpy 這個套件有點延續octave 處理矩陣的習慣,若我們要開一個大小是3*4 的零矩陣,我們使用zeros() 這個指令,為了要設定矩陣的大小,我們使用[3,4] 這個串列作為參數。同理,若我們要開一個每一個元素都是1 的矩陣,我們使用ones() 這個函式。我們看到當我們把A 或B 呼叫出來時,所顯示的型別為array([]),這說明骨子裡陣列還是以串列為基礎。只是當我們讓他變成陣列時,就多了一些方便可用的資訊與函式,例如我們可以用shape 來知道到底矩陣有多大。C 陣列是先給一個串列,然後再用array() 把他轉換成陣列型別。要呼叫array 型別中特定位置的值,我們用A[i][j] 來取二維矩陣的值,如果是一維陣列,我們用C[i] 即可,當然別忘了index 都是從0 開始。

4.6. matrix 型別

Python numpy 套件內的另外一個型別mat 可以製作出很像Octave 的矩陣和向量,取值的用法大致也相同,唯獨輸入時要不先從 array 輸入好值之後再用mat() 函式轉換,也可以直接用mat() 函式搭配字串的方式輸入。範例如下:

>>> A = mat('[3 5 7;9 8 2; 1 3 3]')
>>> A
matrix([[3, 5, 7],
        [9, 8, 2],
        [1, 3, 3]])
>>> b = matrix('[2;3;4]')
>>> y = A*b
>>> y
matrix([[49],
        [50],
        [23]])
>>> A[0,:]
matrix([[3, 5, 7]])
>>> A[:,1]
matrix([[5],
        [8],
        [3]])
>>> A[2,2]
3

array 型別和matrix 型別對應的算子意義不同,要很小心。線性代數裡面的習慣是matrix 型別,例如矩陣乘法,若A ,B 矩陣是array 型別,A*B 是元素對元素相乘。

>>> A = array(A)
>>> A
array([[3, 5, 7],
       [9, 8, 2],
       [1, 3, 3]])
>>> B = array(B)
>>> B
array([[3, 0, 7],
       [9, 8, 2],
       [1, 3, 3]])
>>> C = A*B
>>> C
array([[ 9,  0, 49],
       [81, 64,  4],
       [ 1,  9,  9]])

若A,B 矩陣是matrix 型別,A*B 是線性代數裡的矩陣相乘。

>>> A = mat(A)
>>> A
matrix([[3, 5, 7],
        [9, 8, 2],
        [1, 3, 3]])
>>> B = mat(B)
>>> B
matrix([[3, 0, 7],
        [9, 8, 2],
        [1, 3, 3]])
>>> C = A*B
>>> C
matrix([[ 61,  61,  52],
        [101,  70,  85],
        [ 33,  33,  22]])

5. Python 的流程控制

和多數的程式語言一樣,基本的流程控制不外乎if,for,while 等。在正式介紹前我們先簡單介紹Python 的判斷式。基本的判斷式如下:

5.1. if 敘述

if 敘述會搭配else if 還有 else,在Python 裡面的 else if 寫作elif。if 和elif 之後加判斷式,然後以":" 作為內容敘述的開始,內容敘述需要退縮。由於Python 不用end 或是括符來判斷內容敘述結束的位置,因此相同層級的內容要對齊。範例程式如下

>>> x = int(raw_input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
...     x = 0
...     print ’Negative changed to zero’
... elif x == 0:
...     print ’Zero’
... elif x == 1:
...     print ’Single’
... else:
...     print ’More’

5.2. while 敘述

while 敘述和if 很像,後面加判斷式,然後以":" 作為內容敘述的開頭。範例如下,

>>> i = 0
>>> while i < 10:
...     i+=1
...     print i
...     
... 
1
2
3
4
5
6
7
8
9
10
>>> 

5.3. for 敘述

for 敘述用屬於(in) 後面加上有序或無序的型別,然後依該型別的長度來控制程式跑的次數。範例如下,

>>> name = ['John','Bob','Emily','Glophy']
>>> for x in name:
...     print x, len(x)
...     
... 
John 4
Bob 3
Emily 5
Glophy 6
>>> 

這樣的寫法,不需要先計算name 到底有多長,並且可以讓x 直接提取name 裡面的內容。若只是單純的需要一個固定長度的序列,我們用range(n) 來控制。

5.4. pass 敘述

pass 這個敘述就是甚麼都不做的意思,雖然甚麼都不寫,程式就甚麼都不會作,但是保留這一個敘述有一個好處,就是當我們有一大段程式暫時沒寫完,或是未來才要繼續撰寫,我們就可以先用pass 敘述帶過。這樣也方便提醒未來還要回頭來繼續撰寫程式,而不會因為空白而忘記要回來撰寫。

5.5. break 與continue 敘述

break 與 continuous 這兩個指令都是借用C 語言的語法,break 會中斷最靠近的迴圈並結束該迴圈,而continue 會跳出最靠近的迴圈,並且繼續執行下一個迴圈值。範例如下,

>>> for n in range(2, 10):
...     for x in range(2, n+1):
...         if n % x == 0 and x<n:
...             print n, ’equals’, x, ’*’, n/x
...             break
...     if x==n:
...         # loop fell through without finding a factor
...         print n, ’is a prime number’
...
...
...
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

這裡我們看到,break 會中斷x 迴圈,但不影響n 迴圈的進行。另外一個範例是關於continue,

>>> for i in range(10):
...     if i%2==1:
...         continue
...         print i
...     else:
...         print i
...
...
...
0
2
4
6
8

我們看到緊接著continue 之下的指令沒有被執行,並且continue 也沒有中斷i 迴圈的繼續進行。

6. 小試身手

在更詳細介紹Python 程式之前,經過前面學到的一些初步入門技巧,我們已經可以開始著手處理一些數學小問題。這一章希望用到數學的數列公式,讓對程式恐懼的同學知道如何把數學轉換成程式。

6.1. Fibonacci 數列

Fibonacci 數列與許多自然界的事物有關,0, 1, 1, 2, 3, 5, 8, 13, ... 從第三項開始,每一項是前兩項的總和。我們先給定一個正整數N (N>3),然後利用Python 程式製作一個長度為N 的Fibonacci 數列。程式如下

>>> N =30
>>> F = [0,1]
>>> for i in range(2,N):
...     F.append(sum(F[-2:]))
...     
... 
>>> F
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,
4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229]
>>> len(F)
30
>>> 

由於Fibonacci 序列的前兩個項數是給定的,所以我們先定義F 是一個長度是二的串列,內容是[0,1],之後的項數就是前兩項的和。這裡我們利用到sum() 函數來計算串列的總和,F[-2:] 這個用法,是指F 串列的倒數第二個元素到最後一個元素。最後兩個元素的總和,成為新的元素的值,因此append 到串列之後。我們把F 列出來,並且用len() 函數檢查F 的長度。

6.2. 判斷質數

給定一個正整數N ,我們判斷該整數是否為質數。數學上我們會檢查該整數N 是否能被根號N 以下的整數來整除,若找不到因數,那麼該整數就是質數。程式如下,

>>> from math import sqrt
>>> N = 31
>>> i = 2
>>> flag = N%i
>>> if flag ==0:
...     print 'The number', N, 'has factor', i       
>>> while (flag<>0) and (i<sqrt(N)):
...     i+=1
...     flag = N%i
...     if flag == 0:
...         print 'The number', N, 'has factor', i
...     
... 
>>> if flag <> 0:
...     print 'The number', N, 'is prime'
...     
... 
The number 31 is prime

這裡我們因為要使用到根號(sqrt) 我們要從math 套件取出sqrt() 這個函式。用法是 from math import sqrt,也有人常常懶惰地用from math import * 直接把math 裡面的函數全部叫出來。

我們試著把程式和我們檢查質數的方法作一個對應。一開始給定N 的值(假設是31),第一個我們會檢查的因數是2 ,所以i = 2。然後我們要看看N 是否會被i 整除,紀錄是否有整除的標誌,我習慣用flag 來代表,因此flag = N%i。若整除,則flag 等於0 。所以下一行if flag==0: 那我們就印出數字N 有因數i 。

接著當不能整除時(flag<>0) 並且i < 根號N: 我們就檢查下一個可能的因數,所以i+=1。接著重複同樣的步驟。如果i 大於等於根號N 了,會跳出while 迴圈。若這時的標誌flag 還是非零,表示根號N 之前可能的因數都無法整除N ,那麼N 就是質數。

6.3. 拉丁矩陣

拉丁矩陣是一個n 乘n 的整數方陣,每一行與每一列的值都介於0 到n-1 之間,並且每一個值在每一行每一列間都不重複。給定一個正整數n 我們希望程式可以亂數產生一個拉丁矩陣。範例如下,

>>> from numpy import array, zeros
>>> from random import sample
>>> n = 5
>>> A = zeros([n,n])
>>> for i in range(n):
...     A[i][:] = sample(range(n),n)
...
>>> #check whether A is a latin matrix
>>> c = 0
>>> for i in range(n):
...     for j in range(n):
...         tmp_i = range(n)
...         tmp_j = range(n)
...         tmp_i.remove(i)
...         tmp_j.remove(j)
...         #check the same column
...         for k in tmp_i:
...             if A[i][j]==A[k][j]:
...                 c+=1
...                 break
...         #check the same row
...         for k in tmp_j:
...             if A[i][j]==A[i][k]:
...                 c+=1
...                 break
...
>>>  while c<>0:
...      c=0
...      for i in range(n):
...          A[i][:] = sample(range(n),n)
...      #check whether A is a latin matrix
...      for i in range(n):
...          for j in range(n):
...              tmp_i = range(n)
...              tmp_j = range(n)
...              tmp_i.remove(i)
...              tmp_j.remove(j)
...              #check the same column
...              for k in tmp_i:
...                  if A[i][j]==A[k][j]:
...                      c+=1
...                      break
...              #check the same row
...              for k in tmp_j:
...                  if A[i][j]==A[i][k]:
...                      c+=1
...                      break
...
>>> A
array([[ 1.,  2.,  0.,  3.,  4.],
       [ 3.,  1.,  2.,  4.,  0.],
       [ 0.,  4.,  3.,  1.,  2.],
       [ 2.,  3.,  4.,  0.,  1.],
       [ 4.,  0.,  1.,  2.,  3.]])

這個程式因為要使用到numpy 裡面的zeros() 以及array() 函式,所以第一行我們用from numpy import array, zeros 。這裡我們看到import 後面如果要引進好幾個函式,我們可以用逗號不厭其煩地把要引進的函式寫上來。當然,如果需要引進的函式過多,就乾脆用* 引進所有套件內的函式。如 from numpy import *

我們製作Latin 矩陣的策略是用亂數排序,決定每一個列的值,然後檢查每一行與每一列看看是否有重複,如果有,就重新製作矩陣,如果沒有,就結束。因此,我們要用到亂數的套件。我們使用from random import sample 。sample 的語法是的語法是在一個population 裡面取k 個不重複的值出來。因此,我們若把population 設作range(n),而又正好取n 個值出來,就會變成是0,...,n-1 個數字的亂數排序。程式中我們用到break 指令讓c 不等於0 時,表示這個矩陣已經失敗了,就無須繼續計算。

如果讀者照著上述的程式打在自己的IDLE 裡面,沒什麼意外,很容易因為打字錯誤等因素,讓程式無法執行。特別是,這個程式還不是一個優化過的程式,我們可以看到,其中有幾段程式是不斷重複地。這些不斷出現的程式區塊若可以用一個副程式來表示,不但可以精簡程式內容,讓程式更容易閱讀,也可以減少出錯地機會。另外,若程式本身可以變成一個套件中的程式,就可以避免因為打字的錯誤,來減少使用IDLE 的困難。下一個章節,我們要介紹如何撰寫副程式以及製作屬於自己的套件。

7. 副程式與套件

從先前的經驗我們看到,重複的程式區段會讓程式變得冗長不容易閱讀,並且許多常用的程式可以集結成一個套件,方便程式的攜帶與使用。接下來我們就介紹如何撰寫Python 的副程式。

7.1. 副程式

Python 的副程式用def 開頭,然後是程式名稱,接著是參數區。和控制迴圈的語法一樣":" 之後開始副程式內容的撰寫,最後傳回值是用 return 指令回傳。範例如下:

>>> def myfunction(x,y):
...     from random import random
...     if x<y:
...         return y
...     else:
...         if random()<0.5:
...             return x*y
...         else:    
...             return x
...     
... 
>>> myfunction(1,3)
3
>>> myfunction(3,2)
6

這裡我們看見副程式的基本結構,並且我們看到在副程式裡如果欠缺一些套件裡的程式,我們一樣可以呼叫使用。

7.2. 套件

要製作套件很簡單,我們可以用簡單的文字編輯器開啟一個檔名是.py 結尾的文字檔,然後把我們想寫得副程式全部放在裡面,然後就可以用引進套件的方式來使用這些副程式了。舉例來說,我們製作一個mytools.py 檔案,其內容如下:

from numpy import sqrt
from math import pi

def myfun1(x):
    return sqrt(x/pi)

def myfun2(x,y):
    return x**2+x*y+y**2+1

存檔後我們在IDLE 裡面執行。

>>> from mytools import myfun1
>>> myfun1(3)
0.97720502380583985
>>> myfun2
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'myfun2' is not defined
>>> from mytools import *
>>> myfun2(2,5)
40
>>> 

我們看到把一堆副程式集結在一起,就變成一個套件,然後就可以用引進套件中函式的方法把我們要的函式抓進來。當myfun2 還沒有被引入時,我們直接使用它會出錯。其實這裡少說明一件事,就是這個套件可以被引用還必須要把此套件存放在與Python 預設的起始目錄相同才可以。相信有些讀者在這一關會遇到錯誤,就是IDLE 顯示沒有此套件。有時Python 起始的目錄會在很奇怪的地方,這和我們習慣管理我們檔案的目錄不同時,就會發生錯誤。因此,我們需要知道Python 現在到底在哪裡工作?

7.3. 取得工作中的目錄

我們可以使用os 套件裡的getcwd() 來取得現在工作的路徑。這個指令其實沒有很難背get"取得"、c "current"、w "work"、d "directory" 就成了取得目前的工作目錄。

>>> import os
>>> os.getcwd()
'/home/glophy/python'

如果你沒有看到"/home/glophy/python" 不要太驚訝,因為這是我的電腦,我才取名叫"glophy",通常你會看到的是你自己使用Python 的預設目錄,沒什麼意外當然不一樣。如果你的確看到"/home/glophy" 那有兩種可能,第一、你用了我的電腦。第二、有人暗戀我!(抱歉,我死會囉,下次請早)

7.4. 移動工作的目錄

假設你的套件放在別的位置,例如"/home/glophy/python/toolbox",要引進此套件時就需要先移動工作的目錄到這個位置才能使用該目錄。因此,我們需要懂得如何移動工作中的目錄。方法在os.chdir() 裡面:

>>> os.getcwd()
'/home/glophy/python'
>>> os.chdir('/home/glophy/python/toolbox')
>>> os.getcwd()
'/home/glophy/python/toolbox'
>>> 

瞧!正男的Python 城堡移動了。

7.5. 加入自己的工作區

假若這個toolbox 是每次必經之路,那麼每次開始Python 程式都需要這樣移動來移動去的不是很麻煩嗎?況且,Python 的套件一定也不是全部都放在同一個目錄下,所以,我們應該有方法可以將這個toolbox 目錄變成是Python 會去搜尋的目錄。為了追求一勞永逸的方法,我們使用建立startup.py 檔案的方法,來達到自動執行一些經常需要在開始Python 時輸入的指令。

在你的使用者目錄下(我假設你已經使用了Linux 系統),先用ls -a 指令看看有沒有一個.pythonrc.py 的檔案,如果沒有就自行用文字編輯器建立一個.pythonrc.py 文件,內容是你經常需要使用的指令(如更改工作目錄,增加Python 自動搜尋的目錄,或是經常要匯入的套件),以下是範例。

import os
os.chdir('/home/glophy/python')
import sys
sys.path.append('/home/glophy/python/toolbox')

然後在.bashrc 下面加入這兩行

alias python='python -i ~/.pythonrc.py'
alias bpython='bpython -i ~/.pythonrc.py' 

這樣在下次啟動python 或是bpython 時就會自動地把工作目錄移到我們想要的地方,並且放在toolbox 底下的目錄也會自動被python 搜尋到。

7.6. 程式減肥了

當我們學會如何撰寫副程式與套件並且設定我們的工作環境,就可以讓我們的程式變得更為精簡。首先我們以計算Fabonacci 數列為例,若我們要更改數列的長度,每次都要重打程式是非常不方便的,不如我們製作一個Fabonacci 的副程式,之後只要把N 的值丟進副程式裡,就可以得到新的Fabonacci 序列。

假設你已經做好了先前介紹的工作區設定,並且在"~/python/toopbox" 目錄下有一個mytools.py 這個檔案,你可以增加下列程式到mytools.py 裡面,然後記得存檔。

def fibonacci(N):
    F = [0,1]
    for i in range(2,N):
        F.append(sum(F[-2:]))

    return F

接著我們在IDLE 裡面,

>>> from mytools import fibonacci
>>> fibonacci(5)
[0, 1, 1, 2, 3]
>>> x = fibonacci(1000)
>>> x[999]
2686381002448535938614672720214292396761660931898695234012317599761798170024788
1689338369654483356564191827856161443356312976673642210350324634850410377680367
334151172899169723197082763985615764450078474174626L
>>> 

就可以從mytools 套件中叫出fibonacci 函式使用。我們看到第1000項Fibonacci 項是一個超大的數字,當這個數字太大時,Python 用長整數的方式儲存,因此數字後面有一個L(Long),代表長整數。

接下來我們試著利用副程式與套件的寫法來縮減製作Latin matrix 的程式。我們可以把程式拆解成幾個部份,第一、亂數產生可能的拉丁矩陣,第二、檢查是否為拉丁矩陣。所以流程是先給一個可能的拉丁矩陣,然後檢查是否為拉丁矩陣。如果是,就回傳,如果不是,就再重選一次可能的拉丁矩陣。我們建立一個名為latin.py 的套件,內容如下:

from numpy import array, zeros
from random import sample

def latin_ini(n):
    A = zeros([n,n])
    for i in range(n):
        A[i][:] = sample(range(n),n)

    return A

def islatin(A):
    c = 0
    n = A.shape[0]
    for i in range(n):
        for j in range(n):
            tmp_i = range(n)
            tmp_j = range(n)
            tmp_i.remove(i)
            tmp_j.remove(j)
            #check the same column
            for k in tmp_i:
                if A[i][j]==A[k][j]:
                    c+=1
                    break
            #check the same row
            for k in tmp_j:
                if A[i][j]==A[i][k]:
                    c+=1
                    break

    if c==0:
        return True
    else:
        return False

def latin(n):
    #initialize possible latin matrix
    A = latin_ini(n)

    #check whether A is a latin matrix
    flag = islatin(A)
    while flag == False:
        A = latin_ini(n)
        flag = islatin(A)

    return A

這樣的安排可以很清楚的看見主程式latin() 變得非常簡潔。在使用上,我們在IDLE 裡面鍵入:

>>> from latin import *
>>> latin(2)
array([[ 1.,  0.],
       [ 0.,  1.]])
>>> latin(3)
array([[ 2.,  0.,  1.],
       [ 0.,  1.,  2.],
       [ 1.,  2.,  0.]])
>>> 

就可以很輕鬆地製作拉丁矩陣了。

7.7. 函式的註解與說明

通常當自己寫的函式越來越多時,久了就會忘記原本是為了甚麼目的要寫這個函式,或是我們會將寫好的函式分享給其他人使用。這時好的註解與說明就非常重要。註解通常寫在程式中,Python 的註解是"#" 號開頭,跟在"#" 之後的文字,不會被當作程式來解讀。

當我們定義一個新的函式時,我們可以緊接在def 的下一行寫一下這一個函式的使用說明。我們用連續三個"來框出說明的內容,寫函式說明內容的好處是我們可以用print 函數來觀看函數的用法。舉例如下:

>>> def myfunc(x,y):
...     """
...     x,y are integers, the output is x-y
...     """
...     return x-y
...     
... 
>>> print myfunc.__doc__

    x,y are integers, the output is x-y

這裡的__doc__ 就是這個函式的說明部份,有時我們會在說明中加上使用範例,有了這樣的說明,使用者就更清楚這個函式的使用方法了。

8. 檔案的操作

學程式有沒有學到檔案的操作差別很大,如果沒有學到這一步,要不只能在特定程式環境下處理問題,要不對於廣大的應用問題就只能採取迴避的態度。這是開啟應用大門的重要鑰匙,請學生們不要過度恐懼。如果你害怕因為程式的錯誤而毀損檔案,很簡單,在跑程式前先把程式備份一下,只在備份過的檔案上試驗。反正現在硬碟的儲存空間都很大,不差多複製這一個檔案。

為了方便解說,我們先給定一個簡單的檔案,檔名是test.txt 其內容如下:

This is a test of python.

This is the third line.
This is the fourth line.
This is the final line.

這個檔案有五列,第二列是空白列。

Python 借用C 語言的語法,要開啟一個檔案,需要先設定一個檔案的物件。例如我們要讀取上述的test.txt ,我們先用open() 函式,將text.txt 這個檔案指定到檔案物件。

>>> F = open('test.txt','r')
>>> F.readlines()
['This is a test of python.\n', '\n', 'This is the third line.\n', 'This is the
 fourth line.\n', 'This is the final line.\n']
>>> F.close()

程式的第一列是指定F 為處理'test.txt' 的檔案物件,參數'r' 是說明接下來我們只對檔案作讀取的動作,只做讀取的動作並不會對檔案造成損害。因為Python 幾乎所有東西都是物件,所以F 作為檔案物件它有的函數也很容易理解。readline() 和readlines() 是讀取資料最常用的函式,有s 的會把資料一次讀出,沒s 的會逐行讀取。

'\n' 是換行的意思,若我們要寫入資料,並且希望寫入的資料換行,我們也是要加入'\n'字串。當資料讀取後,若沒有用close() 函數釋放讀取檔案的權限,會讓其他程式在讀取此文件時遇到一些小麻煩。因此,當確定不再對檔案進行任何處理時,我們會用F.close() 來釋放使用該檔案的權限。

8.1. 讀檔的技巧

許多統計好的資料是用特定的格式儲存在檔案裡,透過固定格式的存取方式,這些資料得以在不同的平台上交流。一般來說,檔案中紀錄這些格式的部份,我們稱為一個檔案的profile,profile 會告訴人們如何去讀取紀錄在檔案內的資料。例如,微軟的word、power point 或是 excel 檔,這些檔案都有固定的profile 告訴人們他們是如何將資料紀錄在檔案裡。假若我們不知道檔案的格式,最古老的方法就是將資料讀出之後檢查並歸納資料儲存的規則。以下是一個名為data.csv 的範例,雖然多數人都清楚csv 是如何被儲存。

Name	Age
John	30
Bob	29
Alice	24

如果我們預先知道檔案的結構是一列一列往下紀錄的話,我們可以用很精簡的方式讀取這類檔案。

>>> for line in open('data.csv','r'):
...     print line
...     
... 
Name,Age

John,30

Bob,29

Alice,24

>>> 

這裡我們並沒有指定一個變數作為開檔使用,我們也不用設定一個變數去紀錄讀取那一行,也沒有用到open() 和close() 函數。line 這個變數會是字串型別,我們可以對這個字串作更細緻的處理。例如,我們想要從檔案裡面讀出Bob 的年紀。我們可以用下列的方法:

>>> for line in open('data.csv','r'):
...     line = line.rstrip('\n')
...     x = line.split(',')
...     if x[0] == 'Bob':
...         print x[1]
...         
...     
... 
29
>>> 

這裡我們介紹兩個字串重要的函數,第一是rstrip(),這個函數會刪除括號裡的字串,因為line 的最後一個字元是換行字元'\n' ,在整理資料時這個字元會會會帶來一些麻煩,所以我們先刪除。再來是split() 函數,這個函數會以括符裡的內容當作分割資料的節點,範例中我們以',' 當作分割的節點,之後把資料變成串列的方式儲存出來。然後我們檢查,如果x[0] 所儲存的名字是Bob 就顯示他的年紀。

8.2. 寫入檔案的技巧

假若我們有一個矩陣,其內容為

>>> A
array([[ 2.77683213,  2.18282477,  3.75829991,  0.59203534],
       [ 7.36885893,  2.76181867,  4.89250421,  7.7363254 ],
       [ 1.46742936,  4.13023438,  4.37342687,  0.36814209]])
>>> 

要將此矩陣的資料寫入csv 檔,我們用以下的程式。

>>> F = open('output.csv','w')
>>> for i in range(3):
...     line = str(A[i])
...     line = line[2:-2]
...     x = line.split('  ')
...     while len(x)>1:
...         F.write(x.pop(0))
...         F.write(',')
...         
...     F.write(x.pop())
...     F.write('\n')
...     
... 
>>> F.close()

line = str(A[i]) 這行會將A 矩陣的第i 列的資料轉成字串,但因為字串的前頭和後頭有括符"[ ]" 所以要把最前頭和最後頭非資料的部份移除。之後我們利用split() 指令把分隔符號' '去掉,變成串列。當寫入檔案沒有寫到最後一個元素時我們用','來分隔寫入csv 檔,當最後一個資料寫入時,我們用換行符號寫入。

8.3. 使用pickle 套件

當我們使用Python 跑出一些資料,並不需要被其他作業平台,或通用的程式來使用時,Python 自己也有方便儲存和讀取的檔案管理程式,我們可以使用pickle 套件,來達到快速儲存檔案和讀取檔案的功能。要儲存資料時,我們主要使用到pickle 套件中的dump() 函式,要讀取資料時我們用load() 函式。範例如下:

>>> A
array([[ 3.30824587,  9.30210078,  2.49705893,  6.4262162 ],
       [ 5.03822582,  1.13081658,  4.4522248 ,  2.42150153],
       [ 6.95038582,  5.43423605,  1.16215867,  4.17716922]])
>>> F = open('matrix_data.pkl','w')
>>> import pickle
>>> pickle.dump(A,F)
>>> F.close()
>>> B = pickle.load(open('matrix_data.pkl','r'))
>>> B
array([[ 3.30824587,  9.30210078,  2.49705893,  6.4262162 ],
       [ 5.03822582,  1.13081658,  4.4522248 ,  2.42150153],
       [ 6.95038582,  5.43423605,  1.16215867,  4.17716922]])
>>> 

pickle.dump() 函式的用法是資料,然後接要存入的檔案。這樣我們就不需要特別去想要用甚麼格式去紀錄資料,dump 進去就對了,當然存檔後要記得關檔。要讀取時,我們用load() 函式,這裡用比較簡潔的寫法,直接把open() 出來的物件指給load() 當參數。我們可以比較一下,矩陣B 與A 的值的確完全相同。

當然,並不是一個檔名只能存取一個變數所代表的資料。當我們有很多變數所代表的資料要存到一個檔案時,我們就依序把這些資料dump 進去。然後再依序讀出。例如:

>>> x = [1,3,5]
>>> y = ('John', 'Bob', 'Emily')
>>> z = {'key1':1,'key2':30,'key3':[8,5,3,(7,4,9)]}
>>> A
array([[ 3.30824587,  9.30210078,  2.49705893,  6.4262162 ],
       [ 5.03822582,  1.13081658,  4.4522248 ,  2.42150153],
       [ 6.95038582,  5.43423605,  1.16215867,  4.17716922]])
>>> F = open('multi_data.pkl','w')
>>> pickle.dump(x,F)
>>> pickle.dump(y,F)
>>> pickle.dump(z,F)
>>> pickle.dump(A,F)
>>> F.close()
>>> G = open('multi_data.pkl','r')
>>> x1 = pickle.load(G)
>>> x1
[1, 3, 5]
>>> y1 = pickle.load(G)
>>> y1
('John', 'Bob', 'Emily')
>>> z1 = pickle.load(G)
>>> z1
{'key3': [8, 5, 3, (7, 4, 9)], 'key2': 30, 'key1': 1}
>>> A = pickle.load(G)
>>> A
array([[ 3.30824587,  9.30210078,  2.49705893,  6.4262162 ],
       [ 5.03822582,  1.13081658,  4.4522248 ,  2.42150153],
       [ 6.95038582,  5.43423605,  1.16215867,  4.17716922]])
>>> B = pickle.load(G)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/usr/lib/python2.7/pickle.py", line 1378, in load
    return Unpickler(file).load()
  File "/usr/lib/python2.7/pickle.py", line 858, in load
    dispatch[key](self)
  File "/usr/lib/python2.7/pickle.py", line 880, in load_eof
    raise EOFError
EOFError

我們看見load 出來的資料是依照dump 進去的順序,如果我們超過dump 進去的次數,會出現錯誤訊息。當然,我們無法記住若干年前使用pickle 儲存的次序與變數名稱,所以我們通常會使用別的變數名稱來取得資料,但是要如何知道load 出來的資料是甚麼型別呢?我們可以使用type() 函數來取得變數所對應的資料型別。例如:

>>> type(x1)
<type 'list'>
>>> type(y1)
<type 'tuple'>
>>> type(A)
<type 'numpy.ndarray'>

8.4. 與Octave 間的資料交換

Octave 是一套很方便處理矩陣相關的程式,在處理大資料時我們時常使用Python 作資料的處理,將資料變成矩陣形式之後,熟悉Python 的人可以直接使用Python 的套件來處理矩陣的計算,不熟悉的人可以把資料寫成mat 檔讓Octave 使用。我們用scipy.io 套件下的loadmat() 和savemat() 來讀取和儲存mat 檔。使用方式如下:

>>> from scipy.io import savemat, loadmat
>>> A
array([[ 0.,  1.,  2.,  3.,  4.],
       [ 1.,  2.,  3.,  4.,  5.],
       [ 2.,  3.,  4.,  5.,  6.]])
>>> data = {'A':A}
>>> savemat('Python2octave.mat',data)

儲存array 型別檔案到mat 檔,為了讓變數也被攜帶過去,我們用字典型別來連結變數名稱和變數內容。所以,我們使用data={'A':A} ,這樣前面的'A' 會被Octave 當作是變數名稱,後面的A 會是變數的內容。

>>> data2 = loadmat('octave2python.mat')
>>> data2
{'A': array([[ 0.94350849,  0.12175604,  0.9382679 ,  0.64581014,  0.11789431],

       [ 0.96681409,  0.64503332,  0.90802475,  0.77103722,  0.53668295],
       [ 0.83709097,  0.68478333,  0.1933252 ,  0.22569427,  0.44437615],
       [ 0.82716025,  0.91637864,  0.39121335,  0.15870892,  0.973453  ],
       [ 0.80987658,  0.90737243,  0.06761126,  0.68397593,  0.43275306]]), '__
version__': '1.0', '__header__': 'MATLAB 5.0 MAT-file, written by Octave 3.2.4,
 2012-01-26 09:58:05 UTC', '__globals__': []}
>>> A = data2['A']
>>> A
array([[ 0.94350849,  0.12175604,  0.9382679 ,  0.64581014,  0.11789431],
       [ 0.96681409,  0.64503332,  0.90802475,  0.77103722,  0.53668295],
       [ 0.83709097,  0.68478333,  0.1933252 ,  0.22569427,  0.44437615],
       [ 0.82716025,  0.91637864,  0.39121335,  0.15870892,  0.973453  ],
       [ 0.80987658,  0.90737243,  0.06761126,  0.68397593,  0.43275306]])

讀檔也很簡單,讀入的mat 檔也會以字典型別呈現,要取出資料,直接用鑰匙取值的方式即可。有了這招,就可以讓Python 和Octave 合作了。

8.5. 影像資料讀取

若我們要處理的資料來源是圖檔,最方便處理的方式是先把影像資料讀成矩陣格式,然後再對矩陣裡的值進行處理。讀取影像資料至矩陣的方式如下:

>>> import scipy.misc as pnimg
>>> img1 = pnimg.imread('cat.jpg')
>>> img1.shape
(360, 360, 3)
>>> type(img1)
<type 'numpy.ndarray'>
>>> img1[100:105,100:105,0]
array([[214, 206, 194, 181, 161],
       [196, 188, 175, 157, 139],
       [207, 188, 171, 142, 117],
       [217, 194, 178, 148, 117],
       [219, 198, 172, 155, 123]], dtype=uint8)

我們建議使用matplotlib.image 套件裡的imread() 函式來讀檔,讀入的檔案型別會是numpy 的ndarray,用shape 屬性可以觀看此影像的大小,他是一個三階的矩陣,分別對應RGB 三原色。要觀看特定區域的值,直接用array 讀取的習慣即可。例如img1[100:105,100:105,0] 就是讀出紅色矩陣(100 到104)*(100 到104) 這個區塊的值。

>>> img1[100:105,100:105,0] = 128
>>> pnimg.imsave('cat2.jpg',img1)

我們可以更改img1 的值,然後再回存回檔案。

9. 幾個常用的函式與招式

這裡我們介紹一些使用頻率較高的函式以及應用方式。

9.1. 使用raw_input()

有時候我們會需要讓程式等待使用者輸入一些特定的字串或是數字,取得這些字串或數字之後程式才繼續執行。這時我們會使用raw_input() 這個函數,來取得從鍵盤上輸入的資訊。raw_input() 的參數是一個要給使用者觀看的提示字串,用來說明希望使用者輸入那一類的資料,從鍵盤上取得的資訊會以字串的方式儲存在給定的變數中。若是數值資料,我們要用int() 或是float() 來變換資料型別。

>>> str1 = raw_input('enter a number:')
enter a number:32
>>> str1
'32'
>>> x = int(str1)
>>> x
32
>>> str1 = raw_input('enter a number:')
enter a number:8.57
>>> y = float(str1)
>>> y
8.57

9.2. 使用filter()

filter(判斷函數,序列) 的用法是會把序列中的值帶入判斷函數裡,如果判斷的值為真就保留,不然就刪除。舉例如下

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]

我們看到f(x) 會篩選不是2 也不是3 的倍數,所以2 到24 的序列中,只剩下這7 個數字通過。

9.3. 使用map()

map(函數,序列) 的用法是把序列中的值對應到函數裡成為新的序列,舉例如下:

>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

有時函數的輸入參數不只一個,我們可以把兩個序列放在後頭,但是要注意其長度必須相等。

>>> def add(x,y): return x+y
...
>>> map(add,range(3),range(2,5))
[2, 4, 6]
>>> map(add,range(3),range(2,6))
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 1, in add
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

當兩個序列的長度不同時,會出錯。

9.4. 產生亂數

產生亂數可以用random 套件裡面的函式,也可以用numpy.random 裡面的函式,其中numpy.random 裡面的函式比較完整,推薦的作法是把numpy.random 取成別名來使用。範例如下:

>>> import numpy.random as random
>>> random.random()
0.020675150031558842
>>> random.randint(1,10,5)
array([2, 1, 1, 6, 8])
>>> random.uniform(0,100,7)
array([ 10.77959537,  19.51317526,  33.50635597,  45.18479565,
        13.45436783,  90.93941707,  24.728344  ])
>>> random.randn(6)
array([-0.4827663 , -0.92881795, -0.15934045, -1.02052931, -0.16193882,
       -1.90333935])
>>> 

當我們將numpy.random 取名為random 之後,裡面的random() 函式是是從[0,1) 區間中選取一個浮點數。randint(最小值,最大值,長度) 可以製作需要長度與範圍的整數亂數,uniform(最小值,最大值,長度)可以製作需要長度與範圍的浮點亂數,而randn(長度)會製造符合長度的normal samplings。

9.5. 暫停程式

要暫停程式有幾種目的,一種是我們需要在程式執行中間檢查結果,這時我們會把資料輸出到螢幕上,為了避免資料顯示過快無法閱讀,所以要中斷程式。有幾種方法可以作到,例如只要中斷幾秒鐘的時間,我們使用time 函數。如果要中斷後,再由鍵盤輸入然後繼續,我們可以用raw_input() 函數。使用方法如下:

>>> import time
>>> time.sleep(5)

這一行會讓程式暫停5 秒,如果我們需要更長得時間,我們可以用下列方法:

>>> for i in range(5):
...     x = raw_input('Press enter to continue')
...     print i
...     
... 
Press enter to continue
0
Press enter to continue
1
Press enter to continue
2
Press enter to continue
3
Press enter to continue
4

這樣,當我們壓下enter 鍵時,程式就會繼續。

9.6. 執行外部命令

使用os 套件中的system() 函式可以執行系統程式。system() 內放置執行輸入的字串,我們可以利用這個方式讓Python 和其他程式合作,例如C++ 或是octave 。以執行octave 為例,我們可以先寫一個octave 的.m 檔,在.m 檔結束時將資料寫入.mat 檔案,然後讓Python 去執行。範例如下:

>>> import os
>>> os.system('octave python.m')

這樣系統用octave 就會執行一個名為python.m 的檔案。

9.7. 計算執行時間

我們使用time 套件裡的clock() 函式來紀錄cpu 的時間。例如

>>> import time
>>> tic = time.clock()
>>> s = 0
>>> for i in range(10000):
...     s+=i
...     
... 
>>> toc = time.clock()
>>> toc-tic
0.7800000000000011

我們使用tic 來紀錄開始的時間,等程序完成後再用toc 來紀錄結束的時間,而toc-tic 就是這段時間差,也就是程式計算所花費的時間。

9.8. 閃避錯誤訊息

有時候程式遇到資料有例外狀況時會出錯,例如錯誤的值,錯誤的格式,或是資料有漏失(missing value),若處理的資料是比較大型的資料,有時測試時都沒狀況,跑好幾天之後才突然出錯,這是很可惜的事。因此,我們可以用嚴謹一點的方法來避開這樣的問題。try 指令與except 的配合,可以讓錯誤訊息出現時,避開中斷程式。

>>> x = ['33','24','52y','Hello','5.8']
>>> for y in x:
...     try:
...         z = int(y)
...         print z
...     except ValueError:
...         pass
...         
...     
... 
33
24

10. Python繪圖

下列我們介紹幾個經常使用的Python 繪圖技巧,當Python 也可以繪出精美的圖形並且輸出圖檔時,它就是一套可以專心學習輔助教學研究的工具了。

10.1. 使用pylab 套件繪圖

Python 的繪圖能力畢竟不是強項,因此無論是繪圖的操作方法或是習慣都還沒有太統一與方便,可以繪圖的方式有幾種,有人用matplotlib 的套件,有人用scipy 的套件。以下我僅介紹最簡單的方法,讓使用者可以看到圖,並且可以將輸出的圖拿到投影片或報告使用。 範例如下

>>> from pylab import *
>>> from numpy import *
>>> x = arange(0,2,0.01)
>>> y = 2*sin(2*pi*(x-1/4))
>>> plot(x,y)
[<matplotlib.lines.Line2D object at 0x9ffbb0c>]
>>> fig = figure(1)
>>> fig.show()
>>> fig.savefig('test.png')
>>> ylabel('y-axis')
<matplotlib.text.Text object at 0x9dd682c>
>>> xlabel('x-axis')
<matplotlib.text.Text object at 0x9dcfb2c>
>>> fig.show()
>>> fig.savefig('test1.png')
>>> title(r'$y=2\sin 2\pi (x-1/4)$')
<matplotlib.text.Text object at 0x9ddb4ec>
>>> fig.show()
>>> fig.savefig('test2.png')

這裡要注意,當我們使用plot() 指令時,並不會馬上繪圖,而是要下了show() 指令之後,才會看得到繪出的圖形。但因為版本的緣故,有時只能畫一次,第二次就會失敗,加上遠端要把螢幕的資料送回,對連程式都不太會寫的人來說又是另外一件複雜的故事。因此,我們乾脆不畫了,直接把圖輸出到一個通用的圖形檔。fig = figure(1) 是設定要輸出的圖是第一個圖框。然後fig.show() 是正式的繪圖。然後fig.savefig() 是將圖存檔。其餘的label 與title 指令都顯而易見。這樣我們就能在遠端將圖畫好,然後再用sftp 等軟體把繪好的圖像送回。

10.2. 動態繪圖

若我們計算PDE 數值解,我們一定會很想看到動態的熱傳導方程的熱量是如何地消散,以及波動方程是如何地擺盪,這時候我們會希望Python 也能動態繪圖。因為Python 的pylab 繪圖套件預設是會把命令列的shell block 住,繪圖的預設值也會將每一筆繪畫指令重複畫在同一張圖上,因此我們需要用pylab.ion() 這個指令,告訴Python 我們要進入互動狀態,就是畫好圖後,就跳回shell。並且我們要做清圖的動作,就是讓繪圖不要hold 住。範例如下:

>>> import pylab
>>> import random
>>> import time
>>>
>>> x = range(10)
>>> pylab.ion()
>>> pylab.hold(False)
>>> fig = pylab.figure()
>>> for i in range(10):
....    y = random.sample(xrange(100),10)
....    pylab.plot(x,y)
....    pylab.draw()
....    time.sleep(1)
....
>>>

這裡我們用draw() 而不用show(),因為show() 函式會把shell 中斷,透過draw() 才能成功使用互動狀態。time.sleep() 是暫停指令,先前我們介紹過,避免顯示過快什麼都看不到。

10.3. matplotlib 3D 繪圖

數學系學生最常需要3D 繪圖的機會是計算z=f(x,y) 的情形,無論是微積分或是PDE 的計算,當我們需要畫出一個3D 圖形,並且需要旋轉到一個特定的視角幫助我們可以更清楚地觀察圖形時,我們需要用到axes3d 套件。範例如下:

>>> import numpy as np
>>> from pylab import *
>>> import mpl_toolkits.mplot3d.axes3d as p3
>>> u = np.r_[0:2*pi:100j]
>>> v = np.r_[0:pi:100j]
>>> x = 10*np.outer(cos(u),sin(v))
>>> y = 10*np.outer(sin(u),sin(v))
>>> z = 10*np.outer(np.ones(size(u)),cos(v))
>>> fig1 = figure(1)
>>> ax = p3.Axes3D(fig1)
>>> ax.plot_wireframe(x,y,z)
<mpl_toolkits.mplot3d.art3d.Line3DCollection object at 0x1d3fcd0>
>>> ax.set_xlabel('X')
<matplotlib.text.Text object at 0x610ec30>
>>> ax.set_ylabel('Y')
<matplotlib.text.Text object at 0x611c710>
>>> ax.set_zlabel('Z')
<matplotlib.text.Text object at 0x6125150>
>>> fig1.show()

這裡我們介紹一個numpy 很容易創造出np.array 型別的等差數列方法,就是r_[]。他的使用方式很類似Octave 的linspace,r_[0:pi:100j] 的意思是起始值為0,結束於pi 長度是100 的等差數列。outer() 是做向量的outer product ,他會產生一個矩陣。ax = Axes3D(fig1) 是定義ax 是fig1 3D 的軸,ax.plot_wireframe(x,y,z) 是繪圖並且綁住軸,這樣當我們用滑鼠拉動軸時,圖形就會跟著旋轉。

如果我們要讓圖自動旋轉,我們修改上例寫成如下程式:

import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
plt.ion()

u = np.r_[0:2*np.pi:30j]
v = np.r_[0:np.pi:30j]
x = 10*np.outer(np.cos(u),np.sin(v))
y = 10*np.outer(np.sin(u),np.sin(v))
z = 10*np.outer(np.ones(np.size(u)),np.cos(v))
fig1 = plt.figure(1)
ax = p3.Axes3D(fig1)
ax.plot_wireframe(x,y,z)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

for angle in range(0,360):
    ax.view_init(30, angle)
    plt.draw()

11. Python 風格

因為Python 是數學人開發的軟體,所以有一些數學人共有的習慣。例如,我們寫證明題時會寫令x = y = z = 0 。在Python 裡也接受這樣的寫法。即

>>> x = y = z = 0

有時候對於一些起始值的設定,或是固定組合的變數要進行值的改變,我們不需要每一個變數佔用一行程式空間。我們可以用以下的方式縮減程式:

>>> a,b,c = 5,8,10
>>> a
5
>>> b
8
>>> c
10

這裡,a, b, c 三個變數直接放在同一行給值。

另外,剛計算完的結果,如果要提取使用,"_" 符號就是剛剛計算後的結果。例如:

>>> 3+7
10
>>> 5+_
15

11.1. 串列的技巧

假設我們要製造一個串列,是{i2}i=1...5 。常用的方法是我們先開一個空的串列,然後我們用for 迴圈的方式來製作這個平方串列。例如

>>> seq = []
>>> for i in range(1,6):
...     seq.append(i**2)
...     
... 
>>> seq
[1, 4, 9, 16, 25]

Python 提供了一個很精簡的方法來製作這類有公式可以表達的串列。

>>> seq1 = [i**2 for i in range(1,6)]
>>> seq1
[1, 4, 9, 16, 25]

第一次看到應該會"哇!" 一下吧!不只是一維的串列可以這樣用,高維度的串列,只要是可以用公式表達的都可以。舉例如下

>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

再看這一個例子

>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

會用這種寫法寫程式,就可以顯出Python 的風格了。

11.2. 有序輸出

串列和值組本來就是有順序的資料型別,我們可以用enumerate() 函式去強調元素在串列中的順序。

>>> for (i,v) in enumerate(['Bob','John','Alice']):
...     print i, v
...     
... 
0 Bob
1 John
2 Alice

以下是一個特別的Print 技巧,結合zip() 函式達到對應輸出的目的。

>>> questions = [’name’, ’quest’, ’favorite color’]
>>> answers = [’lancelot’, ’the holy grail’, ’blue’]
>>> for q, a in zip(questions, answers):
...
print ’What is your {0}? It is {1}.’.format(q, a)
...
What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.

字典型別雖然是沒有順序的,但是我們可以用iteritems() 函式,讓它變成有順序的。舉例如下

>>> knights = {’gallahad’: ’the pure’, ’robin’: ’the brave’}
>>> for k, v in knights.iteritems():
...
print k, v
...
gallahad the pure
robin the brave

11.3. 引用套件的技巧

從過去使用過的許多例子,我們發現要使用套件好像有許多方法。例如直接import

>>> import numpy

這樣的好處是當我們打上"numpy." 時IDLE 會跳出屬於numpy 的函式與屬性,這對於對套件不熟悉的人來說很好用。有時我們會使用名稱比較長的套件,當我們對此套件不熟悉時,每次又要打這麼一長串實在很麻煩,所以我們會使用下列方法:

>>> import matplotlib as mlab

這樣當我們打上"mlab." 時,也會跳出提示字串。當然,我們也可以用很精準的方式提取我們想要的函數,例如

>>> from numpy import arange, array

不只是套件的名稱可以替換,如果要引進的函數名稱過長,也可以用as 更改。例如

>>> from numpy import asfortranarray as af

我們只要活用 "from", "import" 和 "as" 就可以方便的提取我們要的函數,並安排成方便我們使用的方式。

11.4. Python 等號的矜持

Python 的等號就像小姑娘一樣矜持得很,我們習慣把等號左邊當作未來值,右邊當作歷史值,這是一般程式的習慣。但是數學裡的"=" 就是完全相等,所以,A 若等於B ,則B 也會等於A 。所以,當B 的內容改變時,A 的內容也跟著變。

>>> A
matrix([[3, 5, 7],
        [9, 8, 2],
        [1, 3, 3]])
>>> B = A
>>> B[0,0]= 0
>>> A
matrix([[0, 5, 7],
        [9, 8, 2],
        [1, 3, 3]])

要避免A 的值被改變,我們不是令B 等於A,而是拷貝A 的值給B。

>>> A
matrix([[0, 5, 7],
        [9, 8, 2],
        [1, 3, 3]])
>>> B = A.copy()
>>> B[0,0] = 3
>>> B
matrix([[3, 5, 7],
        [9, 8, 2],
        [1, 3, 3]])
>>> A
matrix([[0, 5, 7],
        [9, 8, 2],
        [1, 3, 3]])

若該型別有copy() 函數可以使用,我們用拷貝的方式把值複製過去,如果該型別沒有copy() 函數可以使用,通常用[:] 可以達到同樣效果。

>>> a = [1,3,5,7]
>>> b = a
>>> b[0] = 0
>>> a
[0, 3, 5, 7]
>>> b
[0, 3, 5, 7]
>>> b = a[:]
>>> b[0] = 1
>>> a
[0, 3, 5, 7]
>>> b
[1, 3, 5, 7]

12. 遠端操作技巧

會推薦Python 其中一個原因就是Python 適合處理大量的資料,現在的電腦網路硬體環境的趨勢是手持裝置要待機越久越好,需要耗CPU 的部份丟到雲端處理,大資料的計算不太可能用桌機或是筆記型電腦完成。所以,我們需要學習如何將寫好的程式放在計算伺服器上計算。

12.1. 遠端執行Python 程式

要遠端執行Python 程式,當然程式要放在遠端的server,我們可以直接在server 上編寫程式,也可以在client 端寫好程式再上傳。

假設程式名稱為program.py,最簡單的執行方式是直接在命令列上打python program.py。 另外一個方式是在prpgram.py 的第一行打上

#!/usr/bin/python

雖然"#" 是註解符號,但加了這一行之後程式就會用/usr/bin/python 這個程式來執行這一個py 檔。由於linux 系統不會讓非執行檔變成可執行,所以我們要先讓program.py 變成可執行檔。作法是

chmod +x program.py
./program.py

第一行chmod 是改變檔案屬性,+x 的意思是讓檔案變成可執行。第二行是執行該檔案。如果程式可以很快有結果,使用上述兩種方法當然沒問題。但若程式需要跑一陣子,我們會發現當網路離線時,程式就跳開了。因此,我們需要把執行程式和網路需要一直連線這件事脫勾。我們使用linux 的nohup 這個指令,來達到脫勾的功能。

nohup ./program.py &

用這行指令來執行program.py 就可以放心的把網路給斷線了。

13. 結語

這份文件只是將數學系的學生預備到可以開始學習數值分析的基本技巧,關於Python 程式還有許許多多很重要的主題沒有在這份文件中說明,希望藉由此文件將學生引進門之後,能讓學生對Python 的操作更有信心。

創用 CC 授權條款
Python 程式入門曾正男製作,以創用CC 姓名標示-非商業性-禁止改作 3.0 台灣 授權條款釋出。