您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關python讀寫文件操作的方法的內容。小編覺得挺實用的,因此分享給大家做個參考。一起跟隨小編過來看看吧。
I/O操作概述
文件讀寫實現原理與操作步驟
文件打開模式
Python文件操作步驟示例
Python文件讀取相關方法
文件讀寫與字符編碼
I/O在計算機中是指Input/Output,也就是Stream(流)的輸入和輸出。這里的輸入和輸出是相對于內存來說的,Input Stream(輸入流)是指數據從外(磁盤、網絡)流進內存,Output Stream是數據從內存流出到外面(磁盤、網絡)。程序運行時,數據都是在內存中駐留,由CPU這個超快的計算核心來執行,涉及到數據交換的地方(通常是磁盤、網絡操作)就需要IO接口。
那么這個IO接口是由誰提供呢?高級編程語言中的IO操作是如何實現的呢?
操作系統是個通用的軟件程序,其通用目的如下:
硬件驅動
進程管理
內存管理
網絡管理
安全管理
I/O管理
操作系統屏蔽了底層硬件,向上提供通用接口。因此,操作I/O的能力是由操作系統的提供的,每一種編程語言都會把操作系統提供的低級C接口封裝起來供開發者使用,Python也不例外。
文件讀寫就是一種常見的IO操作。那么根據上面的描述,可以推斷python也應該封裝操作系統的底層接口,直接提供了文件讀寫相關的操作方法。事實上,也確實如此,而且Java、PHP等其他語言也是。
那么我們要操作的對象是什么呢?我們又如何獲取要操作的對象呢?
由于操作I/O的能力是由操作系統提供的,且現代操作系統不允許普通程序直接操作磁盤,所以讀寫文件時需要請求操作系統打開一個對象(通常被稱為文件描述符--file descriptor, 簡稱fd),這就是我們在程序中要操作的文件對象。
通常高級編程語言中會提供一個內置的函數,通過接收"文件路徑"以及“文件打開模式”等參數來打開一個文件對象,并返回該文件對象的文件描述符。因此通過這個函數我們就可以獲取要操作的文件對象了。這個內置函數在Python中叫open(), 在PHP中叫fopen(),
不同的編程語言讀寫文件的操作步驟大體都是一樣的,都分為以下幾個步驟:
1)打開文件,獲取文件描述符2)操作文件描述符--讀/寫3)關閉文件
只是不同的編程語言提供的讀寫文件的api是不一樣的,有些提供的功能比較豐富,有些比較簡陋。
需要注意的是:文件讀寫操作完成后,應該及時關閉。一方面,文件對象會占用操作系統的資源;另外一方面,操作系統對同一時間能打開的文件描述符的數量是有限制的,在Linux操作系統上可以通過ulimit -n
來查看這個顯示數量。如果不及時關閉文件,還可能會造成數據丟失。因為我將數據寫入文件時,操作系統不會立刻把數據寫入磁盤,而是先把數據放到內存緩沖區異步寫入磁盤。當調用close方法時,操作系統會保證把沒有寫入磁盤的數據全部寫到磁盤上,否則可能會丟失數據。
我們先來看下在Python、PHP和C語言中打開文件的函數定義
# Python2open(name[, mode[, buffering]])# Python3open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
resource fopen ( string $filename , string $mode [, bool $use_include_path = false [, resource $context ]] )
int open(const char * pathname, int flags);
會發現以上3種編程語言內置的打開文件的方法接收的參數中,除了都包含一個“文件路徑名稱”,還會包含一個mode參數(C語言的open函數中的flags參數作用相似)。這么mode參數定義的是打開文件時的模式,常見的文件打開模式有:只讀、只寫、可讀可寫、只追加。不同的編程語言中對文件打開模式的定義有些微小的差別,我們來看下Python中的文件打開模式有哪些。
文件打開模式 | 描述 |
---|---|
r | 以只讀模式打開文件,并將文件指針指向文件頭;如果文件不存在會報錯 |
w | 以只寫模式打開文件,并將文件指針指向文件頭;如果文件存在則將其內容清空,如果文件不存在則創建 |
a | 以只追加可寫模式打開文件,并將文件指針指向文件尾部;如果文件不存在則創建 |
r+ | 在r的基礎上增加了可寫功能 |
w+ | 在w的基礎上增加了可讀功能 |
a+ | 在a的基礎上增加了可讀功能 |
b | 讀寫二進制文件(默認是t,表示文本),需要與上面幾種模式搭配使用,如ab,wb, ab, ab+(POSIX系統,包括Linux都會忽略該字符) |
思考1: r+、w+和a+都可以實現對文件的讀寫,那么他們有什么區別呢?
r+會覆蓋當前文件指針所在位置的字符,如原來文件內容是"Hello,World",打開文件后寫入"hi"則文件內容會變成"hillo, World"
w+與r+的不同是,w+在打開文件時就會先將文件內容清空,不知道它有什么用
a+與r+的不同是,a+只能寫到文件末尾(無論當前文件指針在哪里)
思考2: 為什么要定義這些模式呢?為什么不能像我們用word打開一篇文檔一樣既可以讀,又可以寫,還可修改呢?
關于這個問題,我查了很多資料,也沒找到很權威的說明。在跟同行朋友交流過程中,發現大家主要有兩種觀點:
跟安全有關,有這種觀點的大部分是做運維的朋友,他們認為這就像linux上的rwx(讀、寫、執行)權限。
跟操作系統內核管理I/O的機制有關,有這種觀點的大部分是做C開發的,特別是與內核相關的開發人員。為了提高讀寫速度,要寫入磁盤的數據會先放進內存緩沖區,之后再回寫。由于可能會同時打開很多文件,當要回寫數據時,需要遍歷以打開的文件判斷是否需要回寫。他們認為如果打開文件時指定了讀寫模式,那么需要回寫時,只要去查找以“可寫模式”打開的文件就可以了。
我們來讀取這樣一個文本文件:song.txt,該文件的字符編碼為utf-8。
匆匆那年我們 究竟說了幾遍 再見之后再拖延 可惜誰有沒有 愛過不是一場 七情上面的雄辯 匆匆那年我們 一時匆忙撂下 難以承受的諾言 只有等別人兌現
Python3實現:
# 第一步:(以只讀模式)打開文件f = open('song.txt', 'r', encoding='utf-8')# 第二步:讀取文件內容print(f.read())# 第三步:關閉文件f.close()
這里說下Python2的實現
# 第一步:(以只讀模式)打開文件f = open('song.txt', 'r')# 第二步:讀取文件內容print(f.read().decode('utf-8'))# 第三步:關閉文件f.close()
說明:
Python3中已經內置對Unicode的支持,字符串str已經是真正的Unicode字符串。也就是說Python3中的文件讀取方法已經自動完成了解碼處理,因此無需再手動進行解碼,可以直接將讀取的文件中的內容進行打印;Python2中的字符串str是字節串,讀取文件得到的也是字節串,在打印之前應該手動將其解碼成Unicode字符串。關于這部分的說明,可以參考之前這篇文章<<再談Python中的字符串與字符編碼>>。
在實現基本功能的前提下,考慮一些可能的意外因素。因為文件讀寫時都有可能產生IO錯誤(IOError),一旦出錯,后面包括f.close()在內的所有代碼都不會執行了。因此我們要保證文件無論如何都能被關閉。那么可以用try...finally來實現,這實際上就是try...except..finally的簡化版(我們只用Python3來進行示例演示):
f = ''try: f = open('song.txt', 'r', encoding='utf-8') print(f.read()) num = 10 / 0finally: print('>>>>>>finally') if f: f.close()
輸出結果:
匆匆那年我們 究竟說了幾遍 再見之后再拖延 可惜誰有沒有 愛過不是一場 七情上面的雄辯 匆匆那年我們 一時匆忙撂下 難以承受的諾言 只有等別人兌現>>>>>>finally Traceback (most recent call last): File "<stdin>", line 4, in <module>ZeroDivisionError: division by zero
輸出結果說明,盡管with代碼塊中出現了異常,但是”>>>>>>finally“ 信息還是被打印了,說明finally代碼塊被執行,即文件關閉操作被執行。但是結果中錯誤信息還是被輸出了,因此還是建議用一個完成的try...except...finally語句對異常信息進行捕獲和處理。
為了避免忘記或者為了避免每次都要手動關閉文件,我們可以使用with語句(一種語法糖,語法糖語句通常是為了簡化某些操作而設計的)。with語句會在其代碼塊執行完畢之后自動關閉文件。因此我們可以這樣來改寫上面的程序:
with open('song.txt', 'r', encoding='utf-8') as f: print(f.read())print(f.closed)
輸出結果:
匆匆那年我們 究竟說了幾遍 再見之后再拖延可惜誰有沒有 愛過不是一場 七情上面的雄辯匆匆那年我們 一時匆忙撂下 難以承受的諾言只有等別人兌現True
是不是變得簡介多了,代碼結構也比較清晰了。with之后打印的f.closed屬性值為True,說明文件確實被關閉了。
思考:
with語句會幫我們自動處理異常信息嗎?
要回答這個問題就要提到“上下文管理器” 和 with語句的工作流程。
with語句不僅僅可以用于文件操作,它實際上是一個很通用的結構,允許使用所謂的上下文管理器(context manager)。上下文管理器是一種支持__enter__()和__exit__()這兩個方法的對象。__enter__()方法不帶任何參數,它在進入with語句塊的時候被調用,該方法的返回值會被賦值給as關鍵字之后的變量。__exit__()方法帶有3個參數:type(異常類型), value(異常信息), trace(異常棧),當with語句的代碼塊執行完畢或執行過程中因為異常而被終止都會調用__exit__()方法。正常退出時該方法的3個參數都為None,異常退出時該方法的3個參數會被分別賦值。如果__exit__()方法返回值(真值測試結果)為True則表示異常已經被處理,命令執行結果中就不會拋出異常信息了;反之,如果__exit__()方法返回值(真值測試結果)為False,則表示異常沒有被處理并且會向外拋出該異常。
現在我們應該明白了,異常信息會不會被處理是由with后的語句返回對象的__exit__()方法決定的。文件可以被用作上下文管理器。它的__enter__方法返回文件對象本身,__exit__方法會關閉文件并返回None。我們看下file類中關于這兩個方法的實現:
def __enter__(self): # real signature unknown; restored from __doc__ """ __enter__() -> self. """ return self def __exit__(self, *excinfo): # real signature unknown; restored from __doc__ """ __exit__(*excinfo) -> None. Closes the file. """ pass
可見,file類的__exit__()方法的返回值為None,None的真值測試結果為False,因此用于文件讀寫的with語句代碼塊中的異常信息還是會被拋出來,需要我們自己去捕獲并處理。
with open('song.txt', 'r', encoding='utf-8') as f: print(f.read()) num = 10 / 0
輸出結果:
匆匆那年我們 究竟說了幾遍 再見之后再拖延 可惜誰有沒有 愛過不是一場 七情上面的雄辯 匆匆那年我們 一時匆忙撂下 難以承受的諾言 只有等別人兌現 Traceback (most recent call last): File "<stdin>", line 3, in <module> ZeroDivisionError: division by zero
注意: 上面所說的__exit__()方法返回值(真值測試結果)為True則表示異常已經被處理,指的是with代碼塊中出現的異常。它對于with關鍵字之后的代碼中出現的異常是不起作用的,因為還沒有進入上下文管理器就已經發生異常了。因此,無論如何,還是建議在必要的時候在with語句外面套上一層try...except來捕獲和處理異常。
有關“上下文管理器”這個強大且高級的特性的更多信息,請參看Python參考手冊中的上下文管理器部分。或者可以在Python庫參考中查看上下文管理器和contextlib部分。
我們知道,對文件的讀取操作需要將文件中的數據加載到內存中,而上面所用到的read()方法會一次性把文件中所有的內容全部加載到內存中。這明顯是不合理的,當遇到一個幾個G的的文件時,必然會耗光機器的內存。這里我們來介紹下Python中讀取文件的相關方法:
方法 | 描述 |
---|---|
read() | 一次讀取文件所有內容,返回一個str |
read(size) | 每次最多讀取指定長度的內容,返回一個str;在Python2中size指定的是字節長度,在Python3中size指定的是字符長度 |
readlines() | 一次讀取文件所有內容,按行返回一個list |
readline() | 每次只讀取一行內容 |
此外,還要兩個與文件指針位置相關的方法
方法 | 描述 |
---|---|
seek(n) | 將文件指針移動到指定字節的位置 |
tell() | 獲取當前文件指針所在字節位置 |
下面來看下操作實例
with open('song.txt', 'r') as f: print(f.read(12).decode('utf-8'))
輸出結果:
匆匆那年
結果說明:Python2中read(size)方法的size參數指定的要讀取的字節數,而song.txt文件是UTF-8編碼的內容,一個漢字占3個字節,因此12個字節剛好是4個漢字。
with open('song.txt', 'r', encoding='utf-8') as f: print(f.read(12))
輸出結果:
匆匆那年我們 究竟說
結果說明:Python3中read(size)方法的size參數指定的要讀取的字符數,這與文件的字符編碼無關,就是返回12個字符。
with open('song.txt', 'r', encoding='utf-8') as f: print(f.readline())
with open('song.txt', 'r') as f: print(f.readline().decode('utf-8'))
輸出結果都一樣:
匆匆那年我們 究竟說了幾遍 再見之后再拖延
這里我們只以Python3來進行實例操作,Python2僅僅是需要在讀取到內容后進行手動解碼而已,上面已經有示例。
with open('song.txt', 'r', encoding='utf-8') as f: for line in f.readlines(): print(line)
輸出結果:
匆匆那年我們 究竟說了幾遍 再見之后再拖延 可惜誰有沒有 愛過不是一場 七情上面的雄辯 匆匆那年我們 一時匆忙撂下 難以承受的諾言 只有等別人兌現
這種方式的缺點與read()方法是一樣的,都是會消耗大量的內存空間。
with open('song.txt', 'r', encoding='utf-8', newline='') as f: for line in f: print(line)
輸出結果:
匆匆那年我們 究竟說了幾遍 再見之后再拖延 可惜誰有沒有 愛過不是一場 七情上面的雄辯 匆匆那年我們 一時匆忙撂下 難以承受的諾言 只有等別人兌現
另外,發現上面的輸出結果中行與行之間多了一個空行。這是因為文件每一行的默認都有換行符,而print()方法也會輸出換行,因此就多了一個空行。去掉空行也比較簡單:可以用line.rstrip()
去除字符串右邊的換行符,也可以通過print(line, end='')避免print方法造成的換行。
file類的其他方法:
方法 | 描述 |
---|---|
flush() | 刷新緩沖區數據,將緩沖區中的數據立刻寫入文件 |
next() | 返回文件下一行,這個方法也是file對象實例可以被當做迭代器使用的原因 |
truncate([size]) | 截取文件中指定字節數的內容,并覆蓋保存到文件中,如果不指定size參數則文件將被清空; Python2無返回值,Python3返回新文件的內容字節數 |
write(str) | 將字符串寫入文件,沒有返回值 |
writelines(sequence) | 向文件寫入一個字符串或一個字符串列表,如果字符串列表中的元素需要換行要自己加入換行符 |
fileno() | 返回一個整型的文件描述符,可以用于一些底層IO操作上(如,os模塊的read方法) |
isatty() | 判斷文件是否被連接到一個虛擬終端,是則返回True,否則返回False |
前面已經寫過一篇介紹Python中字符編碼的相關文件<<再談Python中的字符串與字符編碼>> 里面花了很大的篇幅介紹Python中字符串與字符編碼的關系以及轉換過程。其中談到過兩個指定的字符編碼的地方,及其作用:
PyCharm等IDE開發工具指定的項目工程和文件的字符編碼: 它的主要作用是告訴Pycharm等IDE開發工具保存文件時應該將字符轉換為怎樣的字節表示形式,以及打開并展示文件內容時應該以什么字符編碼將字節碼轉換為人類可識別的字符。
Python源代碼文件頭部指定的字符編碼,如*-* coding:utf-8 -*-
: 它的主要作用是告訴Python解釋器當前python代碼文件保存時所使用的字符編碼,Python解釋器在執行代碼之前,需要先從磁盤讀取該代碼文件中的字節然后通過這里指定的字符編碼將其解碼為unicode字符。Python解釋器執行Python代碼的過程與IDE開發工具是沒有什么關聯性的。
那么這里為什么又要談起字符編碼的問題呢?
或者換個問法,既然從上面已經指定了字符編碼,為什么對文件進行讀寫時還要指定字符編碼呢?從前面的描述可以看出:上面兩個地方指定的是Python代碼文件的字符編碼,是給Python解釋器和Pycharm等程序軟件用的;而被讀寫文件的字符編碼與Python代碼文件的字符編碼沒有必然聯系,讀寫文件時指定的字符編碼是給我們寫的程序軟件用的。這是不同的主體和過程,希望我說明白了。
讀寫文件時怎樣指定字符編碼呢?
上面解釋了讀寫文件為什么要指定字符編碼,這里要說下怎樣指定字符編碼(其實這里主要討論是讀取外部數據時的情形)。這個問題其實在上面的文件讀取示例中已經使用過了,這里我們再詳細的說一下。
首先,再次看一下Python2和Python3中open函數的定義:
# Python2open(name[, mode[, buffering]])# Python3open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
可以看到,Python3的open函數中多了幾個參數,其中包括一個encoding參數。是的,這個encoding就是用來指定被操作文件的字符編碼的。
# 讀操作with open('song.txt', 'r', encoding='utf-8') as f: print(f.read())# 寫操作with open('song.txt', 'w', encoding='utf-8') as f: print(f.write('你好'))
那么Python2中怎樣指定呢?Python2中的對文件的read和write操作都是字節,也就說Python2中文件的read相關方法讀取的是字節串(如果包含中文字符,會發現len()方法的結果不等于讀取到的字符個數,而是字節數)。如果我們要得到 正確的字符串,需要手動將讀取到的結果decode(解碼)為字符串;相反,要以特定的字符編碼保存要寫入的數據時,需要手動encode(編碼)為字節串。這個encode()和decode()函數可以接收一個字符編碼參數。Python3中read和write操作的都是字符串,實際上是Python解釋器幫我們自動完成了寫入時的encode(編碼)和讀取時的decode(解碼)操作,因此我們只需要在打開文件(open函數)時指定字符編碼就可以了。
# 讀操作with open('song.txt', 'r') as f: print(f.read().decode('utf-8')) # 寫操作with open('song2.txt', 'w') as f: # f.write(u'你好'.encode('utf-8')) # f.write('你好'.decode('utf-8').encode('utf-8')) f.write('你好')
Python3中open函數的encoding參數顯然是可以不指定的,這時候就會用一個“默認字符編碼”。
看下Python3中open函數文檔對encoding參數的說明:
encoding is the name of the encoding used to decode or encode thefile. This should only be used in text mode. The default encoding isplatform dependent, but any encoding supported by Python can be passed. See the codecs module for the list of supported encodings.
也就是說,encoding參數的默認值是與平臺有關的,比如Window上默認字符編碼為GBK,Linux上默認字符編碼為UTF-8。
而對于Python2來說,在進行文件寫操作時,字節會被直接保存;在進行文件讀操作時,如果不手動進行來decode操作自然也就用不著默認字符編碼了。但是這時候在不同的字符終端打印的時候,會用當前平臺的字符編碼自動將字節解碼為字符,此時可能會出現亂碼。如song.txt文件時UTF-8編碼的,在windows(字符編碼為GBK)的命令行終端進行如下操作就會出現亂碼:
>>> with open('song.txt', 'r') as f: ... print(f.read()) ... 鍖嗗寙閭e勾鎴戜滑 絀剁珶璇翠簡鍑犻亶 鍐嶈涔嬪悗鍐嶆嫋寤? 鍙儨璋佹湁娌℃湁 鐖辮繃涓嶆槸涓€鍦?涓冩儏涓婇潰鐨勯泟杈? 鍖嗗寙閭e勾鎴戜滑 涓€鏃跺寙蹇欐拏涓?闅句互鎵垮彈鐨勮璦€ 鍙湁絳夊埆浜哄厬鐜
我們應該盡可能的獲取被操作文件的字符編碼,并明確指定encoding參數的值。
感謝各位的閱讀!關于python讀寫文件操作的方法就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。