鍍金池/ 教程/ Python/ 輸入和輸出
浮點(diǎn)運(yùn)算
非正式的 Python 簡(jiǎn)介
深入流程控制
標(biāo)準(zhǔn)庫(kù)概覽
交互式輸入行編輯歷史回溯
輸入和輸出
使用 Python 解釋器
標(biāo)準(zhǔn)庫(kù)概覽Ⅱ
錯(cuò)誤和異常
模塊
數(shù)據(jù)結(jié)構(gòu)
附錄
接下來?
激起你的興趣

輸入和輸出

一個(gè)程序可以有幾種輸出方式:以人類可讀的方式打印數(shù)據(jù),或者寫入一個(gè)文件供以后使用。本章將討論幾種可能性。

格式化輸出

我們有兩種大相徑庭的輸出值方法:表達(dá)式語句和print語句。(第三種方法是使用文件對(duì)象的write()方法,標(biāo)準(zhǔn)文件輸出可以參考sys.stdout。)

通常,你想要對(duì)輸出做更多的格式控制,而不是簡(jiǎn)單的打印使用空格分隔的值。 有兩種方法可以格式化你的輸出: 第一種方法是由你自己處理整個(gè)字符串,通過使用字符串切割和連接操作可以創(chuàng)建任何你想要的輸出形式。string 類型包含一些將字符串填充到指定列寬度的有用操作,隨后就會(huì)討論這些。 第二種方法是使用?str.format()?方法。

標(biāo)準(zhǔn)模塊?string?包括了一些操作,將字符串填充入給定列時(shí),這些操作很有用。隨后我們會(huì)討論這部分內(nèi)容。第二種方法是使用?Template?方法。

當(dāng)然,還有一個(gè)問題,如何將值轉(zhuǎn)化為字符串?很幸運(yùn),Python 有辦法將任意值轉(zhuǎn)為字符串:將它傳入?repr()?或?str()?函數(shù)。

函數(shù)?str()?用于將值轉(zhuǎn)化為適于人閱讀的形式,而?repr()?轉(zhuǎn)化為供解釋器讀取的形式(如果沒有等價(jià)的語法,則會(huì)發(fā)生?SyntaxError?異常) 某對(duì)象沒有適于人閱讀的解釋形式的話,str()?會(huì)返回與?repr()?等同的值。很多類型,諸如數(shù)值或鏈表、字典這樣的結(jié)構(gòu),針對(duì)各函數(shù)都有著統(tǒng)一的解讀方式。字符串和浮點(diǎn)數(shù),有著獨(dú)特的解讀方式。

下面有些例子:

>>> s = 'Hello, world.'  
>>> str(s)  
'Hello, world.' 
>>> repr(s)  
"'Hello, world.'"  
>>> str(1/7)  
'0.14285714285714285'  
>>> x = 10 * 3.25  
>>> y = 200 * 200  
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'  
>>> print(s)  
The value of x is 32.5, and y is 40000...  
>>> # The repr() of a string adds string quotes and backslashes:  
... hello = 'hello, world\n'  
>>> hellos = repr(hello)  
>>> print(hellos) 
'hello, world\n'  
>>> # The argument to repr() may be any Python object:  
... repr((x, y, ('spam', 'eggs')))  
"(32.5, 40000, ('spam', 'eggs'))"  

有兩種方式可以寫平方和立方表:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Note use of 'end' on previous line
...     print(repr(x*x*x).rjust(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('{0:2d} {1:3d} {2:4d}'.format(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

(注意第一個(gè)例子,?print?在每列之間加了一個(gè)空格,它總是在參數(shù)間加入空格。)

以上是一個(gè)?str.rjust()?方法的演示,它把字符串輸出到一列,并通過向左側(cè)填充空格來使其右對(duì)齊。類似的方法還有?str.ljust()?和str.center()?。這些函數(shù)只是輸出新的字符串,并不改變什么。如果輸出的字符串太長(zhǎng),它們也不會(huì)截?cái)嗨?,而是原樣輸出,這會(huì)使你的輸出格式變得混亂,不過總強(qiáng)過另一種選擇(截?cái)嘧址?,因?yàn)槟菢訒?huì)產(chǎn)生錯(cuò)誤的輸出值。(如果你確實(shí)需要截?cái)嗨梢允褂们懈畈僮?,例如?x.ljust(n)[:n]?。)

