4. 輸入與輸出


1.更復(fù)雜的輸出格式

我們已學(xué)習(xí)了兩種寫(xiě)入值的方法:表達(dá)式語(yǔ)句print() 函數(shù)。
第三種方法是使用文件對(duì)象的 write() 方法;標(biāo)準(zhǔn)輸出文件稱為 sys.stdout。詳見(jiàn)標(biāo)準(zhǔn)庫(kù)參考。
對(duì)輸出格式的控制不只是打印空格分隔的值,還需要更多方式。
格式化輸出包括以下幾種方法。

  • 使用格式化字符串字面值 ,要在字符串開(kāi)頭的引號(hào)/三引號(hào)前添加 fF 。在這種字符串中,可以在 {} 字符之間輸入引用的變量,或字面值的 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    # yes_votes = 42572654
>>> no_votes = 43_132_495     # no_votes = 43132495
>>> percentage = yes_votes / (yes_votes + no_votes)
>>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
' 42572654 YES votes  49.67%'
# {:-9} 不足九位,左側(cè)填充空格  {:2.2%} 字符串長(zhǎng)度不足2時(shí)(包含小數(shù)點(diǎn)和整數(shù)),左側(cè)填空格, 保留兩位小數(shù)
  • 最后,還可以用字符串切片和合并操作完成字符串處理操作,創(chuàng)建任何排版布局。字符串類型還支持將字符串按給定列寬進(jìn)行填充,這些方法也很有用。
    如果不需要花哨的輸出,只想快速顯示變量進(jìn)行調(diào)試,可以用repr()str()函數(shù)把值轉(zhuǎn)化為字符串。
    str()函數(shù)返回供人閱讀的值,repr()則生成適于解釋器讀取的值(如果沒(méi)有等效的語(yǔ)法,則強(qiáng)制執(zhí)行 SyntaxError。對(duì)于沒(méi)有支持供人閱讀展示結(jié)果的對(duì)象,str()返回與 repr()相同的值。一般情況下,數(shù)字、列表或字典等結(jié)構(gòu)的值,使用這兩個(gè)函數(shù)輸出的表現(xiàn)形式是一樣的。字符串有兩種不同的表現(xiàn)形式。(使用repr會(huì)在字符串外側(cè)額外增加引號(hào), 如果之前已存在相同引號(hào),則會(huì)轉(zhuǎn)義內(nèi)部引號(hào))
    例:
>>> 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)  # hellos = "'hello, world\\n'"
>>> print(hellos)
'hello, world\n'
>>> print(hello)
hello, world

>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

2. 格式化字符串字面值

格式化字符串字面值(簡(jiǎn)稱為 f-字符串)在字符串前加前綴 fF,通過(guò) {expression} 表達(dá)式,把 Python 表達(dá)式的值添加到字符串內(nèi)
格式說(shuō)明符是可選的,寫(xiě)在表達(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.

在 ':' 后傳遞整數(shù),為該字段設(shè)置最小字符寬度,常用于列對(duì)齊:

>>> 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'.

3. 字符串 format() 方法

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

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

花括號(hào)及之內(nèi)的字符(稱為格式字段)被替換為傳遞給 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ù)名引用值。

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

位置參數(shù)和關(guān)鍵字參數(shù)可以任意組合:

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

如果不想分拆較長(zhǎng)的格式字符串,最好按名稱引用變量進(jìn)行格式化,不要按位置。這項(xiàng)操作可以通過(guò)傳遞字典,并用方括號(hào) '[]' 訪問(wèn)鍵來(lái)完成。

>>> 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 當(dāng)作傳遞的關(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í),這種方式非常實(shí)用,可以返回包含所有局部變量的字典。
例如,下面的代碼生成一組整齊的列,包含給定整數(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

4.手動(dòng)格式化字符串

下面是使用手動(dòng)格式化方式實(shí)現(xiàn)的同一個(gè)平方和立方的表:

>>> 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

(注意,每列之間的空格是通過(guò)使用 print() 添加的:它總在其參數(shù)間添加空格。)
字符串對(duì)象的 str.rjust() 方法通過(guò)在左側(cè)填充空格,對(duì)給定寬度字段中的字符串進(jìn)行右對(duì)齊。同類方法還有 str.ljust()str.center() 。這些方法不寫(xiě)入任何內(nèi)容,只返回一個(gè)新字符串,如果輸入的字符串太長(zhǎng),它們不會(huì)截?cái)嘧址?,而是原樣返回;雖然這種方式會(huì)弄亂列布局,但也比另一種方法好,后者在顯示值時(shí)可能不準(zhǔn)確(如果真的想截?cái)嘧址?,可以使?x.ljust(n)[:n] 這樣的切片操作 。)
另一種方法是 str.zfill() ,該方法在數(shù)字字符串左邊填充零,且能識(shí)別正負(fù)號(hào):

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

