1. 前言
第一次接觸 Python 語言的 IO API 時,是驚豔的。相比較其它語言所提供的 IO 流 API 。
無論是站在使用者的角度還是站在底層設計者的角度,都可以稱得上無與倫比。
很多人在學習 JAVA 語言中的 IO 流 API 時,幾乎是崩潰的。其 API 太多、API 之間的關係過於複雜。類的層次結構需要花費很多時間才能搞明白。API 設計者未免有炫技之嫌。
而 Python 的 IO 流操作,才真正應了哪句話:人生苦短,我學 python 。
以 open( ) 函式 為操作起點,便捷、快速地完成所有操作,絕對算得上輕量級設計的典範,且高度詮釋了“高內聚”概念。使用起來頗有“四兩撥千金”的輕鬆。
通過了解 open( ) 函式的引數設計,其開閉設計思想可謂使用到了極致。
2. open( ) 函式
2.1 函式原型
def open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True):
……
2.2 函式功能
開啟一個指定位置的檔案並返回 IO 流物件。
2.3 函式引數
Tip: open( ) 函式的引數看起來雖然有點多,在使用時,很多引數都可以採用預設設定,它會提供最優的工作方案。
-
file 引數: 指定檔案位置。可以是一個字串描述的檔案路徑,也可以是一個檔案描述符(int 型別)。
Tip: 當使用字串描述時,可以是絕對路徑,也可以是相對路徑。
絕對路徑: 以絕對位置作為路徑的起點。 不同的作業系統中會有差異性,windows 以邏輯碟符為絕對起點,Liunx 以 "/" 根目錄為絕對起點。
file=open("d:/guoke.txt")
Tip: 上述程式碼執行時,需要保證在系統的 d 盤下有一個名字 "guoke.txt" 的檔案。
相對路徑: 所謂相對路徑指以某一個已經存在的路徑(或叫參照目錄、當前目錄)做起點。 預設情況下,相對路徑以當前專案目錄作為參照目錄。可以使用 os 模組 中的 getcwd( ) 方法獲取當前參照目錄的資訊。
import os print(os.getcwd()) # 本程式碼的測試專案放在 d:\myc 下;專案名稱:filedmeo # 輸出結果 # D:\myc\filedmeo
如下程式碼需要保證在專案目錄中存在 " guoke.txt "
file = open("guoke.txt") # 執行時, python 直譯器會自動拼接一個完整路徑 D:\myc\filedmeo\guoke.txt
參照目錄 可以是不固定的,而是可變的。
改變相對路徑的參考目錄:
import os # 把 d 盤作為當前目錄 os.chdir("d:/") print(os.getcwd()) file = open("guoke.txt") # python 直譯器會從 d 盤根目錄下查詢 guoke.txt 檔案
描述符: 使用 open( ) 函式開啟一個檔案後,python 直譯器系統會為此檔案指定一個唯一的數字識別符號。可以用此描述符作為 open( ) 引數。
file = open("guo_ke.txt") # fileno() 獲取檔案的描述符 file1 = open(file.fileno())
Tip: 使用者檔案的描述符從 3 開始。0,1,2 是系統保留檔案描述符。
- 0:表示標準輸入(鍵盤)裝置描述符。
file = open(0) print("請輸入一個數字:") res = file.readline() print("回顯:", res) ''' 輸出結果 請輸入一個數字: 88 回顯: 88 '''
- 1:表示標準輸出裝置(顯示器)描述符。
file = open(1, "w") file.write("you are welcome!") #類似於 print("you are welcome!") 的功能
- 2:表示標準錯誤輸出裝置(顯示器)描述符。
file = open(2, "w") file.write("you are welcome!") #輸出文字會以紅色亮顯
-
mode: 檔案操作模式。預設為 "r" ,表示只讀模式。
模式關鍵字 描述 異常 'r' 以只讀方式開啟檔案 檔案不存時,會丟擲 FileNotFoundError 異常 ‘r+’ 以可讀、可寫方式開啟檔案 檔案不存時,會丟擲 FileNotFoundError 異常 ‘w’ 以可寫方式開啟檔案 檔案不存在時,建立一個位元組 0 的空檔案 ‘w+’ 以可寫、可讀方式開啟檔案(清空原內容) 檔案不存在時,建立一個位元組 0 的空檔案 ‘a’ 以追加方式開啟檔案 檔案不存在時,建立一個位元組 0 的空檔案 ‘a+’ 以可追加、可讀方式開啟檔案 檔案不存在時,建立一個位元組 0 的空檔案 ‘t’ 以文字檔案格式開啟檔案 預設 ‘b’ 以二進位制格式開啟檔案 ‘x’ 建立空檔案並且可寫 檔案存在時,丟擲 FileExistsError 異常 只要在模式組合中有 'r' 關鍵字,則檔案必須提前存在:
file = open("guo_ke.txt") file = open("guo_ke.txt", 'r') file = open("guo_ke.txt", 'rt') file = open("guo_ke.txt", 'r+t')
只要在模式組合中有 ‘w’ 關鍵字,則檔案可以不必預先存在,如果存在,則原檔案中內容會被清空。
# 可寫 file = open("guo_ke.txt", 'w') # 可寫、可讀 file = open("guo_ke.txt", 'w+')
只要在模式組合中有 ‘a’ 關鍵字,則檔案可以不必預先存在,如果存在,原檔案中內空不會被清空
# 追加寫 file = open("guo_ke.txt", 'a') # 追加寫、且可讀 file = open("guo_ke.txt", 'a+')
-
buffering: 設定緩衝策略。可取值為 0、1、>1 。
-
0: 在二進位制模式下關閉緩衝。
-
1:在文字模式下使用行緩衝。
行緩衝:以行資料為單位進行快取。
-
>1 的整數: 指定緩衝區的大小(以位元組為單位)。
如果沒有指定 buffering 引數,則會提供預設緩衝策略:
-
二進位制檔案使用固定大小的緩衝塊。
在許多系統上,緩衝區的長度通常為 4096 或 8192 位元組。
-
"Interactive" 文字檔案( isatty() 返回 True 的檔案)使用行緩衝。其他文字檔案使用和二進位制檔案相同的緩衝策略。
isatty( ) 方法檢測檔案是否連線到一個終端裝置。
-
-
encoding: 指定解碼或編碼檔案時使用的編碼名稱。
只能用於文字檔案。預設使用平臺編碼。
-
errors: 指定如何處理編碼和解碼時丟擲的錯誤。可選項如下:
- strict: 如果存在編碼錯誤,則引發 ValueError 異常。 預設值 None 具有相同的效果。
- ignore: 忽略錯誤。有可能會資料丟失。
- replace: 會將替換標記(例如 '?' )插入有錯誤資料的地方。
-
newline: 在讀或寫文字內容時如何處理換行符號。可取值 None,' ','\n','\r' 和 '\r\n'。
OS 不同,換行符的描述也有差異。Unix 的行結束 '\n'、Windows 中為 '\r\n'
- 從流中讀資料時,如果 newline 為 None,則啟用平臺約定換行模式。
- 寫入流時,如果 newline 為 None,則寫入的任何 '\n' 字元都將轉換為系統預設行分隔符 。如果 newline 是 ' ' 或 '\n',則直接寫入。如果 newline 是任何其他合法值,則寫入的任何 '\n' 字元將被轉換為給定的字串。
-
closefd:
file = open("guo_ke.txt",closefd=False) ''' 輸出結果 Traceback (most recent call last): File "D:/myc/filedmeo/檔案巢狀.py", line 1, in <module> file = open("guo_ke.txt",closefd=False) ValueError: Cannot use closefd=False with file name '''
如果通過一個字串路徑描述開啟檔案, closefd 必須為 True (預設值),否則將引發錯誤。
file = open("guo_ke.txt", ) # 通過 file 檔案的描述符開啟檔案 file1 = open(file.fileno(), closefd=False) file1.close() print("先開啟檔案:", file.closed) print("後開啟檔案:", file1.closed) ''' 輸出結果 先開啟檔案: False 後開啟檔案: True '''
當 open file1 檔案時設定為 closefd=False ,則當 file1 檔案關閉後,file 檔案將保持開啟狀態。
-
opener:可理解為 open( ) 函式是一個高階封裝物件,本質是通過 opener 引數接入了一個真正的具有底層檔案操作能力的介面。
import os def opener(path, flags): return os.open(path, flags) # 呼叫 opener('guo_ke.txt','r') 時的引數來自於 open() 的第一個和第二個 with open('guo_ke.txt', 'r', opener=opener) as f: print(f.read())
預設 opener 引數引用的就是 os.open( ) 方法。
3. 讀寫操作
呼叫 open( ) 函式後會返回一個 IO 流物件。IO 流物件中提供了常規的與讀寫相關的屬性和方法。
class IO(Generic[AnyStr]):
#返回檔案的讀寫模式
@abstractproperty
def mode(self) -> str:
pass
#返回檔案的名稱
@abstractproperty
def name(self) -> str:
pass
#關閉檔案
@abstractmethod
def close(self) -> None:
pass
#判斷檔案是否關閉
@abstractproperty
def closed(self) -> bool:
pass
#返回檔案描述符號,每開啟一個檔案,python 會分配一個唯一的數字描述符號
@abstractmethod
def fileno(self) -> int:
pass
#重新整理快取中的內容
@abstractmethod
def flush(self) -> None:
pass
#是否連線到一個終端裝置
@abstractmethod
def isatty(self) -> bool:
pass
# 引數 n 為 -1 或不傳遞時,一次性讀取檔案中的所有內容,如果檔案內容過多,可分多次讀取
# 讀取到檔案末尾時,返回一個空字串 ('')
@abstractmethod
def read(self, n: int = -1) -> AnyStr:
pass
# 檔案是否可讀
@abstractmethod
def readable(self) -> bool:
pass
# 從檔案中讀取一行;換行符(\n)留在字串的末尾
# 返回一個空的字串時,表示已經到達了檔案末尾
# 空行使用 '\n' 表示
@abstractmethod
def readline(self, limit: int = -1) -> AnyStr:
pass
# 讀取所有行並儲存到列表中
# 也可以使用 list(f)
@abstractmethod
def readlines(self, hint: int = -1) -> List[AnyStr]:
pass
# 移動讀寫游標,改變檔案的讀寫位置
# 通過向一個參考點新增 offset 來計算位置;參考點由 whence 引數指定。
# whence 的 0 值表示從檔案開頭起算,1 表示使用當前檔案位置,2 表示使用檔案末尾作為參考點。
# whence 如果省略則預設值為 0,即使用檔案開頭作為參考點。
@abstractmethod
def seek(self, offset: int, whence: int = 0) -> int:
pass
# 是否可以移動游標
@abstractmethod
def seekable(self) -> bool:
pass
# 返回檔案的當前位置
@abstractmethod
def tell(self) -> int:
pass
# 清除內容
@abstractmethod
def truncate(self, size: int = None) -> int:
pass
# 是否可寫
@abstractmethod
def writable(self) -> bool:
pass
# 向檔案寫入內容
@abstractmethod
def write(self, s: AnyStr) -> int:
pass
#向檔案寫入一行資料
@abstractmethod
def writelines(self, lines: List[AnyStr]) -> None:
pass
呼叫 open( ) 函式中使用文字模式時返回的是 TextIO 物件,相比較父類,多了幾個特定於文字操作的屬性。
class TextIO(IO[str]):
# 快取資訊
@abstractproperty
def buffer(self) -> BinaryIO:
pass
# 設定編碼
@abstractproperty
def encoding(self) -> str:
pass
# 裝置錯誤處理方案
@abstractproperty
def errors(self) -> Optional[str]:
pass
# 設定行快取
@abstractproperty
def line_buffering(self) -> bool:
pass
# 換行符的設定方案
@abstractproperty
def newlines(self) -> Any:
pass
3.1 文字檔案讀操作
- 基本操作
file = open("guo_ke.txt", mode='r')
print("讀寫模式:", file.mode)
print("檔名:", file.name)
print("檔案是否關閉:", file.closed)
print("檔案描述符號:", file.fileno())
print("檔案是否可讀", file.readable())
print("是否是標準輸入流:", file.isatty())
print("檔案是否可寫:", file.writable())
print("快取方案", file.buffer)
print("檔案預設編碼:", file.encoding)
print("程式設計錯誤處理方案", file.errors)
print("是否設定行快取", file.line_buffering)
print("換行符的設定方案", file.newlines)
'''
輸出結果
讀寫模式: r
檔名: guo_ke.txt
檔案是否關閉: False
檔案描述符號: 3
檔案是否可讀 True
是否是標準輸入流: False
檔案是否可寫: False
快取方案 <_io.BufferedReader name='guo_ke.txt'>
檔案預設編碼: cp936
程式設計錯誤處理方案 strict
是否設定行快取 False
換行符的設定方案 None
'''
cp936 指的是系統的第 936 號編碼方案,即 GBK 編碼。
-
多樣化的讀方法:
無論是讀還是寫時,需要理解一個檔案指標(游標)的概念,也可理解為檔案位置。讀或寫時,只能從當前位置向前移動。
提前準備好一個文字檔案,在檔案中寫入如下內容
You hide in my heart deeply.
Happiness! There is only you and I together time...
With you just I don't want to give anyone the chance.
Honey, can you marry me, I'll marry you!
Don't know love you count is a close reason?
-
read( ) 方法的使用
file = open("guo_ke.txt", "r") print("----------讀取所有內容--------------") res = file.read() print(res) print("----------讀取部分內容--------------") # 重新回到檔案頭 file.seek(0) res = file.read(100) print(res) # 關閉檔案資源 file.close() ''' 輸出結果 ----------讀取所有內容-------------- You hide in my heart deeply. Happiness! There is only you and I together time... With you just I don't want to give anyone the chance. Honey, can you marry me, I'll marry you! Don't know love you count is a close reason? ----------讀取部分內容-------------- You hide in my heart deeply. Happiness! There is only you and I together time... With you just I don '''
這裡有一個細節要注意:
第一次讀取完所有檔案內容後,讀取位置已經移到了檔案尾部。繼續讀取時是不能讀到資料的。
可通過 seek( ) 方法,把游標移到檔案頭部。
-
readline( ) 方法的使用
file = open("guo_ke.txt", "r")
print("---------讀取一行--------")
res = file.readline()
print("資料長度:", len(res))
print(res)
print("-----------限制內容-------------")
res = file.readline(10)
print("資料長度:", len(res))
print(res)
print("-----------以行為單位讀取所有資料-------------")
#回到檔案頭部位置
file.seek(0)
while True:
res = file.readline()
print(res)
if res == "":
break
file.close()
'''
輸出結果
---------讀取一行--------
資料長度: 29
You hide in my heart deeply.
-----------限制內容-------------
資料長度: 10
Happiness!
-----------以行為單位讀取所有資料-------------
You hide in my heart deeply.
Happiness! There is only you and I together time...
With you just I don't want to give anyone the chance.
Honey, can you marry me, I'll marry you!
Don't know love you count is a close reason?
'''
一行一行讀取所有內容時,輸出時會在行與行之間產生一個空行。原因是行結束符號 'n' 會被當成一個空行輸出。
- readline( ) 還有一個兄弟 readlines() 。把資料以行為單位一次性儲存一個列表中.
file = open("guo_ke.txt", "r")
print("-----------把檔案中資料以行為單位儲存在列表中---------")
res = file.readlines()
print(res)
file.close()
'''
輸出結果
-----------把檔案中資料以行為單位儲存在列表中---------
['You hide in my heart deeply.\n', 'Happiness! There is only you and I together time...\n', "With you just I don't want to give anyone the chance.\n", "Honey, can you marry me, I'll marry you!\n", "Don't know love you count is a close reason?"]
'''
注意使用資料時換行符號的影響。
讀取所有行也可以使用 ist(f) 方式。
file = open("guo_ke.txt", "r") print(list(file))
- 檔案物件支援以行為單位進行迭代操作。
file = open("guo_ke.txt", "r")
print("-----------迭代方式輸出檔案內容---------")
for f in file:
print(f)
file.close()
3.2 文字檔案寫操作
如果使用 "w" 模式進行寫操作時,會丟失原來資料。如果不希望這樣的事情 發生,可使用 "a" 模式對文寫操作。
file = open("guo_ke_0.txt", "w")
file.write("this is a test")
# 新增新行
file.write("\n")
file.write("who are you?")
# 把列表中資料一次寫入檔案
lst = ["food\n", "fish\n", "cat\n"]
file.write("\n")
file.writelines(lst)
file.close()
3.3 編碼的問題
對檔案同時做讀寫操作時,請務必保證編碼的一致性。
如下面的程式碼就會出現 UnicodeDecodeError 異常。
file = open("guo_ke_1.txt", mode="w", encoding="utf-8")
file.write("你好!果殼……")
file.close()
file_ = open("guo_ke_1.txt", mode="r", encoding="gbk")
res = file_.read()
print(res)
'''
輸出結果
Traceback (most recent call last):
File "D:/myc/filedmeo/亂碼問題.py", line 6, in <module>
res = file_.read()
UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 16: illegal multibyte sequence
'''
3.4 二進位制文字件操作
呼叫 open( ) 函式時使用 mode='rb' 返回的是 BinaryIO 物件。此物件提供了對二進位制檔案的讀寫,對二進位制檔案的讀寫操作和文字的沒有什麼太多區別。
文字檔案與二進位制文字的操作使用一個引數就能靈活切換。
class BinaryIO(IO[bytes]):
@abstractmethod
def write(self, s: Union[bytes, bytearray]) -> int:
pass
4. 總結
open( ) 函式是一個神奇的存在。無論是對文字檔案還是二進進位制檔案,無論是讀還是寫,它都能工作的很好。不得不佩服 python 設計者的簡潔設計理念。
像通過檔案描述符開啟檔案,使用 opener 引數自定義底層實現,都可稱得上神來之筆。
另使用檔案後一定要關閉,除了可以直接呼叫 close( ) 方法外,還可能使用 with 語句,此語法結構能自動呼叫 close().
with open("guo_ke.txt") as f:
pass