### 7. 輸入和輸出
展現程序的輸出有多種方法;可以打印成人類可讀的形式,也可以寫入到文件以便后面使用。本章將討論其中的幾種方法。
### 7.1. 格式化輸出
到目前為止我們遇到過兩種輸出值的方法:*表達式*語句和[print()](# "print")函數。(第三個方式是使用文件對象的write()方法;標準輸出文件可以引用 sys.stdout。詳細內容參見庫參考手冊。)
通常你會希望更好地控制輸出的格式而不是簡單地打印用空格分隔的值。有兩種方法來設置輸出格式;第一種方式是自己做所有的字符串處理;使用字符串切片和連接操作,你可以創建任何你能想象到的布局。字符串類型有一些方法,用于執行將字符串填充到指定列寬度的有用操作;這些稍后將討論。第二種方法是使用[str.format()](# "str.format")方法。
[string](# "string: Common string operations.")模塊包含一個[Template](# "string.Template")類,提供另外一種向字符串代入值得方法。
當然還有一個問題:如何將值轉換為字符串?幸運的是,Python 有方法將任何值轉換為字符串:將它傳遞給[repr()](# "repr")或[str()](# "str")函數。
[str()](# "str")函數的用意在于返回人類可讀的表現形式,而[repr()](# "repr")的用意在于生成解釋器可讀的表現形式(如果沒有等價的語法將會引發[SyntaxError](# "exceptions.SyntaxError")異常)。對于對人類并沒有特別的表示形式的對象,[str()](# "str")和[repr()](# "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, 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
~~~
(注意在第一個示例中,每列之間的一個空格由[print()](# "print")自動添加:它總會在它的參數之間添加空格。)
上面的例子演示了字符串對象的[str.rjust()](# "str.rjust")方法,它通過在左側填充空格使字符串在給定寬度的列右對齊。類似的方法還有[str.ljust()](# "str.ljust")和[str.center()](# "str.center")。這些方法不會輸出任何內容,它們只返回新的字符串。如果輸入的字符串太長,它們不會截斷字符串,而是保持原樣返回;這會使列的格式變得混亂,但是通常好于另外一種選擇,那可能是一個錯誤的值。(如果你真的想要截斷,可以加上一個切片操作,例如x.ljust(n)[:n]。)
另外一種方法 [str.zfill()](# "str.zfill"),它向數值字符串左側填充零。該函數可以正確識別正負號:
~~~
>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'
~~~
[str.format()](# "str.format")方法的基本用法如下所示:
~~~
>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"
~~~
花括號及其中的字符(稱為格式字段)將被替換為傳遞給[str.format()](# "str.format")方法的對象。可以用括號中的數字指定傳遞給[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()](# "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.
~~~
'!a'(用于[ascii()](# "ascii")),'!s'(用于[str()](# "str"))和'!r'(用于[repr()](# "repr"))可以用來格式化之前對值進行轉換。
~~~
>>> 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.
~~~
字段名后允許可選的': '和格式指令。這允許更好地控制如何設置值的格式。下面的例子將 Pi 轉為三位精度。
~~~
>>> import math
>>> print('The value of PI is approximately {0:.3f}.'.format(math.pi))
The value of PI is approximately 3.142.
~~~
':'后面緊跟一個整數可以限定該字段的最小寬度。這在美化表格時很有用。
~~~
>>> 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
~~~
如果你有一個實在是很長的格式字符串但又不想分開寫,要是可以按照名字而不是位置引用變量就好了。有個簡單的方法,可以傳入一個字典,然后使用'[]'訪問。
~~~
>>> 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()](# "vars")組合起來將更加有用,該函數返回一個包含所有局部變量的字典。
關于[str.format()](# "str.format")完整的描述,請參見[*格式字符串語法*](#)。
#### 7.1.1. 舊式的字符串格式
%運算符也可以用于字符串格式化。它將左邊類似sprintf()-風格的參數應用到右邊的參數,然后返回這種格式化操作生成的字符串。例如:
~~~
>>> import math
>>> print('The value of PI is approximately %5.3f.' % math.pi)
The value of PI is approximately 3.142.
~~~
在[*printf-style String Formatting*](#)一節,可以找到更多的信息。
### 7.2. 讀寫文件
[open()](# "open")返回一個[*文件對象*](#),最常見的用法帶有兩個參數:open(filename,mode)。
~~~
>>> f = open('workfile', 'w')
~~~
第一個參數是一個含有文件名的字符串。第二個參數也是一個字符串,含有描述如何使用該文件的幾個字符。*mode*為'r'時表示只是讀取文件;w 表示只是寫入文件(已經存在的同名文件將被刪掉);'a'表示打開文件進行追加,寫入到文件中的任何數據將自動添加到末尾。'r+'表示打開文件進行讀取和寫入。*mode* 參數是可選的,默認為'r'。
通常,文件以*文本*打開,這意味著,你從文件讀出和向文件寫入的字符串會被特定的編碼方式(默認是UTF-8)編碼。模式后面的'b'以*二進制模式*打開文件:數據會以字節對象的形式讀出和寫入。這種模式應該用于所有不包含文本的文件。
在文本模式下,讀取時默認會將平臺有關的行結束符(Unix上是\n, Windows上是\r\n)轉換為\n。在文本模式下寫入時,默認會將出現的\n轉換成平臺有關的行結束符。這種暗地里的修改對 ASCII 文本文件沒有問題,但會損壞JPEG或EXE這樣的二進制文件中的數據。使用二進制模式讀寫此類文件時要特別小心。
#### 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 file\n'
>>> 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 test\n')
15
~~~
如果想寫入字符串以外的數據,需要先將它轉換為一個字符串:
~~~
>>> value = ('the answer', 42)
>>> s = str(value)
>>> f.write(s)
18
~~~
f.tell()返回一個給出文件對象在文件中當前位置的整數,在二進制模式下表示自文件開頭的比特數,在文本模式下是一個無法理解的數。
若要更改該文件對象的位置,可以使用f.seek(offset,from_what)。新的位置由參考點加上 *offse* 計算得來,參考點的選擇則來自于 *from_what* 參數。*from_what* 值為 0 表示以文件的開始為參考點,1 表示以當前的文件位置為參考點,2 表示以文件的結尾為參考點。*from_what* 可以省略,默認值為 0,表示以文件的開始作為參考點。
~~~
>>> f = open('workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5) # Go to the 6th byte in the file
>>> f.read(1)
'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
>>> f.read(1)
'd'
~~~
在文本文件中(沒有以b模式打開),只允許從文件頭開始尋找(有個例外是用seek(0,2)尋找文件的最末尾處)而且合法的*偏移*值只能是f.tell()返回的值或者是零。其它任何*偏移* 值都會產生未定義的行為。
使用完一個文件后,調用f.close()可以關閉它并釋放其占用的所有系統資源。調用f.close()后,再嘗試使用該文件對象將失敗。
~~~
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file
~~~
處理文件對象時使用[with](#)關鍵字是很好的做法。這樣做的好處在于文件用完后會自動關閉,即使過程中發生異常也沒關系。它還比編寫一個等同的[try](#)-[finally](#)語句要短很多:
~~~
>>> with open('workfile', 'r') as f:
... read_data = f.read()
>>> f.closed
True
~~~
文件對象還有一些不太常用的方法,例如isatty()和truncate();有關文件對象的完整指南,請參閱 Python 庫參考手冊。
#### 7.2.2. 使用[json](# "json: Encode and decode the JSON format.")存儲結構化數據
從文件中讀寫字符串很容易。數值就要多費點兒周折,因為read ()方法只會返回字符串,應將其傳入[int()](# "int")這樣的函數,就可以將'123'這樣的字符串轉換為對應的數值 123。當你想要保存更為復雜的數據類型,例如嵌套的列表和字典,手工解析和序列化它們將變得更復雜。
好在用戶不是非得自己編寫和調試保存復雜數據類型的代碼,Python 允許你使用常用的數據交換格式[JSON(JavaScript Object Notation)](http://json.org)。標準模塊[json](# "json: Encode and decode the JSON format.")可以接受 Python 數據結構,并將它們轉換為字符串表示形式;此過程稱為*序列化*。從字符串表示形式重新構建數據結構稱為*反序列化*。序列化和反序列化的過程中,表示該對象的字符串可以存儲在文件或數據中,也可以通過網絡連接傳送給遠程的機器。
注意
JSON 格式經常用于現代應用程序中進行數據交換。許多程序員都已經熟悉它了,使它成為相互協作的一個不錯的選擇。
如果你有一個對象x,你可以用簡單的一行代碼查看其 JSON 字符串表示形式:
~~~
>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'
~~~
[dumps()](# "json.dumps")函數的另外一個變體[dump()](# "json.dump"),直接將對象序列化到一個[*文本文件*](#)。所以如果f是為寫入而打開的一個[*文本文件*](#)對象,我們可以這樣做:
~~~
json.dump(x, f)
~~~
為了重新解碼對象,如果f是為讀取而打開的[*文本文件*](#)對象:
~~~
x = json.load(f)
~~~
這種簡單的序列化技術可以處理列表和字典,但序列化任意類實例為 JSON 需要一點額外的努力。[Json](# "json: Encode and decode the JSON format.")模塊的手冊對此有詳細的解釋。
另請參閱
[pickle](# "pickle: Convert Python objects to streams of bytes and back.") - pickle模塊
與[*JSON*](#) 不同,*pickle* 是一個協議,它允許任意復雜的 Python 對象的序列化。因此,它只能用于 Python 而不能用來與其他語言編寫的應用程序進行通信。默認情況下它也是不安全的:如果數據由熟練的攻擊者精心設計, 反序列化來自一個不受信任源的 pickle 數據可以執行任意代碼。
- Python 2 教程
- 1. 吊吊你的胃口
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限
- Python 2 標準庫
- 1. 引言
- 2. 內建函數
- 3. 不太重要的內建函數
- 4. 內建的常量
- 5. 內建的類型
- 6. 內建的異常
- 7. String Services
- 8. Data Types
- 9. Numeric and Mathematical Modules
- 10. File and Directory Access
- 11. Data Persistence
- 13. File Formats
- 14. Cryptographic Services
- 15. Generic Operating System Services
- 16. Optional Operating System Services
- 17. Interprocess Communication and Networking
- 18. Internet Data Handling
- 20. Internet Protocols and Support
- 26. Debugging and Profiling
- 28. Python Runtime Services
- Python 2 語言參考
- 1. 簡介
- 2. 詞法分析
- 3. 數據模型
- 4. 執行模型
- 5. 表達式
- 6. 簡單語句
- 7. 復合語句
- 8. 頂層的組件
- 9. 完整的語法規范
- Python 3 教程
- 1. 引言
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限