5.舊式字符串格式化方法

% 運(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.

6.讀寫(xiě)文件

open() 返回 file object,最常用的參數(shù)有兩個(gè): open(filename, mode)。

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

第一個(gè)實(shí)參是文件名字符串。第二個(gè)實(shí)參是包含描述文件使用方式字符的字符串。mode 的值包括 'r' ,表示文件只能讀??;'w' 表示只能寫(xiě)入(現(xiàn)有同名文件會(huì)被覆蓋);'a' 表示打開(kāi)文件并追加內(nèi)容,任何寫(xiě)入的數(shù)據(jù)會(huì)自動(dòng)添加到文件末尾。'r+' 表示打開(kāi)文件進(jìn)行讀寫(xiě)。mode 實(shí)參是可選的,省略時(shí)的默認(rèn)值為 'r'。

通常,文件以 text mode 打開(kāi),即,從文件中讀取或?qū)懭胱址畷r(shí),都以指定編碼方式進(jìn)行編碼。如未指定編碼格式,默認(rèn)值與平臺(tái)相關(guān) (參見(jiàn) open())。在 mode 中追加的 'b' 則以 binary mode 打開(kāi)文件:此時(shí),數(shù)據(jù)以字節(jié)對(duì)象的形式進(jìn)行讀寫(xiě)。該模式用于所有不包含文本的文件。

在文本模式下讀取文件時(shí),默認(rèn)把平臺(tái)特定的行結(jié)束符(Unix 上為 \n, Windows 上為 \r\n)轉(zhuǎn)換為 \n。在文本模式下寫(xiě)入數(shù)據(jù)時(shí),默認(rèn)把 \n 轉(zhuǎn)換回平臺(tái)特定結(jié)束符。這種操作方式在后臺(tái)修改文件數(shù)據(jù)對(duì)文本文件來(lái)說(shuō)沒(méi)有問(wèn)題,但會(huì)破壞 JPEGEXE 等二進(jìn)制文件中的數(shù)據(jù)。注意,在讀寫(xiě)此類文件時(shí),一定要使用二進(jìn)制模式。

在處理文件對(duì)象時(shí),最好使用 with 關(guān)鍵字。優(yōu)點(diǎn)是,子句體結(jié)束后,文件會(huì)正確關(guān)閉,即便觸發(fā)異常也可以。而且,使用 with 相比等效的 try-finally 代碼塊要簡(jiǎn)短得多:

>>> with open('workfile') as f:
...     read_data = f.read()

>>> # We can check that the file has been automatically closed.
>>> f.closed
True

如果沒(méi)有使用 with 關(guān)鍵字,則應(yīng)調(diào)用 f.close() 關(guān)閉文件,即可釋放文件占用的系統(tǒng)資源。
警告: 調(diào)用 f.write() 時(shí),未使用 with 關(guān)鍵字,或未調(diào)用 f.close(),即使程序正常退出,也**可能** 導(dǎo)致 f.write() 的參數(shù)沒(méi)有完全寫(xiě)入磁盤。
通過(guò) with 語(yǔ)句,或調(diào)用 f.close() 關(guān)閉文件對(duì)象后,再次使用該文件對(duì)象將會(huì)失敗。

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

7. 文件對(duì)象的方法

本節(jié)下文中的例子假定已創(chuàng)建 f 文件對(duì)象。
f.read([size])可用于讀取文件內(nèi)容,它會(huì)讀取一些數(shù)據(jù),并返回字符串(文本模式),或字節(jié)串對(duì)象(在二進(jìn)制模式下)。 size 是可選的數(shù)值參數(shù)。省略 size 或 size 為負(fù)數(shù)時(shí),讀取并返回整個(gè)文件的內(nèi)容;文件大小是內(nèi)存的兩倍時(shí),會(huì)出現(xiàn)問(wèn)題。size 取其他值時(shí),讀取并返回最多 size 個(gè)字符(文本模式)或 size 個(gè)字節(jié)(二進(jìn)制模式)。如已到達(dá)文件末尾,f.read() 返回空字符串('')。
[size]為可選參數(shù)

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

f.readline()從文件中讀取單行數(shù)據(jù);字符串末尾保留換行符(\n),只有在文件不以換行符結(jié)尾時(shí),文件的最后一行才會(huì)省略換行符。這種方式讓返回值清晰明確;只要 f.readline() 返回空字符串,就表示已經(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()
''

從文件中讀取多行時(shí),可以用循環(huán)遍歷整個(gè)文件對(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) 把 string 的內(nèi)容寫(xiě)入文件,并返回寫(xiě)入的字符數(shù)。

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

寫(xiě)入其他類型的對(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()返回整數(shù),給出文件對(duì)象在文件中的當(dāng)前位置,表示為二進(jìn)制模式下時(shí)從文件開(kāi)始的字節(jié)數(shù),以及文本模式下的意義不明的數(shù)字。

f.seek(offset, whence) 可以改變文件對(duì)象的位置。通過(guò)向參考點(diǎn)添加 offset 計(jì)算位置;參考點(diǎn)由 whence 參數(shù)指定。 whence 值為 0 時(shí),表示從文件開(kāi)頭計(jì)算,1 表示使用當(dāng)前文件位置,2 表示使用文件末尾作為參考點(diǎn)。省略 whence 時(shí),其默認(rèn)值為 0,即使用文件開(kāi)頭作為參考點(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 時(shí)打開(kāi)的文件)中,只允許相對(duì)于文件開(kāi)頭搜索(使用 seek(0, 2) 搜索到文件末尾是個(gè)例外),唯一有效的 offset 值是能從 f.tell() 中返回的,或 0。其他 offset 值都會(huì)產(chǎn)生未定義的行為。

文件對(duì)象還支持 isatty() 和 truncate() 等方法,但不常用;文件對(duì)象的完整指南詳見(jiàn)庫(kù)參考。


8. 使用 json 保存結(jié)構(gòu)化數(shù)據(jù)

從文件寫(xiě)入或讀取字符串很簡(jiǎn)單,數(shù)字則稍顯麻煩,因?yàn)?read() 方法只返回字符串,這些字符串必須傳遞給 int() 這樣的函數(shù),接受 '123' 這樣的字符串,并返回?cái)?shù)字值 123。保存嵌套列表、字典等復(fù)雜數(shù)據(jù)類型時(shí),手動(dòng)解析和序列化的操作非常復(fù)雜。

Python 支持 JSON (JavaScript Object Notation) 這種流行數(shù)據(jù)交換格式,用戶無(wú)需沒(méi)完沒(méi)了地編寫(xiě)、調(diào)試代碼,才能把復(fù)雜的數(shù)據(jù)類型保存到文件。json 標(biāo)準(zhǔn)模塊采用 Python 數(shù)據(jù)層次結(jié)構(gòu),并將之轉(zhuǎn)換為字符串表示形式;這個(gè)過(guò)程稱為 serializing (序列化)。從字符串表示中重建數(shù)據(jù)稱為 deserializing (解序化)。在序列化和解序化之間,表示對(duì)象的字符串可能已經(jīng)存儲(chǔ)在文件或數(shù)據(jù)中,或通過(guò)網(wǎng)絡(luò)連接發(fā)送到遠(yuǎn)方的機(jī)器。
注解: JSON 格式通常用于現(xiàn)代應(yīng)用程序的數(shù)據(jù)交換。程序員早已對(duì)它耳熟能詳,可謂是交互操作的不二之選。
只需一行簡(jiǎn)單的代碼即可查看某個(gè)對(duì)象的 JSON 字符串表現(xiàn)形式:

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

dumps() 函數(shù)還有一個(gè)變體, dump() ,它只將對(duì)象序列化為 text file 。因此,如果 ftext file 對(duì)象,可以這樣做:

# json.dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)
json.dump(x, f)

要再次解碼對(duì)象,如果 f 是已打開(kāi)、供讀取的 text file 對(duì)象:

x = json.load(f)

這種簡(jiǎn)單的序列化技術(shù)可以處理列表和字典,但在 JSON 中序列化任意類的實(shí)例,則需要付出額外努力。json 模塊的參考包含對(duì)此的解釋。

參見(jiàn)pickle - 封存模塊與 JSON 不同,pickle 是一種允許對(duì)復(fù)雜 Python 對(duì)象進(jìn)行序列化的協(xié)議。因此,它為 Python 所特有,不能用于與其他語(yǔ)言編寫(xiě)的應(yīng)用程序通信。默認(rèn)情況下它也是不安全的:如果解序化的數(shù)據(jù)是由手段高明的攻擊者精心設(shè)計(jì)的,這種不受信任來(lái)源的 pickle 數(shù)據(jù)可以執(zhí)行任意代碼。


END

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容