7. 輸入輸出?
有幾種方法可以顯示程序的輸出;數(shù)據(jù)可以以人類可讀的形式打印出來,或者寫入文件以供將來使用。本章將討論一些可能性。
7.1. 更漂亮的輸出格式?
到目前為止,我們遇到了兩種寫入值的方法:表達(dá)式語句 和 print() 函數(shù)。(第三種是使用文件對(duì)象的 write() 方法;標(biāo)準(zhǔn)輸出文件可以作為 sys.stdout 引用。更多相關(guān)信息可參考標(biāo)準(zhǔn)庫指南。)
通常,你需要更多地控制輸出的格式,而不僅僅是打印空格分隔的值。有幾種格式化輸出的方法。
要使用 格式化字符串字面值 ,請(qǐng)?jiān)谧址拈_始引號(hào)或三引號(hào)之前加上一個(gè)
f或F。在此字符串中,你可以在{和}字符之間寫可以引用的變量或字面值的 Python 表達(dá)式。>>> year = 2016 >>> event = 'Referendum' >>> f'Results of the {year} {event}' 'Results of the 2016 Referendum'
字符串的
str.format()方法需要更多的手動(dòng)操作。你仍將使用{和}來標(biāo)記變量將被替換的位置,并且可以提供詳細(xì)的格式化指令,但你還需要提供要格式化的信息。>>> yes_votes = 42_572_654 >>> no_votes = 43_132_495 >>> percentage = yes_votes / (yes_votes + no_votes) >>> '{:-9} YES votes {:2.2%}'.format(yes_votes, percentage) ' 42572654 YES votes 49.67%'
最后,你可以使用字符串切片和連接操作自己完成所有的字符串處理,以創(chuàng)建你可以想象的任何布局。字符串類型有一些方法可以執(zhí)行將字符串填充到給定列寬的有用操作。
當(dāng)你不需要花哨的輸出而只是想快速顯示某些變量以進(jìn)行調(diào)試時(shí),可以使用 repr() or str()?函數(shù)將任何值轉(zhuǎn)化為字符串。
str() 函數(shù)是用于返回人類可讀的值的表示,而 repr() 是用于生成解釋器可讀的表示(如果沒有等效的語法,則會(huì)強(qiáng)制執(zhí)行 SyntaxError)對(duì)于沒有人類可讀性的表示的對(duì)象, str() 將返回和 repr() 一樣的值。很多值使用任一函數(shù)都具有相同的表示,比如數(shù)字或類似列表和字典的結(jié)構(gòu)。特殊的是字符串有兩個(gè)不同的表示。
幾個(gè)例子:
>>> 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'))"
string 模塊包含一個(gè) Template 類,它提供了另一種將值替換為字符串的方法,使用類似 $x 的占位符并用字典中的值替換它們,但對(duì)格式的控制要少的多。
7.1.1. 格式化字符串文字?
格式化字符串字面值 (常簡(jiǎn)稱為 f-字符串)能讓你在字符串前加上 f 和 F 并將表達(dá)式寫成 {expression} 來在字符串中包含 Python 表達(dá)式的值。
可選的格式說明符可以跟在表達(dá)式后面。這樣可以更好地控制值的格式化方式。以下示例將pi舍入到小數(shù)點(diǎn)后三位:
>>> import math
>>> print(f'The value of pi is approximately {math.pi:.3f}.')
The value of pi is approximately 3.142.
在 ':' 后傳遞一個(gè)整數(shù)可以讓該字段成為最小字符寬度。這在使列對(duì)齊時(shí)很有用。:
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
... print(f'{name:10} ==> {phone:10d}')
...
Sjoerd ==> 4127
Jack ==> 4098
Dcab ==> 7678
其他的修飾符可用于在格式化之前轉(zhuǎn)化值。 '!a' 應(yīng)用 ascii() ,'!s' 應(yīng)用 str(),還有 '!r' 應(yīng)用 repr():
>>> animals = 'eels'
>>> print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.
>>> print(f'My hovercraft is full of {animals!r}.')
My hovercraft is full of 'eels'.
有關(guān)這些格式規(guī)范的參考,請(qǐng)參閱參考指南 格式規(guī)格迷你語言。
7.1.2. 字符串的 format() 方法?
str.format() 方法的基本用法如下所示:
>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"
花括號(hào)和其中的字符(稱為格式字段)將替換為傳遞給 str.format() 方法的對(duì)象。花括號(hào)中的數(shù)字可用來表示傳遞給 str.format() 方法的對(duì)象的位置。
>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam
如果在 str.format() 方法中使用關(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.
如果你有一個(gè)非常長(zhǎng)的格式字符串,你不想把它拆開,那么你最好是按名稱而不是按位置引用變量來進(jìn)行格式化。 這可以通過簡(jiǎn)單地傳遞字典并使用方括號(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
這也可以通過使用 '**' 符號(hào)將 table 作為關(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() 結(jié)合使用時(shí)非常有用,它會(huì)返回包含所有局部變量的字典。
例如,下面幾行代碼生成一組整齊的列,其中包含給定的整數(shù)和它的平方以及立方:
>>> 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
關(guān)于使用 str.format() 進(jìn)行字符串格式化的完整概述,請(qǐng)參閱 格式字符串語法 。
7.1.3. 手動(dòng)格式化字符串?
這是同一個(gè)平方和立方的表,手動(dòng)格式化的:
>>> 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
(注意每列之間的一個(gè)空格是通過使用 print() 的方式添加的:它總是在其參數(shù)間添加空格。)
字符串對(duì)象的 str.rjust() 方法通過在左側(cè)填充空格來對(duì)給定寬度的字段中的字符串進(jìn)行右對(duì)齊。類似的方法還有 str.ljust() 和 str.center() 。這些方法不會(huì)寫入任何東西,它們只是返回一個(gè)新的字符串,如果輸入的字符串太長(zhǎng),它們不會(huì)截?cái)嘧址窃瓨臃祷兀贿@雖然會(huì)弄亂你的列布局,但這通常比另一種方法好,后者會(huì)在顯示值時(shí)可能不準(zhǔn)確(如果你真的想截?cái)啵憧梢蕴砑右粋€(gè)切片操作,例如 x.ljust(n)[:n] 。)
還有另外一個(gè)方法,str.zfill() ,它會(huì)在數(shù)字字符串的左邊填充零。它能識(shí)別正負(fù)號(hào):
>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'
7.1.4. 舊的字符串格式化方法?
% 運(yùn)算符(求余)也可用于字符串格式化。 給定 'string' % values,則 string 中的 % 實(shí)例會(huì)以零個(gè)或多個(gè) values 元素替換。 此操作通常被稱為字符串插值。 例如:
>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.
可在 printf 風(fēng)格的字符串格式化 部分找到更多信息。
7.2. 讀寫文件?
open() 返回一個(gè) file object,最常用的有兩個(gè)參數(shù): open(filename, mode)。
>>> f = open('workfile', 'w')
第一個(gè)參數(shù)是包含文件名的字符串。第二個(gè)參數(shù)是另一個(gè)字符串,其中包含一些描述文件使用方式的字符。mode 可以是 'r' ,表示文件只能讀取,'w' 表示只能寫入(已存在的同名文件會(huì)被刪除),還有 'a' 表示打開文件以追加內(nèi)容;任何寫入的數(shù)據(jù)會(huì)自動(dòng)添加到文件的末尾。'r+' 表示打開文件進(jìn)行讀寫。mode 參數(shù)是可選的;省略時(shí)默認(rèn)為 'r'。
通常文件是以 text mode 打開的,這意味著從文件中讀取或?qū)懭胱址畷r(shí),都會(huì)以指定的編碼方式進(jìn)行編碼。如果未指定編碼格式,默認(rèn)值與平臺(tái)相關(guān) (參見 open())。在mode 中追加的 'b' 則以 binary mode 打開文件:現(xiàn)在數(shù)據(jù)是以字節(jié)對(duì)象的形式進(jìn)行讀寫的。這個(gè)模式應(yīng)該用于所有不包含文本的文件。
在文本模式下讀取時(shí),默認(rèn)會(huì)把平臺(tái)特定的行結(jié)束符 (Unix 上的 \n, Windows 上的 \r\n) 轉(zhuǎn)換為 \n。在文本模式下寫入時(shí),默認(rèn)會(huì)把出現(xiàn)的 \n 轉(zhuǎn)換回平臺(tái)特定的結(jié)束符。這樣在幕后修改文件數(shù)據(jù)對(duì)文本文件來說沒有問題,但是會(huì)破壞二進(jìn)制數(shù)據(jù)例如 JPEG 或 EXE 文件中的數(shù)據(jù)。請(qǐng)一定要注意在讀寫此類文件時(shí)應(yīng)使用二進(jìn)制模式。
在處理文件對(duì)象時(shí),最好使用 with 關(guān)鍵字。 優(yōu)點(diǎn)是當(dāng)子句體結(jié)束后文件會(huì)正確關(guān)閉,即使在某個(gè)時(shí)刻引發(fā)了異常。 而且使用 with 相比等效的 try-finally 代碼塊要簡(jiǎn)短得多:
>>> with open('workfile') as f:
... read_data = f.read()
>>> f.closed
True
如果你沒有使用 with 關(guān)鍵字,那么你應(yīng)該調(diào)用 f.close() 來關(guān)閉文件并立即釋放它使用的所有系統(tǒng)資源。如果你沒有顯式地關(guān)閉文件,Python的垃圾回收器最終將銷毀該對(duì)象并為你關(guān)閉打開的文件,但這個(gè)文件可能會(huì)保持打開狀態(tài)一段時(shí)間。另外一個(gè)風(fēng)險(xiǎn)是不同的Python實(shí)現(xiàn)會(huì)在不同的時(shí)間進(jìn)行清理。
通過 with 語句或者調(diào)用 f.close() 關(guān)閉文件對(duì)象后,嘗試使用該文件對(duì)象將自動(dòng)失敗。:
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.
7.2.1. 文件對(duì)象的方法?
本節(jié)中剩下的例子將假定你已創(chuàng)建名為 f 的文件對(duì)象。
要讀取文件內(nèi)容,請(qǐng)調(diào)用 f.read(size),它會(huì)讀取一些數(shù)據(jù)并將其作為字符串(在文本模式下)或字節(jié)串對(duì)象(在二進(jìn)制模式下)返回。 size 是一個(gè)可選的數(shù)值參數(shù)。 當(dāng) size 被省略或者為負(fù)數(shù)時(shí),將讀取并返回整個(gè)文件的內(nèi)容;如果文件的大小是你的機(jī)器內(nèi)存的兩倍就會(huì)出現(xiàn)問題。 當(dāng)取其他值時(shí),將讀取并返回至多 size 個(gè)字符(在文本模式下)或 size 個(gè)字節(jié)(在二進(jìn)制模式下)。 如果已到達(dá)文件末尾,f.read() 將返回一個(gè)空字符串 ('')。
>>> f.read()
'This is the entire file.\n'
>>> f.read()
''
f.readline() 從文件中讀取一行;換行符(\n)留在字符串的末尾,如果文件不以換行符結(jié)尾,則在文件的最后一行省略。這使得返回值明確無誤;如果 f.readline() 返回一個(gè)空的字符串,則表示已經(jīng)到達(dá)了文件末尾,而空行使用 '\n' 表示,該字符串只包含一個(gè)換行符。:
>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''
要從文件中讀取行,你可以循環(huán)遍歷文件對(duì)象。這是內(nèi)存高效,快速的,并簡(jiǎn)化代碼:
>>> for line in f:
... print(line, end='')
...
This is the first line of the file.
Second line of the file
如果你想以列表的形式讀取文件中的所有行,你也可以使用 list(f) 或 f.readlines()。
f.write(string) 會(huì)把 string 的內(nèi)容寫入到文件中,并返回寫入的字符數(shù)。:
>>> f.write('This is a test\n')
15
在寫入其他類型的對(duì)象之前,需要先把它們轉(zhuǎn)化為字符串(在文本模式下)或者字節(jié)對(duì)象(在二進(jìn)制模式下):
>>> value = ('the answer', 42)
>>> s = str(value) # convert the tuple to string
>>> f.write(s)
18
f.tell() 返回一個(gè)整數(shù),給出文件對(duì)象在文件中的當(dāng)前位置,表示為二進(jìn)制模式下時(shí)從文件開始的字節(jié)數(shù),以及文本模式下的意義不明的數(shù)字。
要改變文件對(duì)象的位置,請(qǐng)使用 f.seek(offset, whence)。 通過向一個(gè)參考點(diǎn)添加 offset 來計(jì)算位置;參考點(diǎn)由 whence 參數(shù)指定。 whence 的 0 值表示從文件開頭起算,1 表示使用當(dāng)前文件位置,2 表示使用文件末尾作為參考點(diǎn)。 whence 如果省略則默認(rèn)值為 0,即使用文件開頭作為參考點(diǎn)。
>>> 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 的打開的文件)中,只允許相對(duì)于文件開頭搜索(使用 seek(0, 2) 搜索到文件末尾是個(gè)例外)并且唯一有效的 offset 值是那些能從 f.tell() 中返回的或者是零。其他 offset 值都會(huì)產(chǎn)生未定義的行為。
文件對(duì)象有一些額外的方法,例如 isatty() 和 truncate() ,它們使用頻率較低;有關(guān)文件對(duì)象的完整指南請(qǐng)參閱庫參考。
7.2.2. 使用 json 保存結(jié)構(gòu)化數(shù)據(jù)?
字符串可以很輕松地寫入文件并從文件中讀取出來。數(shù)字可能會(huì)費(fèi)點(diǎn)勁,因?yàn)?read() 方法只能返回字符串,這些字符串必須傳遞給類似 int() 的函數(shù),它會(huì)接受類似 '123' 這樣的字符串并返回其數(shù)字值 123。當(dāng)你想保存諸如嵌套列表和字典這樣更復(fù)雜的數(shù)據(jù)類型時(shí),手動(dòng)解析和序列化會(huì)變得復(fù)雜。
Python 允許你使用稱為 JSON (JavaScript Object Notation) 的流行數(shù)據(jù)交換格式,而不是讓用戶不斷的編寫和調(diào)試代碼以將復(fù)雜的數(shù)據(jù)類型保存到文件中。名為 json 的標(biāo)準(zhǔn)模塊可以采用 Python 數(shù)據(jù)層次結(jié)構(gòu),并將它們轉(zhuǎn)化為字符串表示形式;這個(gè)過程稱為 serializing 。從字符串表示中重建數(shù)據(jù)稱為 deserializing 。在序列化和反序列化之間,表示對(duì)象的字符串可能已存儲(chǔ)在文件或數(shù)據(jù)中,或通過網(wǎng)絡(luò)連接發(fā)送到某個(gè)遠(yuǎn)程機(jī)器。
注解
JSON格式通常被現(xiàn)代應(yīng)用程序用于允許數(shù)據(jù)交換。許多程序員已經(jīng)熟悉它,這使其成為互操作性的良好選擇。
如果你有一個(gè)對(duì)象 x ,你可以用一行簡(jiǎn)單的代碼來查看它的 JSON 字符串表示:
>>> import json
>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'
dumps() 函數(shù)的另一個(gè)變體叫做 dump() ,它只是將對(duì)象序列化為 text file 。因此,如果 f 是一個(gè) text file 對(duì)象,我們可以這樣做:
json.dump(x, f)
要再次解碼對(duì)象,如果 f 是一個(gè)打開的以供閱讀的 text file 對(duì)象:
x = json.load(f)
這種簡(jiǎn)單的序列化技術(shù)可以處理列表和字典,但是在JSON中序列化任意類的實(shí)例需要額外的努力。 json 模塊的參考包含對(duì)此的解釋。