還有另一個(gè)方法,str.zfill()?它用于向數(shù)值的字符串表達(dá)左側(cè)填充 0。該函數(shù)可以正確理解正負(fù)號(hào):

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

方法?str.format()?的基本用法如下:

>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))  
We are the knights who say "Ni!"  

大括號(hào)和其中的字符會(huì)被替換成傳入?str.format()?的參數(shù)。大括號(hào)中的數(shù)值指明使用傳入?str.format()?方法的對(duì)象中的哪一個(gè)。

>>> print('{0} and {1}'.format('spam', 'eggs')) 
spam and eggs   
>>> print('{1} and {0}'.format('spam', 'eggs'))  
eggs and spam  

如果在format() 調(diào)用時(shí)使用關(guān)鍵字參數(shù),可以通過參數(shù)名來引用值

>>> print('This {food} is {adjective}.'.format(  
...      food='spam', adjective='absolutely horrible'))  
This spam is absolutely horrible.  

定位和關(guān)鍵字參數(shù)可以組合使用:

>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
                                                       other='Georg'))
The story of Bill, Manfred, and Georg. 

'!a'?(應(yīng)用?ascii()),?'!s'?(應(yīng)用?str()?) 和?'!r'?(應(yīng)用?repr()?) 可以在格式化之前轉(zhuǎn)換值:

>>> import math
>>> print('The value of PI is approximately {}.'.format(math.pi))
The value of PI is approximately 3.14159265359.
>>> print('The value of PI is approximately {!r}.'.format(math.pi))
The value of PI is approximately 3.141592653589793. 

一個(gè)可選的':' 和格式說明符可以用字段來名。這允許對(duì)值進(jìn)行更深的格式化控制,下面的實(shí)例Pi小數(shù)點(diǎn)后三位

>>> import math
>>> print('The value of PI is approximately {0:3f}.'.format(math.pi))
The value of PI is approximately 3.142.

字段名后允許可選的?':'?和格式指令。這允許對(duì)值的格式化加以更深入的控制。下例將 Pi 轉(zhuǎn)為三位精度。

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

如果你有個(gè)實(shí)在是很長(zhǎng)的格式化字符串,不想分割它。如果你可以用命名來引用被格式化的變量而不是位置就好了。有個(gè)簡(jiǎn)單的方法,可以傳入一個(gè)字典,用中括號(hào)訪問它的鍵:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}  
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '  
...     'Dcab: {0[Dcab]:d}'.format(table))  
Jack: 4098; Sjoerd: 4127; Dcab: 8637678  

也可以用‘**’ 標(biāo)志將這個(gè)字典以關(guān)鍵字參數(shù)的方式傳入。

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

這種方式與新的內(nèi)置函數(shù)?vars()?組合使用非常有效。該函數(shù)返回包含所有局部變量的字典。

要進(jìn)一步了解字符串格式化方法?str.format()?,參見?Forma String Syntax?。

舊式的字符串格式化

操作符?%?也可以用于字符串格式化。它以類似?sprintf()-style 的方式解析左參數(shù),將右參數(shù)應(yīng)用于此,得到格式化操作生成的字符串,例如:

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

進(jìn)一步的信息可以參見 printf-style String Formatting?一節(jié)。

文件讀寫

函數(shù)?open()?返回文件對(duì)象,通常的用法需要兩個(gè)參數(shù):open(filename,?mode).

>>> f = open('workfile', 'w')  

第一個(gè)參數(shù)是一個(gè)標(biāo)識(shí)文件名的字符串。第二個(gè)參數(shù)是由有限的字母組成的字符串,描述了文件將會(huì)被如何使用??蛇x的模式有:?'r'?,此選項(xiàng)使文件只讀;?'w'?,此選項(xiàng)使文件只寫(對(duì)于同名文件,該操作使原有文件被覆蓋);?'a'?,此選項(xiàng)以追加方式打開文件;?'r+',此選項(xiàng)以讀寫方式打開文件; 模式參數(shù)是可選的。如果沒有指定,默認(rèn)為?'r'?模式。

通常,文件是在文本模式下打開,這意味著,你讀和寫字符串的時(shí)候,文件編碼在一個(gè)特定的編碼(默認(rèn)是 utf - 8)?!癰”添加到模式打開文件以二進(jìn)制模式打開:現(xiàn)在表單中的數(shù)據(jù)讀取和寫入的字節(jié)對(duì)象。這種模式應(yīng)該用于所有文件,不包含文本。

在文本模式下,閱讀時(shí)默認(rèn)轉(zhuǎn)換特定于平臺(tái)的線末梢(\ nUnix上,\ r \ n 在 Windows上)\ n。當(dāng)你寫在文本模式下的時(shí)候,,默認(rèn)的是出現(xiàn) \ n轉(zhuǎn)換回特定于平臺(tái)的線的結(jié)局。這個(gè)幕后修改文件數(shù)據(jù)文本文件是好的,,但是腐敗的二進(jìn)制數(shù)據(jù)就像 JPEG 或 EXE 文件。當(dāng)你在使用二進(jìn)制模式閱讀和寫作時(shí)要非常仔細(xì)才可以。

文件對(duì)象方法

本節(jié)中的示例都默認(rèn)文件對(duì)象?f?已經(jīng)創(chuàng)建。

要讀取文件內(nèi)容,需要調(diào)用?f.read(size)?,該方法讀取若干數(shù)量的數(shù)據(jù)并以字符串形式返回其容,?size?是可選的數(shù)值,指定字符串長(zhǎng)度。如果沒有指定?size?或者指定為負(fù)數(shù),就會(huì)讀取并返回整個(gè)文件。當(dāng)文件大小為當(dāng)前機(jī)器內(nèi)存兩倍時(shí),就會(huì)產(chǎn)生問題。反之,會(huì)盡可能按比較大的size?讀取和返回?cái)?shù)據(jù)。如果到了文件末尾,f.read()?會(huì)返回一個(gè)空字符串(”“)。

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

f.readline()?從文件中讀取單獨(dú)一行,字符串結(jié)尾會(huì)自動(dòng)加上一個(gè)換行符(?\n?),只有當(dāng)文件最后一行沒有以換行符結(jié)尾時(shí),這一操作才會(huì)被忽略。這樣返回值就不會(huì)有混淆,如果如果?f.readline()?返回一個(gè)空字符串,那就表示到達(dá)了文件末尾,如果是一個(gè)空行,就會(huì)描述為'\n'?,一個(gè)只包含換行符的字符串。

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

對(duì)于從文件讀取行,可以遍歷文件對(duì)象。這是內(nèi)存高效、快速和導(dǎo)致簡(jiǎn)單的代碼:

>>> for line in f:
...      print(line, end='')  
...
This is the first line of the file.
Second line of the file  

如果你想閱讀列表中的所有行的文件,你還可以使用列表(f)或f.readlines()。

f.write(string) 將字符串的內(nèi)容寫入文件,返回字符數(shù)。

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

想要寫入其他非字符串內(nèi)容,首先要將它轉(zhuǎn)換為字符串:

>>> value = ('the answer', 42)  
>>> s = str(value)  
>>> f.write(s)
18   

f.tell()?返回一個(gè)整數(shù),代表文件對(duì)象在文件中的指針位置,在文本模式下,該數(shù)值計(jì)量了自文件開頭到指針處的比特?cái)?shù)。

需要改變文件對(duì)象指針話話,使用 f.seek(offset,from_what)?。指針在該操作中從指定的引用位置移動(dòng)?offset 比特,引用位置由?from_what?參數(shù)指定。?from_what?值為 0 表示自文件起始處開始,1 表示自當(dāng)前文件指針位置開始,2 表示自文件末尾開始。?from_what?可以忽略,其默認(rèn)值為零,此時(shí)從文件頭開始。

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

