微信公眾號:碼農充電站pro
個人主頁:https://codeshellme.github.io
過去的程式碼都是未經測試的程式碼。
目錄
無論是哪種程式語言,IO
操作都是非常重要的部分。I
即Input
(輸入),O
即Output
(輸出)。
IO
操作一般分為以下兩種:
- 磁碟IO: 即在磁碟上
讀寫
檔案。讀檔案
是指將檔案內容從磁碟讀入記憶體,寫檔案
是指將記憶體中的內容寫到磁碟。 - 網路IO: 即檔案在網路上傳輸。網路傳輸一般會有兩種角色,分別是
服務端
(如HTTP Server
)和客戶端
(如瀏覽器
)。
本節我們主要介紹磁碟IO
,即檔案讀寫
。
1,open
函式介紹
要想讀寫檔案,首先要開啟
一個檔案。
Python 中的內建函式open
用來開啟一個檔案,我們可以使用help(open)
,來檢視open
函式的原型,如下:
open(file, mode='r',
buffering=-1, encoding=None,
errors=None, newline=None,
closefd=True, opener=None)
該函式成功呼叫時會返回一個流stream
,用於讀寫檔案等操作;發生錯誤時會丟擲IOError
異常。
被開啟的檔案佔用了系統資源,使用完後要記得close
,否則會浪費系統資源。
不管以讀模式
開啟檔案,還是以寫模式
開啟檔案,成功開啟一個檔案後,這個可操作檔案的流
的內部都有一個隱含的指標
,一般這個指標會指向檔案開頭
或者檔案末尾
的位置,表示從檔案的哪個位置讀寫檔案。
可以看到,該函式支援8 個引數,但最重要的是前兩個引數:
file
:是指要開啟的檔案的路徑mode
:是指以什麼模式開啟檔案,要用引號
引住
mode
引數支援的模式(預設為讀文字
模式,即rt
)如下:
r
:以讀模式
開啟檔案(預設方式),指標在檔案開頭w
:以寫模式
開啟檔案,如果件已存在,則內容會被清空(指標在檔案開頭);如果檔案不存在,則會建立新檔案x
:建立一個新檔案,並以寫模式
開啟,指標在檔案開頭,如果檔案已存在,則丟擲FileExistsError
異常a
:以寫模式
開啟檔案,如果檔案已有內容,在寫入內容時,會追加
到檔案末尾(指標在檔案末尾)b
:以二進位制模式
開啟檔案,一般用於讀寫二進位制檔案,如圖片,視訊等t
:以文字模式
開啟檔案(預設方式),一般用於讀寫文字檔案+
:以讀寫模式
開啟檔案,指標在檔案開頭
這些模式還可以組合使用,常見的組合如下:
rb
:以二進位制模式
開啟一個檔案,用於只讀
r+
:開啟一個檔案,用於讀寫
rb+
:以二進位制模式
開啟一個檔案,用於讀寫
wb
:以二進位制模式
開啟一個檔案,用於寫
w+
:開啟一個檔案,用於讀寫
wb+
: 以二進位制模式
開啟一個檔案,用於讀寫
ab
: 以二進位制模式
開啟一個檔案,用於追加
a+
:開啟一個檔案用於讀寫
,指標在檔案末尾
ab+
:以二進位制模式
開啟一個檔案,用於讀寫
,指標在檔案末尾
2,open
函式示例
如下程式碼,成功開啟檔案./1.txt
:
f = open('./1.txt')
通過type(f)
檢視open
函式的返回值的型別:
>>> type(file)
<class '_io.TextIOWrapper'>
可看到,其返回值型別為_io.TextIOWrapper
。
我們用dir(f)
來檢視物件 f
支援的屬性和方法:
>>> dir(file)
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__',
'__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__iter__', '__le__', '__lt__',
'__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable',
'_finalizing',
'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno',
'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read',
'readable', 'readline', 'readlines', 'seek', 'seekable', 'tell',
'truncate', 'writable', 'write', 'writelines']
可以通過help(f.方法名)
來檢視每個方法的幫助手冊,也可以使用help(f)
來檢視該物件的所有屬性和方法,及其簡介。
我們來看一下常用方法的作用:
mode
:開啟檔案時的模式name
:被開啟的檔名close
:關閉檔案流,並重新整理緩衝區中的內容,之後不能再操作檔案closed
:檔案流是否已關閉flush
:重新整理寫緩衝區,只寫流
與非阻塞流
不適用read
:讀入檔案內容readable
:是否可讀readline
:讀入一行內容readlines
:讀入檔案所有的行,直至檔案末尾seek
:移動檔案指標的位置seekable
:檔案指標是否可被移動tell
:返回檔案指標
當前位置truncate
:截斷檔案內容writable
:是否可寫write
:向檔案中寫入內容writelines
:向檔案中寫入多行
3,關閉系統資源
正確的呼叫close()
函式是關鍵的。
在成功開啟一個檔案後,對該檔案進行操作(讀寫)時,有可能發生異常。
比如我們開啟的檔案只能用來寫
,如果用來讀
,則會發生異常:
>>> f = open('1.txt', 'w') # 用只讀模式開啟檔案
>>> f.readable() # 檢視檔案是否可讀
False # 返回 False,表示不可讀
>>> f.read() # 讀檔案,發生異常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
io.UnsupportedOperation: not readable
如果,我們將這段程式碼寫在檔案中:
#! /usr/bin/env python3
f = open('1.txt', 'w')
f.read()
f.close()
用python3
來執行,結果如下:
$ python3 Test.py
Traceback (most recent call last):
File "Test.py", line 4, in <module>
f.read()
io.UnsupportedOperation: not readable
可以看到,在執行到f.read()
這句程式碼的時候,程式異常退出,那麼後邊的f.close()
就沒有執行到,這就導致程式執行不夠完整,系統資源沒有關閉。
這時,我們可以用try...finally
來處理,如下:
#! /usr/bin/env python3
f = open('1.txt', 'w')
try:
f.read()
except Exception as e:
print('read file err:%s' % e)
finally:
f.close()
print('file closed')
上面程式碼的執行結果如下:
$ python3 Test.py
read file err:not readable
file closed
我們將f.close()
這句程式碼放在了finally
程式碼塊中,這樣,不管遇到什麼情況,f.close()
這句話總會被執行,就不會導致系統資源洩漏的問題。
4,with
語句使用
為了確保系統資源能夠關閉,Python 中提供了with
語句,能夠讓我們更加安全方面的使用open
函式,而不用關心資源關閉的問題。
with
語句也叫上下文管理器
,有了with
語句,我們可以這樣使用open
函式:
with open('./1.txt') as f:
print(f.read())
這樣的程式碼,不管在with
語句塊內出現怎樣的異常,close
函式都會被呼叫,而我們也不需要自己呼叫。
使用with
語句,就不再需要使用try...finally
語句,也使得程式碼更加簡潔。
需要特別注意的是,這裡的f
只能在with
語句塊中使用,一旦離開with
語句塊,f
就被關閉了。如果在with
語句塊之外使用f
進行讀寫等操作,將出現異常。
如下程式碼中,f.closed
將返回True
:
with open('./1.txt') as f:
pass
f.closed # True
5,with
語句原理
為什麼open
函式能夠使用with
語句?
實際上open
函式能夠使用with
語句的原因取決於open
的返回值的型別
。我們知道,open
的返回值的型別為_io.TextIOWrapper
,而這個類中有兩個方法,__enter__
方法和__exit__
方法。
我們再來看下with
語句的格式:
with ... as ... :
pass
with
關鍵字的後邊是一個表示式
,as
後邊是一個變數名,表示式的計算結果會賦值給as
後邊的變數。
Python 規定,只要一個類中有__enter__
和__exit__
方法,就可以使用with
語句。with
語句後邊的表示式執行完畢後,就會執行__enter__
方法,在退出with
語句塊時,會執行__exit__
方法。
我們自己編寫一個測試類,使其能夠使用with
語句:
#! /usr/bin/env python3
class TestWith:
def __init__(self):
print('執行__init__')
def __enter__(self):
print('執行__enter__')
def __exit__(self, exc_type, exc_val, exc_tb):
print('執行__exit__')
print('exc_type is %s' % exc_type)
print('exc_val is %s' % exc_val)
print('exc_tb is %s' % exc_tb)
再該類中有三個函式:
__init__
:建構函式,建立類的物件時呼叫__enter__
:進入with
語句塊時會呼叫__exit__
:離開with
語句塊時會呼叫
其中__exit__
方法有三個引數:
exc_type
:with
語句塊中的程式碼發生異常時的異常型別
exc_val
:發生異常時的異常值
exc_tb
:發生異常時的traceback
類的物件
我們這樣使用這個類:
with TestWith() as t:
print('test with')
用python3
來執行,結果如下:
$ python3 Test.py
執行__init__
執行__enter__
test with
執行__exit__
exc_type is None
exc_val is None
exc_tb is None
可以看到執行步驟是這樣的:
- 生成該類的物件,執行
__init__
方法 - 進入
with
語句塊,執行__enter__
方法 - 執行
with
語句塊中的程式碼 - 退出
with
語句塊,執行__exit__
方法
因為with
語句塊中沒有發生異常,所以__exit__
方法中的 exc_type
,exc_val
,exc_tb
三個引數均為None
。
下面再示範一個with
語句塊中出現異常的程式碼:
with TestWith() as t:
print('test with1...')
1 / 0 # 除數為 0,丟擲異常
print('test with2...')
該程式碼的執行結果如下:
$ python3 Test.py
執行__init__
執行__enter__
test with1...
執行__exit__
exc_type is <class 'ZeroDivisionError'>
exc_val is division by zero
exc_tb is <traceback object at 0x7fe8b7c98888>
Traceback (most recent call last):
File "Test.py", line 27, in <module>
1 / 0
ZeroDivisionError: division by zero
通過上面的執行結果可以看到,在執行1 / 0
之前,我們不用多說。在執行到1 / 0
時,出現異常,然後會執行__exit__
方法。
在執行結果中,我們能看到 exc_type
,exc_val
,exc_tb
三個引數的值,最後丟擲Traceback
異常。
with
語句中,丟擲異常的語句1 / 0
之後的程式碼不會再執行。
(完。)
推薦閱讀:
Python 簡明教程 --- 19,Python 類與物件
Python 簡明教程 --- 20,Python 類中的屬性與方法
Python 簡明教程 --- 21,Python 繼承與多型
Python 簡明教程 --- 22,Python 閉包與裝飾器
Python 簡明教程 --- 23,Python 異常處理
歡迎關注作者公眾號,獲取更多技術乾貨。