03.06 Python第七課7. 輸入輸出

7. 輸入輸出¶

有幾種方法可以顯示程序的輸出;數據可以以人類可讀的形式打印出來,或者寫入文件以供將來使用。本章將討論一些可能性。

7.1. 更漂亮的輸出格式¶

到目前為止,我們遇到了兩種寫入值的方法:表達式語句 和 print() 函數。(第三種是使用文件對象的 write() 方法;標準輸出文件可以作為 sys.stdout引用。更多相關信息可參考標準庫指南。)

通常,你需要更多地控制輸出的格式,而不僅僅是打印空格分隔的值。有幾種格式化輸出的方法。

  • 要使用 格式字字符串字面值 ,請在字符串的開始引號或三引號之前加上一個 f 或 F 。在此字符串中,你可以在 { 和 } 字符之間寫可以引用的變量或字面值的 Python 表達式。
  • >>> year = 2016>>> event = 'Referendum'>>> f'Results of the {year} {event}''Results of the 2016 Referendum'
  • 字符串的 str.format() 方法需要更多的手動操作。你仍將使用 { 和 } 來標記變量將被替換的位置,並且可以提供詳細的格式化指令,但你還需要提供要格式化的信息。
  • >>> 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%'
  • 最後,你可以使用字符串切片和連接操作自己完成所有的字符串處理,以創建你可以想象的任何佈局。字符串類型有一些方法可以執行將字符串填充到給定列寬的有用操作。

當你不需要花哨的輸出而只是想快速顯示某些變量以進行調試時,可以使用 repr() or str() 函數將任何值轉化為字符串。

str() 函數是用於返回人類可讀的值的表示,而 repr() 是用於生成解釋器可讀的表示(如果沒有等效的語法,則會強制執行 SyntaxError)對於沒有人類可讀性的表示的對象, str() 將返回和 repr()一樣的值。很多值使用任一函數都具有相同的表示,比如數字或類似列表和字典的結構。特殊的是字符串有兩個不同的表示。

幾個例子:

>>> 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, worldn'>>> hellos = repr(hello)>>> print(hellos)'hello, worldn'>>> # The argument to repr() may be any Python object:... repr((x, y, ('spam', 'eggs')))"(32.5, 40000, ('spam', 'eggs'))"

string 模塊包含一個 Template 類,它提供了另一種將值替換為字符串的方法,使用類似 $x 的佔位符並用字典中的值替換它們,但對格式的控制要少的多。

7.1.1. 格式化字符串文字¶

格式化字符串字面值 (常簡稱為 f-字符串)能讓你在字符串前加上 f 和 F 並將表達式寫成 {expression} 來在字符串中包含 Python 表達式的值。

可選的格式說明符可以跟在表達式後面。這樣可以更好地控制值的格式化方式。以下示例將pi舍入到小數點後三位:

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

在 ':' 後傳遞一個整數可以讓該字段成為最小字符寬度。這在使列對齊時很有用。:

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

其他的修飾符可用於在格式化之前轉化值。 '!a' 應用 ascii() ,'!s' 應用 str(),還有 '!r' 應用 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'.

有關這些格式規範的參考,請參閱參考指南 格式規格迷你語言。

7.1.2. 字符串的 format() 方法¶

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

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

花括號和其中的字符(稱為格式字段)將替換為傳遞給 str.format() 方法的對象。花括號中的數字可用來表示傳遞給 str.format() 方法的對象的位置。

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

如果在 str.format() 方法中使用關鍵字參數,則使用參數的名稱引用它們的值。:

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

位置和關鍵字參數可以任意組合:

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

如果你有一個非常長的格式字符串,你不想把它拆開,那麼你最好按名稱而不是位置引用變量來進行格式化。這可以通過簡單地傳遞字典和使用方括號 '[]' 訪問鍵來完成:

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

這也可以通過使用 '**' 符號將表作為關鍵字參數傳遞。:

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

這在與內置函數 vars() 結合使用時非常有用,它會返回包含所有局部變量的字典。

例如,下面幾行代碼生成一組整齊的列,其中包含給定的整數和它的平方以及立方:

>>> 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 72910 100 1000

關於使用 str.format() 進行字符串格式化的完整概述,請參閱 格式字符串語法 。

7.1.3. 手動格式化字符串¶

這是同一個平方和立方的表,手動格式化的:

>>> 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 72910 100 1000

(注意每列之間的一個空格是通過使用 print() 的方式添加的:它總是在其參數間添加空格。)

字符串對象的 str.rjust() 方法通過在左側填充空格來對給定寬度的字段中的字符串進行右對齊。類似的方法還有 str.ljust() 和 str.center() 。這些方法不會寫入任何東西,它們只是返回一個新的字符串,如果輸入的字符串太長,它們不會截斷字符串,而是原樣返回;這雖然會弄亂你的列布局,但這通常比另一種方法好,後者會在顯示值時可能不準確(如果你真的想截斷,你可以添加一個切片操作,例如 x.ljust(n)[:n] 。)

還有另外一個方法,str.zfill() ,它會在數字字符串的左邊填充零。它能識別正負號:

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

7.1.4. 舊的字符串格式化方法¶

% 操作符也可以用作字符串格式化。它將左邊的參數解釋為一個很像 sprintf() 風格 的格式字符串,應用到右邊的參數,並返回一個由此格式化操作產生的字符串。例如:

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

可在 printf 風格的字符串格式化 部分找到更多信息。

7.2. 讀寫文件¶

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

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

第一個參數是包含文件名的字符串。第二個參數是另一個字符串,其中包含一些描述文件使用方式的字符。mode 可以是 'r' ,表示文件只能讀取,'w'表示只能寫入(已存在的同名文件會被刪除),還有 'a' 表示打開文件以追加內容;任何寫入的數據會自動添加到文件的末尾。'r+' 表示打開文件進行讀寫。mode 參數是可選的;省略時默認為 'r'。

通常文件是以 text mode 打開的,這意味著從文件中讀取或寫入字符串時,都會以指定的編碼方式進行編碼。如果未指定編碼格式,默認值與平臺相關 (參見 open())。在mode 中追加的 'b' 則以 binary mode打開文件:現在數據是以字節對象的形式進行讀寫的。這個模式應該用於所有不包含文本的文件。

在文本模式下讀取時,默認會把平臺特定的行結束符 (Unix 上的 n, Windows 上的 rn) 轉換為 n。在文本模式下寫入時,默認會把出現的 n 轉換回平臺特定的結束符。這樣在幕後修改文件數據對文本文件來說沒有問題,但是會破壞二進制數據例如 JPEG 或 EXE 文件中的數據。請一定要注意在讀寫此類文件時應使用二進制模式。

在處理文件對象時,最好使用 with 關鍵字。 優點是當子句體結束後文件會正確關閉,即使在某個時刻引發了異常。 而且使用 with 相比等效的 try-finally 代碼塊要簡短得多:

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

如果你沒有使用 with 關鍵字,那麼你應該調用 f.close() 來關閉文件並立即釋放它使用的所有系統資源。如果你沒有顯式地關閉文件,Python的垃圾回收器最終將銷燬該對象併為你關閉打開的文件,但這個文件可能會保持打開狀態一段時間。另外一個風險是不同的Python實現會在不同的時間進行清理。

通過 with 語句或者調用 f.close() 關閉文件對象後,嘗試使用該文件對象將自動失敗。:

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

7.2.1. 文件對象的方法¶

本節中剩下的例子將假定你已創建名為 f 的文件對象。

要讀取文件內容,請調用 f.read(size) ,它會讀取一些數據並將其作為字符串(在文本模式下)或字節對象(在二進制模式下)返回。 size 是一個可選的數字參數。當 size 被省略或者為負的時候,將讀取並返回文件的整個內容;如果文件的大小是機器內存的兩倍,那麼就可能出現問題。否則,最多讀取並返回 size 字節的內容,如果已到達文件末尾,f.read()將返回一個空字符串 ('')。

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

f.readline() 從文件中讀取一行;換行符(n)留在字符串的末尾,如果文件不以換行符結尾,則在文件的最後一行省略。這使得返回值明確無誤;如果 f.readline() 返回一個空的字符串,則表示已經到達了文件末尾,而空行使用 'n' 表示,該字符串只包含一個換行符。:

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

要從文件中讀取行,你可以循環遍歷文件對象。這是內存高效,快速的,並簡化代碼:

>>> 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 的內容寫入到文件中,並返回寫入的字符數。:

>>> f.write('This is a testn')15

在寫入其他類型的對象之前,需要先把它們轉化為字符串(在文本模式下)或者字節對象(在二進制模式下):

>>> value = ('the answer', 42)>>> s = str(value) # convert the tuple to string>>> f.write(s)18

f.tell() 返回一個整數,給出文件對象在文件中的當前位置,表示為二進制模式下時從文件開始的字節數,以及文本模式下的不透明數字。

要改變文件對象的位置,請使用 f.seek(offset,from_what) 。通過向參考點添加 offset 來計算位置;參考點由 from_what 參數指定。from_what*值為0時,表示從文件開頭開始,1 表示從當前位置,2 表示把文件末尾作為參考點。*from_what 可以省略,默認為0,即使用文件開頭作為參考點。:

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

在文本文件(那些在模式字符串中沒有 b 的打開的文件)中,只允許相對於文件開頭搜索(使用 seek(0, 2) 搜索到文件末尾是個例外)並且唯一有效的 offset 值是那些能從 f.tell() 中返回的或者是零。其他 offset 值都會產生未定義的行為。

文件對象有一些額外的方法,例如 isatty() 和 truncate() ,它們使用頻率較低;有關文件對象的完整指南請參閱庫參考。

7.2.2. 使用 json 保存結構化數據¶

字符串可以很輕鬆地寫入文件並從文件中讀取出來。數字可能會費點勁,因為 read() 方法只能返回字符串,這些字符串必須傳遞給類似 int() 的函數,它會接受類似 '123' 這樣的字符串並返回其數字值 123。當你想保存諸如嵌套列表和字典這樣更復雜的數據類型時,手動解析和序列化會變得複雜。

Python 允許你使用稱為 JSON (JavaScript Object Notation) 的流行數據交換格式,而不是讓用戶不斷的編寫和調試代碼以將複雜的數據類型保存到文件中。名為 json 的標準模塊可以採用 Python 數據層次結構,並將它們轉化為字符串表示形式;這個過程稱為 serializing 。從字符串表示中重建數據稱為 deserializing 。在序列化和反序列化之間,表示對象的字符串可能已存儲在文件或數據中,或通過網絡連接發送到某個遠程機器。

註解

JSON格式通常被現代應用程序用於允許數據交換。許多程序員已經熟悉它,這使其成為互操作性的良好選擇。

如果你有一個對象 x ,你可以用一行簡單的代碼來查看它的 JSON 字符串表示:

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

dumps() 函數的另一個變體叫做 dump() ,它只是將對象序列化為 text file 。因此,如果 f 是一個 text file 對象,我們可以這樣做:

json.dump(x, f)

要再次解碼對象,如果 f 是一個打開的以供閱讀的 text file 對象:

x = json.load(f)

這種簡單的序列化技術可以處理列表和字典,但是在JSON中序列化任意類的實例需要額外的努力。 json 模塊的參考包含對此的解釋。

參見

pickle - 封存模塊

與 JSON 不同,pickle 是一種允許對任意複雜 Python 對象進行序列化的協議。因此,它為 Python 所特有,不能用於與其他語言編寫的應用程序通信。默認情況下它也是不安全的:如果數據是由熟練的攻擊者精心設計的,則反序列化來自不受信任來源的 pickle 數據可以執行任意代碼。



分享到:


相關文章: