打開文件
語法格式
打開文件使用函數(shù)open。語法格式如下:
open(filename[,access_mode][,buffering][,encoding])
參數(shù)說明:
- filename變量:是一個(gè)包含要訪問的文件名稱的字符串值。必須。
- access_mode變量:指打開文件的模式,包括:只讀、寫入、讀寫等。非必須。默認(rèn)的文件訪問模式為只讀(r)
- buffering:緩存值大小,0代表不緩存,如果取負(fù)數(shù),代表使用系統(tǒng)默認(rèn)的緩存大小,如果取整數(shù),將使用該值作為緩存大小。
- encoding:指定打開的字符編碼
示例如下:
#! /usr/bin/python
# -*-coding:UTF-8-*-
path = 'h:/test.txt'
#需要確保該文件存在,否則會(huì)報(bào)異常
file = open(path)
print('文件名是:',file.name)
輸出:
文件名是: h:/test.txt
絕對路徑和相對路徑
| 概念 | 說明 |
|---|---|
| 絕對路徑 | 從根目錄開始,windows系統(tǒng)從盤符開始,linux系統(tǒng)從usr、home等根文件開始。指定是文件存在的真實(shí)路徑,通過資源管理器中輸入文件地址可以訪問到的路徑 |
| 相對路徑 | 相對于當(dāng)前程序文件所在目錄的路徑。比如當(dāng)前程序目錄的絕對路徑是:d:\python\pyspace,如果使用相對路徑,當(dāng)和程序目錄相同時(shí),可以使用符號單個(gè)點(diǎn).代替這個(gè)路徑值。還可以使用符號兩個(gè)點(diǎn)..代替上級目錄。 |
實(shí)例如下:
#使用相對路徑
#創(chuàng)建一個(gè)文件,如果存在就覆蓋,不存在則新建
import os
path = './testfile'
if not os.path.exists(path):
os.mkdir(path)
path = './testfile/test2.txt'
file = open(path,'w')
print('文件名是:',file.name)
輸出:
文件名是: ./testfile/test2.txt
文件模式
打開文件的模式:access_mode,詳見以下表格
| 模式 | 描述 |
|---|---|
| t | 文本模式 (默認(rèn))。 |
| x | 寫模式,新建一個(gè)文件,如果該文件已存在則會(huì)報(bào)錯(cuò)。 |
| b | 二進(jìn)制模式。 |
| + | 打開一個(gè)文件進(jìn)行更新(可讀可寫)。 |
| U | 通用換行模式(不推薦)。 |
| r | 以只讀方式打開文件。文件的指針將會(huì)放在文件的開頭。這是默認(rèn)模式。 |
| rb | 以二進(jìn)制格式打開一個(gè)文件用于只讀。文件指針將會(huì)放在文件的開頭。這是默認(rèn)模式。一般用于非文本文件如圖片等。 |
| r+ | 打開一個(gè)文件用于讀寫。文件指針將會(huì)放在文件的開頭。 |
| rb+ | 以二進(jìn)制格式打開一個(gè)文件用于讀寫。文件指針將會(huì)放在文件的開頭。一般用于非文本文件如圖片等。 |
| w | 打開一個(gè)文件只用于寫入。如果該文件已存在則打開文件,并從開頭開始編輯,即原有內(nèi)容會(huì)被刪除。如果該文件不存在,創(chuàng)建新文件。 |
| wb | 以二進(jìn)制格式打開一個(gè)文件只用于寫入。如果該文件已存在則打開文件,并從開頭開始編輯,即原有內(nèi)容會(huì)被刪除。如果該文件不存在,創(chuàng)建新文件。一般用于非文本文件如圖片等。 |
| w+ | 打開一個(gè)文件用于讀寫。如果該文件已存在則打開文件,并從開頭開始編輯,即原有內(nèi)容會(huì)被刪除。如果該文件不存在,創(chuàng)建新文件。 |
| wb+ | 以二進(jìn)制格式打開一個(gè)文件用于讀寫。如果該文件已存在則打開文件,并從開頭開始編輯,即原有內(nèi)容會(huì)被刪除。如果該文件不存在,創(chuàng)建新文件。一般用于非文本文件如圖片等。 |
| a | 打開一個(gè)文件用于追加。如果該文件已存在,文件指針將會(huì)放在文件的結(jié)尾。也就是說,新的內(nèi)容將會(huì)被寫入到已有內(nèi)容之后。如果該文件不存在,創(chuàng)建新文件進(jìn)行寫入。 |
| ab | 以二進(jìn)制格式打開一個(gè)文件用于追加。如果該文件已存在,文件指針將會(huì)放在文件的結(jié)尾。也就是說,新的內(nèi)容將會(huì)被寫入到已有內(nèi)容之后。如果該文件不存在,創(chuàng)建新文件進(jìn)行寫入。 |
| a+ | 打開一個(gè)文件用于讀寫。如果該文件已存在,文件指針將會(huì)放在文件的結(jié)尾。文件打開時(shí)會(huì)是追加模式。如果該文件不存在,創(chuàng)建新文件用于讀寫。 |
| ab+ | 以二進(jìn)制格式打開一個(gè)文件用于追加。如果該文件已存在,文件指針將會(huì)放在文件的結(jié)尾。如果該文件不存在,創(chuàng)建新文件用于讀寫。 |
下面的表格很好的總結(jié)了這幾種模式:
| 模式 | r | r+ | w | w+ | a | a+ |
|---|---|---|---|---|---|---|
| 讀 | ? | ? | ? | ? | ||
| 寫 | ? | ? | ? | ? | ? | |
| 創(chuàng)建 | ? | ? | ? | ? | ||
| 覆蓋 | ? | ? | ||||
| 指針在開始 | ? | ? | ? | ? | ||
| 指針在結(jié)尾 | ? | ? |
說明:
- 使用open函數(shù)時(shí),不指定參數(shù)和指定參數(shù)為讀模式的效果是一樣的,因?yàn)閍ccess_mode的默認(rèn)值就是讀。
- +參數(shù)可以用在任意模式中,代表讀和寫都是允許的。
- 當(dāng)參數(shù)帶上字母b時(shí),表示已二進(jìn)制的方式操作文件。如果不指定,則只能處理文本文件。
IO編程
IO在計(jì)算機(jī)中指Input/Output,也就是輸入和輸出。由于程序和運(yùn)行時(shí)數(shù)據(jù)是在內(nèi)存中駐留,由CPU這個(gè)超快的計(jì)算核心來執(zhí)行,涉及到數(shù)據(jù)交換的地方,通常是磁盤、網(wǎng)絡(luò)等,就需要IO接口。
比如你打開瀏覽器,訪問新浪首頁,瀏覽器這個(gè)程序就需要通過網(wǎng)絡(luò)IO獲取新浪的網(wǎng)頁。瀏覽器首先會(huì)發(fā)送數(shù)據(jù)給新浪服務(wù)器,告訴它我想要首頁的HTML,這個(gè)動(dòng)作是往外發(fā)數(shù)據(jù),叫Output,隨后新浪服務(wù)器把網(wǎng)頁發(fā)過來,這個(gè)動(dòng)作是從外面接收數(shù)據(jù),叫Input。所以,通常,程序完成IO操作會(huì)有Input和Output兩個(gè)數(shù)據(jù)流。當(dāng)然也有只用一個(gè)的情況,比如,從磁盤讀取文件到內(nèi)存,就只有Input操作,反過來,把數(shù)據(jù)寫到磁盤文件里,就只是一個(gè)Output操作。
IO編程中,Stream(流)是一個(gè)很重要的概念,可以把流想象成一個(gè)水管,數(shù)據(jù)就是水管里的水,但是只能單向流動(dòng)。Input Stream就是數(shù)據(jù)從外面(磁盤、網(wǎng)絡(luò))流進(jìn)內(nèi)存,Output Stream就是數(shù)據(jù)從內(nèi)存流到外面去。對于瀏覽網(wǎng)頁來說,瀏覽器和新浪服務(wù)器之間至少需要建立兩根水管,才可以既能發(fā)數(shù)據(jù),又能收數(shù)據(jù)。
由于CPU和內(nèi)存的速度遠(yuǎn)遠(yuǎn)高于外設(shè)的速度,所以,在IO編程中,就存在速度嚴(yán)重不匹配的問題。舉個(gè)例子來說,比如要把100M的數(shù)據(jù)寫入磁盤,CPU輸出100M的數(shù)據(jù)只需要0.01秒,可是磁盤要接收這100M數(shù)據(jù)可能需要10秒,怎么辦呢?有兩種辦法:
第一種是CPU等著,也就是程序暫停執(zhí)行后續(xù)代碼,等100M的數(shù)據(jù)在10秒后寫入磁盤,再接著往下執(zhí)行,這種模式稱為同步IO;
另一種方法是CPU不等待,只是告訴磁盤,“您老慢慢寫,不著急,我接著干別的事去了”,于是,后續(xù)代碼可以立刻接著執(zhí)行,這種模式稱為異步IO。
同步和異步的區(qū)別就在于是否等待IO執(zhí)行的結(jié)果。好比你去麥當(dāng)勞點(diǎn)餐,你說“來個(gè)漢堡”,服務(wù)員告訴你,對不起,漢堡要現(xiàn)做,需要等5分鐘,于是你站在收銀臺前面等了5分鐘,拿到漢堡再去逛商場,這是同步IO。
你說“來個(gè)漢堡”,服務(wù)員告訴你,漢堡需要等5分鐘,你可以先去逛商場,等做好了,我們再通知你,這樣你可以立刻去干別的事情(逛商場),這是異步IO。
很明顯,使用異步IO來編寫程序性能會(huì)遠(yuǎn)遠(yuǎn)高于同步IO,但是異步IO的缺點(diǎn)是編程模型復(fù)雜。想想看,你得知道什么時(shí)候通知你“漢堡做好了”,而通知你的方法也各不相同。如果是服務(wù)員跑過來找到你,這是回調(diào)模式,如果服務(wù)員發(fā)短信通知你,你就得不停地檢查手機(jī),這是輪詢模式。總之,異步IO的復(fù)雜度遠(yuǎn)遠(yuǎn)高于同步IO。
操作IO的能力都是由操作系統(tǒng)提供的,每一種編程語言都會(huì)把操作系統(tǒng)提供的低級C接口封裝起來方便使用,Python也不例外。我們后面會(huì)詳細(xì)討論P(yáng)ython的IO編程接口。
基本文件方法
讀和寫
read()方法從一個(gè)打開的文件中讀取字符串。需要注意的是,Python字符串可以是二進(jìn)制數(shù)據(jù),而不僅僅是文本。語法:
file.read([count])
其中count參數(shù)是可選的,它代表讀取的文本內(nèi)容的字節(jié)長度。如果沒有傳入count,就會(huì)嘗試盡可能多的讀取文件,直到文件結(jié)束。
write()方法不會(huì)在字符串結(jié)尾添加換行符“\n”,語法格式如下:
file.write(string)
string是要向文件中寫入的內(nèi)容,可以是文本,也可以是二進(jìn)制數(shù)據(jù),該方法返回寫入文件字符串的長度。
示例:
#read and write
_path = './testfile/test.txt'
_file = open(_path,'w+')
#在文件中寫入:“hello world!welcome”
_file.write("hello world!welcome")
#注意:對于新寫入文件的內(nèi)容,只有在重新打開后才可以讀取到
_file = open(_path,'r')
print('file.read(12)=',_file.read(12))
#追加寫入
_file = open(_path,'a')
_file.write('!I am coming!')
_file = open(_path)
print('_file.read()=',_file.read())
#添加換行符,增加換行
_file = open(_path,'a')
_file.write('\nyou am coming too!')
_file = open(_path)
print('_file.read()=',_file.read())
輸出:
file.read(12)= hello world!
_file.read()= hello world!welcome!I am coming!
_file.read()= hello world!welcome!I am coming!
you am coming too!
使用讀和寫操作,實(shí)現(xiàn)copy文件的例子:
# copy文件
def copyFile(source, target):
file = open(source, 'rb')
file2 = open(target, 'wb')
# file2 = open(target, 'ab')
_len = file2.write(file.read())
print('寫入大?。?, _len)
path = "h:/1.mp4"
path2 = "h:/test.mp4"
opyFile(path,path2)
提示:如果要讀和寫特定的編碼格式的文件,需要在打開文件時(shí)指定編碼格式,例如:
file.open("./test.txt",'r',encoding='UTF-8')
讀寫行
python為我們提供了readline()、readlines()和writelines()等方法用于行操作。示例如下:
file = open("./testfile/test.txt","w")
file.write('hello world\n')
file = open("./testfile/test.txt","a")
file.write('welcome')
file = open('./testfile/test.txt','r')
print('file.readline()',file.readline())
輸出:
file.readline()= hello world
從結(jié)果中可知,readline()方法會(huì)從文件中讀取一行。readline()方法也可以像read方法一樣傳入數(shù)值,讀取對應(yīng)的字符數(shù),傳入小于0的數(shù)值表示整行都輸出。
如果將最后一行的方法改為使用readlines()方法:
# print('file.readline()=',file.readline())
print('file.readlines()=',file.readlines())
輸出:
file.readlines()= ['hello world\n', 'welcome']
writelines()方法的使用,示例如下:
file = open("./testfile/test.txt","w")
str_list = ['hello world\n','welcome!']
file.writelines(str_list)
file = open('./testfile/test.txt','r')
print(file.read())
file = open('./testfile/test.txt','r')
print(file.readlines())
輸出:
hello world
welcome!
['hello world\n', 'welcome!']
writelines方法和readlines方法相反,傳給它一個(gè)字符串列表(任何序列或者可迭代對象),它會(huì)把所有字符串寫入文件。
關(guān)閉文件
一般來講,一個(gè)文件對象在退出文件后會(huì)自動(dòng)關(guān)閉,但是由于在打開文件對的過程中,可能會(huì)出現(xiàn)各種異常,問了安全起見,可以使用close()方法可以顯示的關(guān)閉文件。一般的做法為把close語句放在try,catch的finnally中。實(shí)例如下:
try:
file = open("./testfile/test.txt","r")
except Exception as e:
print(e)
finally:
if file:
file.close()
在Python中,使用with語句可以自動(dòng)調(diào)用close方法。將上面的示例改寫為:
with open("./testfile/test.txt","w") as file_123:
file_123.writelines(['hello world\n', 'welcome'])
文件重命名
Python的os模塊為我們提供了rename()方法,即文件重命名。使用這個(gè)方法需要導(dǎo)入os模塊。rename()方法的語法格式如下:
os.rename(current_file_name,new_file_name)
示例如下:
# file = open("./test.txt","w")
# file.close()
# 上下兩種方法都可以
open("./test.txt","w")
import os
os.rename('test.txt','test123.txt')
刪除文件
Python的os模塊為我們提供了remove()方法,即刪除文件。語法格式如下:
os.remove(file_name)
如果文件不存在于當(dāng)前目錄下,則文件名需要使用絕對路徑。該方法沒有返回值。實(shí)例如下:
try:
os.remove('./test123.txt')
except Exception:
print('文件不存在!')
對文件內(nèi)容進(jìn)行迭代
按字節(jié)處理
#按字節(jié)處理
file = open('./testfile/test.txt')
while True:
c_str = file.read(1)
print(c_str)
if not c_str:
break
打開一個(gè)文件,然后每次只讀取1個(gè)字節(jié),一直到讀取結(jié)束為止
按行處理
#按行處理
file = open('./test.txt',encoding='UTF-8')
while True:
c_str = file.readline()
print(c_str)
if not c_str:
break
打開一個(gè)文件,每次只讀取一行,一直到讀取結(jié)束為止
使用fileinput實(shí)現(xiàn)懶(延遲)加載
前面介紹的方法(read、readline),在不帶參數(shù)時(shí)(不指定讀取的長度)都將文件中的所有內(nèi)容讀取到了內(nèi)存。這種方式在讀取大文件上時(shí),非常消耗內(nèi)存,容易造成內(nèi)存溢出。因此,考慮使用while循環(huán)和readline方法替代。
在Python中,按行讀取文件時(shí),若能使用for循環(huán),則稱之為懶(延遲)加載,因?yàn)樵诓僮鬟^程中只讀取實(shí)際需要的部分。實(shí)例如下:
path = './test.txt'
import fileinput
for line in fileinput.input(path,openhook=fileinput.hook_encoded('UTF-8')):
print(line)
文件迭代器
文件對象是可迭代的,因此可以在for循環(huán)使用文件對象,從而進(jìn)行迭代。示例如下:
file = open('./test.txt',encoding='UTF-8')
for line in file:
print(line)
file.close()
StringIO函數(shù)
數(shù)據(jù)的讀取除了通過文件,還可以在內(nèi)存中進(jìn)行。Python中的io模塊提供了對str操作的StringIO函數(shù)。要把str寫入StringIO,我們需要?jiǎng)?chuàng)建一個(gè)StringIO,然后像文件一樣寫入。示例如下:
from io import StringIO
strio = StringIO()
strio.write('hello world')
print(strio.getvalue())
輸出如下:
hello world
要讀取StringIO,還可以用str初始化StringIO,然后像讀文件一樣讀取。示例如下:
strio = StringIO('hello\nworld\nI\'m coming')
while True:
line = strio.readline()
if line:
print(line.strip())
else:
break
輸出:
hello
world
I'm coming
序列化和反序列化
序列化——所有變量都在內(nèi)存中,把變量從內(nèi)存中變成可以存儲或可傳輸?shù)倪^程叫做序列化。序列化是數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成二進(jìn)制串的過程。
反序列化——反過來,把變量內(nèi)容從序列化后的對象重新讀取到內(nèi)存中的過程叫做反序列化。反序列化是將二進(jìn)制串轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)或?qū)ο蟮倪^程。
一般序列化和反序列化
Python的pickle模塊實(shí)現(xiàn)了基本數(shù)據(jù)序列和反序列化。
pickle.dumps()方法把任意對象序列轉(zhuǎn)化成一個(gè)bytes,然后把這個(gè)bytes寫入文件。也可以使用另一種方法,pickle.dump(),直接把對象序列化后寫入一個(gè)文件對象中。
既然已經(jīng)將內(nèi)容序列化到文件中了,使用文件時(shí)就需要把對象從磁盤讀到內(nèi)存中。可以先把內(nèi)容讀到一個(gè)bytes中,然后用pickle.loads方法反序列化對象;也可以直接使用pickle.load()方法從一個(gè)文件對象中直接反序列化對象。示例如下:
import pickle
str = 'hello\nworld\nwelcome'
print(pickle.dumps(str))
fileName = open('dump.txt','wb')
#以下兩種寫法都正確
#寫法1
# fileName.write(pickle.dumps(str))
#寫法2
pickle.dump(str,fileName)
fileName.close()
fileName = open('dump.txt','rb')
# 輸出為byte
# print(fileName.read())
# UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte
# print(fileName.read().decode('utf-8'))
# 正確寫法
print(pickle.load(fileName))
輸出:
b'\x80\x03X\x13\x00\x00\x00hello\nworld\nwelcomeq\x00.'
hello
world
welcome
JSON序列化與反序列化
Python3中可以使用json模塊對json數(shù)據(jù)進(jìn)行編碼(序列化)和解碼(反序列化)。包含以下兩個(gè)函數(shù)。
- json.dumps():對數(shù)據(jù)進(jìn)行編碼(序列化)
- json.loads()對數(shù)據(jù)進(jìn)行解碼(反序列化)
Python編碼與json類型映射關(guān)系:
| Python | JSON |
|---|---|
| dict | {} |
| list,tuple | [] |
| str | string |
| Int or Float | number |
| True/False | True/False |
| None | null |
示例代碼:
#Json序列化實(shí)例
import json
data = {'name':'小明','age':23,'sex':'男'}
#如果不加ensure_ascii=False 返回的json數(shù)據(jù)中文顯示有問題,
#變成\uXXX的形式。這是因?yàn)橹形囊評nicode 編碼了,
#而默認(rèn)是以ASCII解析的,中文不在ASCII編碼中,所以無法顯示
str = json.dumps(data,ensure_ascii=False)
print('json str = ',str)
print('python dict = ',data)
data2 = json.loads(str)
print('covert python dict = ',data)
輸出:
json str = {"name": "小明", "sex": "男", "age": 23}
python dict = {'name': '小明', 'sex': '男', 'age': 23}
covert python dict = {'name': '小明', 'sex': '男', 'age': 23}
如果要處理的是文件而不是字符串,就可以使用json.dump(比上面的方法少個(gè)s)和json.load編碼、解碼JSON數(shù)據(jù)。示例如下:
with open('dump.txt','w') as f:
json.dump(data,f)
with open('dump.txt') as f:
data = json.load(f)
print(data)
輸出:
{'sex': '男', 'age': 23, 'name': '小明'}
調(diào)試
當(dāng)我們讀取和寫入文件時(shí),經(jīng)常遇到和空白符相關(guān)的問題。如:
str = '1 2\t 3\n 4'
print(str)
輸出:
1 2 3
4
在這種情況下,Python為我們提供了repr函數(shù),該函數(shù)可接受任何對象作為參數(shù),并返回對象的字符串表示形式。如:
str = repr('1 2\t 3\n 4')
print(str)
輸出:
'1 2\t 3\n 4'