小段落


7. 輸入(Input)與輸出(Output)

有很多的方式可以來表現一個程式的輸出結果,可以印出來在一個可讀的表格裡面,也可以寫入到檔案裡面供作未來使用。這一章裡面將談到一些可能的方法。


7.1 花俏的輸出格式化

到現在為止我們談到了兩種寫入值的方式:用expression的敘述( expression statements ),或是用 print 這個敘述。 (第三種方法是使用file物件的 write() 方法(method),這一個標準輸出所指向的檔案(standard output file),可以用 sys.stdout 來存取之。請參閱程式庫參考手冊上面對此的詳細說明。)

通常你會希望你對於輸出的結果能夠在格式上面稍有控制力,而不只是預設的用空白連結起來而已。有兩種方法可以來控制輸出的格式,第一種是自己動手來做字串的調整。你可以使用字串的切割(slicing)以及連結,做成任何你想要的效果。標準的 string module裡面有一些好用的東西,也可以幫助你填入適當的空白,使字串的寬度成為你想要的寬度,我們待會再來討論如何做。另外一個控制輸出格式的方法是使用 % 這個運算元,配合上用字串成為左邊的參數。這個運算元會翻譯左邊的這個字串參數,其功能類似於C裡面的 sprintf() 的字串參數,然後把右邊要控制的字串適當的填入,之後再傳回這個格式化的結果。

還有一個問題,如何把其他的值轉換成洽當的字串呢?幸好Python裡面的 repr() 函式可以轉換任何的值成為一個字串,你以可以把這個值寫在反撇號( ` ` )的中間也有同樣的效果。請看一些例子:

>>> x = 10 * 3.14
>>> y = 200*200
>>> s = 'The value of x is ' + `x` + ', and y is ' + `y` + '...'
>>> print s
The value of x is 31.4, and y is 40000...
>>> # Reverse quotes work on other types besides numbers:
... p = [x, y]
>>> ps = repr(p)
>>> ps
'[31.4, 40000]'
>>> # Converting a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = `hello`
>>> print hellos
'hello, world\012'
>>> # The argument of reverse quotes may be a tuple:
... `x, y, ('spam', 'eggs')`
"(31.4, 40000, ('spam', 'eggs'))"

底下我們示範兩種格式化的方法,這例子是寫入平方及立方值:

>>> import string
>>> for x in range(1, 11):
... print string.rjust(`x`, 2), string.rjust(`x*x`, 3),
... # Note trailing comma on previous line
... print string.rjust(`x*x*x`, 4)
...
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
>>> for x in range(1,11):
... print '%2d %3d %4d' % (x, x*x, x*x*x)
...
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000

(值得注意的是在數目字中間的空白是使用 print 的結果, print 總是會在每一個參數中間加入空白。)

這個例子示範了使用 string.rjust() 的方法,這個函式會使的一個字串在指定的寬度裡左邊加入空白來向右邊靠攏。另外兩個相類似的函式是 string.ljust() 以及 string.center() 。這些函式本身並沒有印出什麼東西來,他們只是傳回一個新的字串。如果傳回的字串太長了,他們也不會截斷它,他們只是單純的傳回這個新的字串。這有可能會使你的一欄一欄的格式變成亂七八糟,但是這樣做通常比其他的可能要好很多(可能會造成不正確的結果)。(如果你真想把多餘的部分截掉,你可以使用一個切割的動作,例如 "string.ljust(x, n)[0:n]" ) 。

另外有一個函式叫做 string.zfill() 這個函式會使的數目字的字串加入前頭的0。該加入正負號的時候它也會自動加入:

>>> import string
>>> string.zfill('12', 5)
'00012'
>>> string.zfill('-3.14', 7)
'-003.14'
>>> string.zfill('3.14159265359', 5)
'3.14159265359'

你如果使用 % 運算元的話結果會看起來像這樣:

>>> import math
>>> print 'The value of PI is approximately %5.3f.' % math.pi
The value of PI is approximately 3.142.

如果在你的格式化字串(format string)中有超過一個以上的格式存在,你要在 % 的右邊傳入一個tuple。例如這個例子:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
... print '%-10s ==> %10d' % (name, phone)
...
Jack ==> 4098
Dcab ==> 7678
Sjoerd ==> 4127

大部分的格式(format)其效果都與你在C裡面所用的一樣,你必須要在右邊傳入適當型態的資料。如果你沒有正確的如此做時,你會得到一個例外的狀況(exception),而不是得到一個系統核心傾倒出來的記憶體資料(dump)。其中 %s 這個格式最為自由,你可以使用字串或非字串,如果你使用非字串的資料時,資料會自動用內建的 str() 函式轉換成字串資料。你也可以使用 * 來傳入一個獨立的(整數)參數來決定寬度或是精確度(precision)的大小。但是,C裡面的 %n 以及 %p 在Python裡面卻沒有支援。

如果你有一個很長的格式化字串,而你又不想分開他們的話,你可以使用名稱而非位置來使用這些變數。其方法是使用C格式的延伸形式: %(name)format ,舉例如下:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; Dcab: %(Dcab)d' % table
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

這個功能當與新的內建函式 vars() 一起使用時特別有用,這個內建函式會傳回一個含有所有local變數名稱及值的dictionary。


7.2 讀寫檔案

open() 這個函式會傳回一個file物件。通常其用法是傳入兩個參數如: "open(filename, mode)".

>>> f=open('/tmp/workfile', 'w')
>>> print f
<open file '/tmp/workfile', mode 'w' at 80a0960>

第一個參數是一個包含檔案名稱的字串,第二個參數是另外一個字串,其內容是一些用來描述你要怎麼使用這個檔案的字元。 mode 可以是 'r' ,如果你想要這個檔為唯讀的話,也可以使用 'w' 如果你只想要寫入的話(如果該檔本來就存在的話,你會殺掉原來的檔案),你也可以用 'a' 表示你要在檔案的尾端加入東西, 'r+' 則會讓這個檔可以讀也可以寫。你也可以不傳入第二個參數,如果沒有傳入 mode 參數的話,會使用預設的 'r' 模式。

在Windows以及Macintosh系統上,你可以在mode裡面加入 'b' 表示要以二元模式(binary mode)開啟這個檔案,所以你也可以使用 'rb', 'wb', 以及 'r+b' 。在Windows裡面文字檔及二元檔是有區別的,在文字檔裡面行終止字元(end-of-line)在檔案的讀寫時是自動會被稍稍修改的。這個自動修改的動作對於一般的ASCII文字檔沒有什麼影響,但是會使得像是 JPEGs 或是 .EXE 之類的二元檔被損害。所以當你在處理這些檔案時特別注意要使用二元的模式。(值得注意的是,在Macintosh裡面文字模式的精確的語意是會隨著其背後所用的C程式庫而有不同的。)


7.2.1 File物件的Methods(方法)

底下的例子都假設你已經建立了一個叫做 f 的file物件。

如果你想讀一個檔案的內容你需要呼叫 f.read(size) 這個方法(method)。這個method會讀入某個數量的資料,然後將資料以字串的形式傳回。你也可以不傳入 size 這個數值參數,如果你沒有傳入或是傳入負值的話,就會將整個檔案都傳回。如果你的檔案比你的記憶體的兩倍還大的話,這是你自己要處理的問題。其他的情況下,都會讀入並傳回最多是 size 數量的位元組(byte)的資料。如果已經到了檔案的最尾端你還呼叫 f.read() 的話,回傳值就會是一個空字串 ("") 。

>>> f.read()
'This is the entire file.\012'
>>> f.read()
''

f.readline() 會一次只讀入一行,換行符號 (\n ) 仍然會被留在字串的最尾端,並且當檔案不是以換行符號結束時,最後一行的換行符號就會被忽略。這會使得傳回的結果不至於有混淆,當傳回值是空字串時,我們可以很有信心這已經是檔案的最尾端,因為空白的行還是會有 '\n' 單獨存在的。

>>> f.readline()
'This is the first line of the file.\012'
>>> f.readline()
'Second line of the file\012'
>>> f.readline()
''

f.readlines() 會傳回一個 list ,其內容是所有在檔案內的各個行的資料。如果你傳入第二個可有可無的 sizehint 參數時,會從檔案內讀入這個參數所代表的byte數目,並且把最後所在的那一整行也一並讀完。這一個方法通常用在一行一行的讀很大檔案時,如此可以增進讀的效率,並避免在記憶體中放置大量的資料。只有完整的行才會被傳回來。

>>> f.readlines()
['This is the first line of the file.\012', 'Second line of the file\012']

f.write(string) 會在檔案內寫入字串參數 string 所代表的內容,其傳回值是 None

>>> f.write('This is a test\n')

f.tell() 會傳回一個整數,代表目前這個file物件在這個檔案內的所在位置,其單元是從檔案開始處有多少個byte。你可以用 "f.seek(offset, from_what)" 來改變file物件的所在位置, from_what 參數代表從哪裡算起,0代表檔案的最開頭,1代表目前位置,2代表檔案的結尾處。呼叫這個函式file物件會跳到從 from_what 參數代表的位置算起 offset 個byte的距離的地方。如果 from_what 沒有傳入的話,會使用預設的 0,代表從檔案的最開頭算起。

>>> f=open('/tmp/workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5) # Go to the 5th byte in the file
>>> f.read(1)
'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
>>> f.read(1)
'd'

當你已經使用完畢這個file物件時,要記得呼叫 f.close() 把所有因為開檔所使用的系統資源都釋放掉。一但你呼叫了 f.close() 之後,任何的對file物件的動作都會自動的失敗。

>>> f.close()
>>> f.read()
Traceback (innermost last):
File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file

File 物件有一些其他的method可以用,例如 isatty() 以及 truncate() ,這些比較少用的method可以參考在程式庫參考手冊裡面有關file物件的說明。


7.2.2 pickle Module(模組)

從檔案寫入及讀出字串資料都沒有太大問題,但是數值資料則會比較麻煩。因為 read() 這個method 只傳回字串,你還得要將這個字串傳給類似 string.atoi() 這樣的函式來將代表數值的字串 '123' 轉成數值123。如果你要在檔案內儲存較複雜的資料型態例如lists、dictionaries、或是某個類別的物件時,那就更加複雜了。

為使使用者不需要自己寫程式來處理儲存這些複雜的資料型態,Python提供了一個標準的module叫做 pickle 。這個令人驚訝的module可以處理幾乎所有的Python物件(甚至是某些形式的Python程式碼!),並將之轉換成一個字串的表現方式。這個過程也叫做 pickling. R。從這個字串重新組合成我們所要的物件的過程則叫做 unpickling 。在這兩個過程之間,我們可以將這個代表物件的字串儲存成檔案或資料,或是在網路上傳給另一台機器。

如果你有一個 x 物件及一個可以寫入的file物件 f ,要pickle一個物件最簡單的方式只要一行程式就可以了:

pickle.dump(x, f)

如果file物件 f 是可讀的話,要unpickle這個物件只要這樣做:

x = pickle.load(f)

(這個module還有其他的用法可以pickling多個物件,或是你不想將這個pickled的資料寫入檔案。請參考在程式庫參考手冊內有關 pickle 完整的說明。)

pickle 也是一個標準的方法,可以將Python的物件儲存起來給其他程式語言使用,或是等待下一次啟動Python再用。技術上來說這叫做 persistent 的物件。因為 pickle 的運用如此廣泛,許多的程式設計師專門寫一些Python的延伸功能來處理諸如matrices這些新資料型態的pickle 以及 unpickle的過程。


請看關於此文件… 裡面有關如何給我們建議的說明。