Python 簡明教程 --- 24,Python 檔案讀寫
阿新 • • 發佈:2020-07-20
> **微信公眾號:碼農充電站pro**
> **個人主頁:**
> **過去的程式碼都是未經測試的程式碼。**
**目錄**
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200720110122608.png?#center)
無論是哪種程式語言,`IO` 操作都是非常重要的部分。`I` 即`Input`(輸入),`O` 即`Output`(輸出)。
`IO` 操作一般分為以下兩種:
- **磁碟IO:** 即在磁碟上`讀寫`檔案。`讀檔案`是指將檔案內容從磁碟讀入記憶體,`寫檔案`是指將記憶體中的內容寫到磁碟。
- **網路IO:** 即檔案在網路上傳輸。網路傳輸一般會有兩種角色,分別是`服務端`(如`HTTP Server`)和`客戶端`(如`瀏覽器`)。
本節我們主要介紹`磁碟IO`,即`檔案讀寫`。
### 1,`open` 函式介紹
要想讀寫檔案,首先要`開啟`一個檔案。
Python 中的內建函式`open` 用來開啟一個檔案,我們可以使用`help(open)`,來檢視`open` 函式的原型,如下:
```python
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`:
```shell
f = open('./1.txt')
```
通過`type(f)`檢視`open` 函式的返回值的型別:
```shell
> >> type(file)
```
可看到,其返回值型別為`_io.TextIOWrapper`。
我們用`dir(f)` 來檢視`物件 f` 支援的屬性和方法:
```shell
>>> 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()` 函式是關鍵的。
在成功開啟一個檔案後,對該檔案進行操作(讀寫)時,有可能發生異常。
比如我們開啟的檔案只能用來`寫`,如果用來`讀`,則會發生異常:
```shell
>>> f = open('1.txt', 'w') # 用只讀模式開啟檔案
>>> f.readable() # 檢視檔案是否可讀
False # 返回 False,表示不可讀
>>> f.read() # 讀檔案,發生異常
Traceback (most recent call last):
File "", line 1, in
io.UnsupportedOperation: not readable
```
如果,我們將這段程式碼寫在檔案中:
```python
#! /usr/bin/env python3
f = open('1.txt', 'w')
f.read()
f.close()
```
用`python3` 來執行,結果如下:
```shell
$ python3 Test.py
Traceback (most recent call last):
File "Test.py", line 4, in
f.read()
io.UnsupportedOperation: not readable
```
可以看到,在執行到`f.read()` 這句程式碼的時候,程式異常退出,那麼後邊的`f.close()` 就沒有執行到,這就導致程式執行不夠完整,系統資源沒有關閉。
這時,我們可以用`try...finally`來處理,如下:
```python
#! /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')
```
上面程式碼的執行結果如下:
```shell
$ python3 Test.py
read file err:not readable
file closed
```
我們將`f.close()` 這句程式碼放在了`finally` 程式碼塊中,這樣,不管遇到什麼情況,`f.close()` 這句話總會被執行,就不會導致系統資源洩漏的問題。
### 4,`with` 語句使用
為了確保系統資源能夠關閉,Python 中提供了`with` 語句,能夠讓我們更加安全方面的使用`open` 函式,而不用關心資源關閉的問題。
`with` 語句也叫`上下文管理器`,有了`with` 語句,我們可以這樣使用`open` 函式:
```python
with open('./1.txt') as f:
print(f.read())
```
這樣的程式碼,不管在`with` 語句塊內出現怎樣的異常,`close` 函式都會被呼叫,而我們也不需要自己呼叫。
使用`with` 語句,就不再需要使用`try...finally` 語句,也使得程式碼更加簡潔。
需要特別注意的是,這裡的`f`只能在`with` 語句塊中使用,一旦離開`with` 語句塊,`f` 就被關閉了。如果在`with` 語句塊之外使用`f` 進行讀寫等操作,將出現異常。
如下程式碼中,`f.closed` 將返回`True`:
```python
with open('./1.txt') as f:
pass
f.closed # True
```
### 5,`with` 語句原理
為什麼`open` 函式能夠使用`with` 語句?
實際上`open` 函式能夠使用`with` 語句的原因取決於`open` 的返回值的`型別`。我們知道,`open` 的返回值的型別為`_io.TextIOWrapper`,而這個類中有兩個方法,`__enter__` 方法和`__exit__` 方法。
我們再來看下`with` 語句的格式:
```python
with ... as ... :
pass
```
`with` 關鍵字的後邊是一個`表示式`,`as` 後邊是一個變數名,表示式的計算結果會賦值給`as` 後邊的變數。
Python 規定,只要一個類中有`__enter__`和`__exit__` 方法,就可以使用`with` 語句。`with` 語句後邊的表示式執行完畢後,就會執行`__enter__` 方法,在退出`with` 語句塊時,會執行`__exit__` 方法。
我們自己編寫一個測試類,使其能夠使用`with` 語句:
```python
#! /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` 類的物件
我們這樣使用這個類:
```python
with TestWith() as t:
print('test with')
```
用`python3` 來執行,結果如下:
```shell
$ python3 Test.py
執行__init__
執行__enter__
test with
執行__exit__
exc_type is None
exc_val is None
exc_tb is None
```
可以看到執行步驟是這樣的:
1. 生成該類的物件,執行`__init__` 方法
2. 進入`with` 語句塊,執行`__enter__` 方法
3. 執行`with` 語句塊中的程式碼
4. 退出`with` 語句塊,執行`__exit__` 方法
因為`with` 語句塊中沒有發生異常,所以`__exit__` 方法中的 `exc_type`,`exc_val`,`exc_tb` 三個引數均為`None`。
下面再示範一個`with` 語句塊中出現異常的程式碼:
```python
with TestWith() as t:
print('test with1...')
1 / 0 # 除數為 0,丟擲異常
print('test with2...')
```
該程式碼的執行結果如下:
```shell
$ python3 Test.py
執行__init__
執行__enter__
test with1...
執行__exit__
exc_type is
exc_val is division by zero
exc_tb is
Traceback (most recent call last):
File "Test.py", line 27, in
1 / 0
ZeroDivisionError: division by zero
```
通過上面的執行結果可以看到,在執行`1 / 0` 之前,我們不用多說。在執行到`1 / 0` 時,出現異常,然後會執行`__exit__` 方法。
在執行結果中,我們能看到 `exc_type`,`exc_val`,`exc_tb` 三個引數的值,最後丟擲`Traceback` 異常。
`with` 語句中,丟擲異常的語句`1 / 0` 之後的程式碼不會再執行。
(完。)
---
**推薦閱讀:**
[Python 簡明教程 --- 19,Python 類與物件](https://www.cnblogs.com/codeshell/p/13193866.html)
[Python 簡明教程 --- 20,Python 類中的屬性與方法](https://www.cnblogs.com/codeshell/p/13197968.html)
[Python 簡明教程 --- 21,Python 繼承與多型](https://www.cnblogs.com/codeshell/p/13233821.html)
[Python 簡明教程 --- 22,Python 閉包與裝飾器](https://www.cnblogs.com/codeshell/p/13237874.html)
[Python 簡明教程 --- 23,Python 異常處理](https://www.cnblogs.com/codeshell/p/13332048.html)
---
歡迎關注作者公眾號,獲取更多技術乾貨。
![碼農充電站pro](https://img-blog.csdnimg.cn/20200505082843773.png?#pic