使用Python編程時,經常會遇到讀寫文件的操作。對於讀寫文件的各種模式(如閱讀、寫入、追加等)有時真的會迷惑人,以及搞不清open、read、readline、readlines、write、writelines等方法的使用也會把你繞的雲裡霧裡。
期望這篇文章能夠幫你更好的了解應該如何讀寫文件,並在最恰當的地方用最合適的方法。
在我們開始研究如何使用Python中的文件之前,了解文件究竟是什麼以及現代操作系統如何處理它們的某些方面是非常重要的。
從本質上講,文件是用於存儲數據的連續字節集。這些數據以特定格式組織,可以是任何像文本文件一樣簡單的數據,也可以像程序可執行文件一樣復雜。最後,這些字節文件被翻譯成二進制文件1,0以便計算機更容易處理。
大多數現代文件系統上的文件由三個主要部分組成:
1.標題(Header):有關文件內容的元數據(文件名,大小,類型等)
2. 數據(Data):由創建者或編輯者編寫的文件內容
3. 文件結束(EOF):表示文件結尾的特殊字符
數據表示的內容取決於所使用的格式規范,通常由擴展名表示。例如,擴展名為.gif的文件最可能符合圖形交換格式規范。有數百個(如果不是數千個)文件擴展名。
在操作系統上訪問文件時,需要文件路徑。文件路徑是表示文件位置的字符串。它分為三個主要部分:
(1)文件夾路徑:文件系統上的文件夾位置,後續文件夾由正斜槓/(Unix)或反斜槓\(Windows)分隔
(2)文件名:文件的實際名稱
(3)擴展名:文件路徑的末尾預先設置了句號(.),用於表示文件類型
Python文件路徑相關操作:
(1)使用getcwd()
可獲得當前工作目錄,即當前Python腳本工作的目錄路徑,類似Linux中pwd
命令,PS:如果想打印Windows中包含中文的文件名或路徑,需要使用“GBK”進行decode
(2)合並路徑使用path.join()
,Windows中的反斜槓與Linux中的正斜槓使用sep(反斜槓需要使用\轉義)
(3)相對路徑.
表示當前文件夾 ..
表示父文件夾
(4)相對路徑、絕對路徑轉換path.abspath(path)
:返回絕對路徑path.isabs(path)
:判斷是否是絕對路徑path.relpath(path,start)
:返回相對路徑
(5) 路徑分割path.dirname(path)
:返回文件所在目錄,os.path.basename(path)
:返回文件名path.split(path)
:將路徑按分隔符分割path.basename()
:獲取文件名path.splitext()
:分離擴展名path.getsize(filename)
:查看文件大小,只能統計文件,不同統計文件夾,如需統計文件夾需要自行遍歷。stat(file)
:獲取文件屬性
(6) 路徑分割path.exists()
:路徑是否存在path.isdir()
:是否為目錄path.isfile()
:是否是文件
(6) 返回指定目錄下的所有文件和目錄名:listdir()
(7) 目錄操作:copyfile('oldfile', 'newfile')
復制文件,oldfile和newfile都只能是文件copy('oldfile', 'newfile')
oldfile只能是文件夾,newfile可以是文件,也可以是目標目錄copytree('olddir', 'newdir')
復制文件夾,olddir和newdir都只能是目錄,且newdir必須不存在move('oldpos','newpos')
移動文件(目錄)rmdir('dir')
只能刪除空目錄rmtree('dir')
空目錄、有內容的目錄都可以刪
(7) 其他方法:os.remove(file)
刪除文件removedirs(r'c:\python ')
刪除目錄rename(old, new)
重命名makedirs(r'c:\python\test')
創建多級目錄mkdir('test')
創建單個目錄chmod(file)
修改文件權限與時間戳
處理文件數據時經常遇到的一個問題是新行或行結尾的表示。行結尾起源於莫爾斯電碼時代,使用一個特定的符號被用來表示傳輸的結束或一行的結尾。
Windows使用CR+LF字符表示新行,而Unix和較新的Mac版本僅使用LF字符。當你處理來源於不同操作系統上的文件時,這可能會導致一些復雜情況。這是一個簡單的例子。假設我們檢查在Windows系統上創建的文件如下:
Pug\r\n
Jack Russel Terrier\r\n
English Springer Spaniel\r\n
German Shepherd\r\n
Staffordshire Bull Terrier\r\n
Cavalier King Charles Spaniel\r\n
Golden Retriever\r\n
West Highland White Terrier\r\n
Boxer\r\n
Border Terrier\r\n
同樣的輸出將在Unix設備上以不同方式解釋
Pug\r
\n
Jack Russel Terrier\r
\n
English Springer Spaniel\r
\n
German Shepherd\r
\n
Staffordshire Bull Terrier\r
\n
Cavalier King Charles Spaniel\r
\n
Golden Retriever\r
\n
West Highland White Terrier\r
\n
Boxer\r
\n
Border Terrier\r
\n
解決方案:
with open('test.txt', 'r') as f:
for line in f.readlines():
line = line.strip('\n')
使用walk()
輸出總是先文件夾後文件名
import os
# 遍歷root_dir 下所有的文件夾和文件 返回的是一個三元組列表 , root,dir,file 根目錄,文件夾列表,文件列表
def echo_path(root_dir):
list_dirs = os.walk(root_dir)
for root, dirs, files in list_dirs:
for d in dirs:
print os.path.join(root, d)
for f in files:
print os.path.join(root, f)
使用listdir()
按照目錄樹結構以及按照首字母排序進行輸出的。
import os
def echo_path(root_dir):
for dir_list in os.listdir(root_dir):
path = os.path.join(root_dir, dir_list)
print(path)
if os.path.isdir(path):
echo_path(path)
glob模塊是最簡單的模塊之一,內容非常少。用它可以查找符合特定規則的文件路徑名。glob.glob()
返回所有匹配的文件路徑列表。它只有一個參數pathname,定義了文件路徑匹配規則,這裡可以是絕對路徑,也可以是相對路徑。示例:
import glob
#獲取指定目錄下的所有圖片
print(glob.glob(r"E:\Picture\*\*.jpg") )
#獲取上級目錄的所有.py文件
print(glob.glob(r'../*.py') #相對路徑)
glob.iglob()
獲取一個可遍歷對象,使用它可以逐個獲取匹配的文件路徑名。與glob.glob()的區別是:glob.glob同時獲取所有的匹配路徑,而 glob.iglob一次只獲取一個匹配路徑,即生成器。(生成器最明顯的優勢就是節省內存空間,即它不會一次性生成所有的數據,而是什麼時候需要,什麼時候生成。)
import glob
file = glob.iglob(r'../*.py')
print(file) #<generator object iglob at 0x00B9FF80>
for py in f:
print(py)
在Python中讀寫文件需要3個步驟:
(1)調用open函數,返回一個File對象
(2)調用File對象的read()或write()方法
(3)調用File對象的close() 方法,關閉該文件
文件常用打開模式:'r'
:只讀(缺省。如果文件不存在,則拋出錯誤)'w'
:只寫(如果文件不存在,則自動創建文件)'a'
:附加到文件末尾'r+'
:讀寫
如果需要以二進制方式打開文件,需要在mode後面加上字符”b”,比如”rb””wb”等
如果不用with語句,代碼如下:
file = open("/tmp/foo.txt")
data = file.read()
file.close()
這裡有兩個問題:
(1)可能忘記關閉文件句柄;
(2)文件讀取數據發生異常,沒有進行任何處理。with
除了有更優雅的語法,還可以很好的處理上下文環境產生的異常。
with版本的代碼:
with open("/tmp/foo.txt") as file:
data = file.read()
with的工作流程:
緊跟with後面的語句被求值後,返回對象的 enter() 方法被調用,這個方法的返回值將被賦值給as後面的變量。
當with後面的代碼塊全部被執行完之後,將調用前面返回對象的 exit()方法。
有時你可能想要讀取文件並同時寫入另一個文件。如果你使用在學習如何寫入文件時顯示的示例,它實際上可以合並到以下內容中:
d_path = 'dog_breeds.txt'
d_r_path = 'dog_breeds_reversed.txt'
with open(d_path, 'r') as reader, open(d_r_path, 'w') as writer:
dog_breeds = reader.readlines()
writer.writelines(reversed(dog_breeds))
read()
是最簡單的一種方法,一次性讀取文件的所有內容放在一個大字符串中,即存在內存中。
f = open('test.txt')
try:
file_context = file_object.read()
file_context = open(file).read().splitlines() // file_context是一個list,每行文本內容是list中的一個元素
finally:
file_object.close()
read()的優點:方便、簡單,一次性獨讀出文件放在一個大字符串中,速度最快
read()的缺點:文件過大的時候,占用內存會過大
readline()
是逐行讀取文本,結果是一個list
with open(file) as f:
line = f.readline()
while line:
print(line)
readline()的優點:占用內存小,逐行讀取
readline()的缺點:由於是逐行讀取,速度比較慢readlines()
一次性讀取文本的所有內容,結果是一個list
with open(file) as f:
for line in f.readlines():
print(line)
這種方法讀取的文本內容,每行文本末尾都會帶一個’\n’換行符 (可以使用L.rstrip(‘\n’)去掉換行符)
readlines()的優點:一次性讀取文本內容,速度比較快
readlines()的缺點:隨著文本的增大,占用內存會越來越多
write()
傳入的是字符串
lines = ['line1', 'line2']
with open('filename.txt', 'w') as f:
f.write('\n'.join(lines))
writelines()
傳入的數一個數組
lines = ['line1', 'line2']
with open('filename.txt', 'w') as f:
f.writelines("%s\n" % l for l in lines)