在文本文件中(那些沒有使用?b?模式選項(xiàng)打開的文件),只允許從文件頭開始計(jì)算相對(duì)位置(使用?seek(0,?2)?從文件尾計(jì)算時(shí)就會(huì)引發(fā)異常)。

當(dāng)你使用完一個(gè)文件時(shí),調(diào)用?f.close()?方法就可以關(guān)閉它并釋放其占用的所有系統(tǒng)資源。 在調(diào)用?f.close()?方法后,試圖再次使用文件對(duì)象將會(huì)自動(dòng)失敗。

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

用關(guān)鍵字?with?處理文件對(duì)象是個(gè)好習(xí)慣。它的先進(jìn)之處在于文件用完后會(huì)自動(dòng)關(guān)閉,就算發(fā)生異常也沒關(guān)系。它是?try-finally?塊的簡(jiǎn)寫:

>>> with open('workfile', 'r') as f:  
...    read_data = f.read()  
>>> f.closed  
True  

文件對(duì)象還有一些不太常用的附加方法,比如?isatty()?和?truncate()?在庫(kù)參考手冊(cè)中有文件對(duì)象的完整指南。

結(jié)構(gòu)化數(shù)據(jù)保存與 JSON

我們可以很容易的讀寫文件中的字符串。數(shù)值就要多費(fèi)點(diǎn)兒周折,因?yàn)?read()?方法只會(huì)返回字符串,應(yīng)該將其傳入?int()?這樣的方法中,就可以將?'123'?這樣的字符轉(zhuǎn)為對(duì)應(yīng)的數(shù)值 123。不過,當(dāng)你需要保存更為復(fù)雜的數(shù)據(jù)類型,例如列表、字典,類的實(shí)例,事情就會(huì)變得更復(fù)雜了。

好在用戶不必要非得自己編寫和調(diào)試保存復(fù)雜數(shù)據(jù)類型的代碼。 Python 提供了一個(gè)名為?pickle?的標(biāo)準(zhǔn)模塊。這是一個(gè)令人贊嘆的模塊,幾乎可以把任何 Python 對(duì)象 (甚至是一些 Python 代碼段?。┍磉_(dá)為為字符串,這一過程稱之為封裝 (?pickling?)。從字符串表達(dá)出重新構(gòu)造對(duì)象稱之為拆封(?unpickling?)。封裝狀態(tài)中的對(duì)象可以存儲(chǔ)在文件或?qū)ο笾?,也可以通過網(wǎng)絡(luò)在遠(yuǎn)程的機(jī)器之間傳輸。

筆記:JSON 格式是被現(xiàn)代的應(yīng)用程序使用允許數(shù)據(jù)交換。許多程序員已經(jīng)熟悉它,對(duì)于相互操作性,這是一個(gè)很好的選擇。

如果你有一個(gè)對(duì)象?x?,一個(gè)以寫模式打開的文件對(duì)象?f?,封裝對(duì)象的最簡(jiǎn)單的方法只需要一行代碼:

>>> json.dumps([1, 'simple', 'list'])  
'[1, "simple", "list"]'  

另一個(gè)變體轉(zhuǎn)儲(chǔ)函數(shù),稱為 dump(),可以將對(duì)象序列化到一個(gè)文本文件。所以如果f是一個(gè)寫模式打開的文本文件對(duì)象,我們可以這樣做:

json.dump(x, f)

第二次解碼,如果f是一個(gè)讀模式打開的文本文件對(duì)象:

x = json.load(f)

這個(gè)簡(jiǎn)單的序列化技術(shù)可以處理列表和字典,但在 JSON 序列化任意類實(shí)例需要一點(diǎn)額外的努力。json 的參考模塊包含一個(gè)解釋。

參見: pickle,pickle 模塊與 JSON 相反,pickle 是一個(gè)協(xié)議,允許序列化任意復(fù)雜的 Python 對(duì)象。因此,它是特定于 Python 和不能用于與其他語言編寫的應(yīng)用程序通信。也是不安全的默認(rèn):如果數(shù)據(jù)被老練的攻擊者攻擊,來自一個(gè)不可信的源的 pickle 數(shù)據(jù)可以執(zhí)行任意代碼。

上一篇:深入流程控制下一篇: