Python 實用程式設計技巧(檔案篇)
這個問題看似簡單,但是由於py2 和 py3 的巨大差異導致我們依然要深入研究一下。
那麼 python2 和 python3 的什麼區別導致了這種差異的發生呢?
答:python2 和 python3 之間字串的語意發生了變化
python2 有兩種字串的型別:
(1)str
表面上看是字串,但是實際上是抽象的一串連續的位元組(字串中每個字元都是一個位元組)
(2)unicode
這個型別是在更多不同的語言加入以後不得不進行調整的,不再用一個位元組表示一個字元,但是,我們進行檔案操作的時候是不支援直接將多個位元組一下子儲存進去的,我們必須將其轉化成某種單個位元組的編碼再進行儲存操作
示例程式碼:
將文字編碼:
In [1]: a = u'你好世界' In [2]: a.encode('utf8') Out[2]: '\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c' In [3]: a.encode('gbk') Out[3]: '\xc4\xe3\xba\xc3\xca\xc0\xbd\xe7'
可以看到 utf-8 使用的位元組比Gbk多
將文字解碼:
注意:這裡編碼和解碼一定要對應,否則會出現亂碼。
程式碼示例:
In [5]: '\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c'.decode('gbk') Out[5]: u'\u6d63\u72b2\u30bd\u6d93\u682b\u666b' In [6]: print u'\u6d63\u72b2\u30bd\u6d93\u682b\u666b' 浣犲ソ涓栫晫
程式碼示例:
In [8]: '\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c'.decode('utf8') Out[8]: u'\u4f60\u597d\u4e16\u754c' In [9]: print u'\u4f60\u597d\u4e16\u754c' 你好世界
1.python2 的文字讀寫
文字就是Unicode 字串,我們寫入檔案前先要對其選擇一個方式進行編碼,我們讀取檔案的時候也要對其進行先解碼,將其換原成 unicode 。
文字的寫入:
f = open('test.txt','w') s = u'你好世界' f.write(s.encode('utf8'))
文字的讀出:
In [8]: f = open('test.txt','r') In [9]: t = f.read() In [10]: t Out[10]: '\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c'
可以看到這樣讀出來的都是位元組碼
我們必須要進行轉化:
In [11]: t.decode('utf8') Out[11]: u'\u4f60\u597d\u4e16\u754c' In [12]: print u'\u4f60\u597d\u4e16\u754c' 你好世界
2.python3 的文字讀寫
python3 對字串的支援更加清晰合理,unicode 就是 python3 中的 str 而 str 就是python3 中的 byte
就比如說,python2 中字串前面加一個u 才代表 unicode 字串但是 python3 什麼都不用加直接就是,反倒是如果想成為一個 byte 字串要加 b
python3 的讀寫可以指定一個引數 encoding = ‘’ 自動的進行編解碼
文字的寫入:
f = open('test.txt','w',encoding = 'utf8') f.write("國慶快樂")
文字的讀出:
f = open('test.txt','r',encoding='utf8') t = f.read() print (t)
二、如何處理二進位制檔案
wav 是音訊問價,前44位元組是檔案的頭資訊,我們可以嘗試將其讀出來
In [6]: f = open('test.wav','rb') In [7]: info = f.read(44) In [8]: info Out[8]: 'RIFFt\x9b\x0b\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00\x11+\x00\x00D\xac\x00\x00\x04\x00\x10\x00dataP\x9b\x0b\x00'
那麼我們如何將其解析出來呢?
我們可以使用 struct 這個庫中的unpack 方法
舉個例子:
h 是一個 short 的意思
import struct print struct.unpack('h','\x01\x02')//小端序 print '\n' print struct.unpack('>h','\x01\x02')//大端序 (513,)//--------->2x256+1 (258,)//--------->1x256+2
比如我們的音訊檔案的 22-24 位元組是聲道數,於是我們就能使用這個函式將其解析出來
程式碼示例:
import struct f = open('test.wav','rb') info = f.read(44) print struct.unpack('h',info[22:24])
結果:
(2,)
說明這個音訊檔案是雙聲道
再比如我們想讀音訊檔案的取樣頻率,這個東西是24-28 4個位元組
程式碼示例:
import struct f = open('test.wav','rb') info = f.read(44) print struct.unpack('i',info[24:28])//因為這裡是4個位元組所以我們就是用的i
結果:
(11025,)
綜合實驗:
我們現在想讀入音訊檔案的資料部分,並且不能以字串的形式,因為字串的形式沒法進行輸入操作,我們可以將其以每兩個位元組為一組讀入一個buf
我們首先看一下我們的音訊的資料有多大:
import array f = open('test.wav', 'rb') f.seek(0, 2)# 移動指標的位置 t = f.tell()# 返回指標的位 a = (t-44)/2# 每兩個一組進行分組 print a
a就是最終的組數
然後我們就建立指定型別大小的Buf,我們把buf初始化,並將44位元組以後的輸入寫入buf
buf = array.array('h',(0 for _ in xrange(a))) f.seek(44) f.readinto(buf)
接下來我們對 buf 進行操作,將操作完的結果重新寫成另一個檔案
info = f.read(44)
for i in xrange(a):
buf[i]/=8
f2 = open('demo.wav','wb') f2.write(info) buf.tofile(f2) f2.close()
以上步驟,我們其實已經將我們的音訊聲音變成了原來的1/8
程式碼示例:
# coding=utf-8 import struct import array f = open('test.wav', 'rb') info = f.read(44) f.seek(0, 2)# 移動指標的位置 t = f.tell()# 返回指標的位置 a = (t-44)/2 buf = array.array('h',(0 for _ in xrange(a))) f.seek(44) f.readinto(buf) for i in xrange(a): buf[i]/=8 f2 = open('demo.wav','wb') f2.write(info) buf.tofile(f2) f2.close()
三、如何設定檔案的緩衝
將檔案寫入硬體裝置的時候是按照塊進行寫入的,每個塊有固定的大小,也就是說,進行一次寫入操作的時間是固定的,不管你有沒有把塊填滿,因此如果沒有緩衝,就會在塊不滿的情況下進行寫入,導致次數過多時間過長,於是我們需要設定檔案緩衝。
緩衝又分為全緩衝和行緩衝,一般檔案的緩衝就是全緩衝,意思就是填滿一個就釋放一個,而行緩衝就是遇到換行符就釋放。
當然並不是所有的裝置都需要緩衝的,串列埠裝置我們就希望能即時傳送,於是我們不設定緩衝區
1.檢驗緩衝區大小實驗:
ipython :
In [13]: f = open(‘demo.txt’,’w’)
In [14]: f.write(‘abc’)
In [15]: f.write(‘+’*4093)
In [16]: f.write(‘-‘)
另一個終端:
$ tail -f demo.txt
14、15 命令執行以後 tail 都沒反應,直到16命令執行,在tail 後面輸出了 abc和 4093個*,可見緩衝區的大小是4096位元組
2.修改預設緩衝區的大小
(1)全緩衝:
open 函式 有一個 buffering 的選項可以設定緩衝區大小,我們設定為大於一的整數
f = open('demo.txt','w',buffering = 2048)
(2)行緩衝:
將 buffering 設定為1
f = open('demo.txt','w',buffering = 1)
(3)無緩衝:
將 buffering 設定為0
f = open('demo.txt','w',buffering = 0)
四、如何將檔案對映到記憶體
需求:
1.在訪問二進位制檔案的時候,希望把檔案對映到記憶體空間,實現隨機訪問
2.嵌入式裝置中,暫存器被編址到記憶體地址空間,我們可以對映 /dev/mem 的某些範圍,來操縱這些暫存器
3.如果多個程序對映同一個檔案,還能實現程序通訊
使用 mmap.mmap()
通過下面這條命令建立一個1M的全0檔案:
dd if=/dev/zero of=demo.bin bs=1024 count=1024
使用 od -x 檢視,發現的確是全0
od -x demo.bin
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
4000000
我們獲取檔案描述符
示例程式碼:
f = open('demo.bin','r+b') print f.fileno()
結果:
我們將整個檔案對映到變數m
m = mmap.mmap(num,0,mmap.ACCESS_WRITE) # 這裡的0表示的是全部檔案
我們看一下內容:
In [10]: m[100] Out[10]: '\x00'
修改:
In [11]: m[100] = '\x08'
檢視修改後的內容
In [12]: m[99:120] Out[12]: '\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
實際上在這種情況下檔案已經被修改。
我們還可以跳過整數倍的頁的大小對檔案進行修改(頁的概念來源於作業系統,如果不瞭解可以去查一下)
m = mmap.mmap(num,mmap.PAGESIZE*8,mmap.ACCESS_WRITE,offset = mmap.PAGESIZE*4)
五、如何訪問檔案的狀態
在某些專案中我們需要獲取檔案的狀態,
比如:
(1)檔案型別
(2)檔案許可權
(3)檔案最後修改時間
(4)檔案的大小
方法一:系統呼叫
1.os.stat()
2.os.lstat()
它與os.stat 的區別在於它不跟隨符號連結檔案,意思就是lstat 拿到的是快捷方式的屬性,而stat 拿到的是原始檔案的屬性
3.os.fstat()
這個函式和上面兩個函式的區別是這個函式傳入的引數是檔案描述符,而不是上面兩個引數傳入的路徑。
示例程式碼:
In [3]: import os In [4]: os.stat('demo.bin') Out[4]: posix.stat_result(st_mode=33188, st_ino=264254, st_dev=2049, st_nlink=1, st_uid=0, st_gid=0, st_size=1048576, st_atime=1538365252, st_mtime=1538364797, st_ctime=1538364797)
檢視檔案的型別:
檔案的型別就儲存在 st_mode裡面,我們需要將其解析
In [15]: import os In [16]: s = os.stat('demo.bin') In [17]: import stat In [18]: stat.S_ISDIR(s.st_mode) Out[18]: False
檢視檔案的許可權
In [19]: s.st_mode & stat.S_IRUSR Out[19]: 256
只要這個返回值大於0就說明有使用者讀的許可權
In [20]: s.st_mode & stat.S_IXUSR Out[20]: 0
可見使用者的執行許可權是0
檢視檔案的最後訪問時間:
In [25]: import time In [26]: time.localtime(s.st_atime) Out[26]: time.struct_time(tm_year=2018, tm_mon=10, tm_mday=1, tm_hour=11, tm_min=40, tm_sec=52, tm_wday=0, tm_yday=274, tm_isdst=0)
方法二:快捷函式
快捷函式在底層實現上是利用的是系統函式,但是會讓你操作起來更加方便
判斷檔案型別:
In [27]: os.path.isdir('demo.bin') Out[27]: False In [28]: os.path.isfile('demo.bin') Out[28]: True
判斷檔案最後訪問時間:
In [32]: time.localtime(os.path.getatime('demo.bin')) Out[32]: time.struct_time(tm_year=2018, tm_mon=10, tm_mday=1, tm_hour=11, tm_min=40, tm_sec=52, tm_wday=0, tm_yday=274, tm_isdst=0)
六、如何使用臨時檔案:
比如我們採集資料進行分析,但是我們只需要儲存結果,我們採集的資料如果常駐記憶體就會讓電腦崩潰,於是我們將這個資料放在臨時檔案中(外部儲存),在檔案關閉後將被刪除
示例程式碼:
In [15]: from tempfile import TemporaryFile,NamedTemporaryFile In [16]: f = TemporaryFile() In [17]: f.write('abc'*10000) In [18]: f.seek(0) In [19]: f.read(100) Out[19]: 'abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca'
這個臨時檔案在系統中是找不到的,如果我們想建立一個能在檔案系統中看到的臨時檔案,我們就用NamedTemporaryFile
示例程式碼:
我們能看到檔案路徑
In [20]: nte = NamedTemporaryFile() In [21]: nte.name Out[21]: 'c:\\users\\k0rz3n\\appdata\\local\\temp\\tmpob51yb'
如果我們重新建立一個檔案的話,原來的檔案就會被關閉,關閉以後臨時檔案就會被自動刪除,如果我們不想讓他刪除,我們在建立的時候就要設定一個引數
nte = NamedTemporaryFile(delete = False)