Python檔案處理-專題筆記

天涯明月笙發表於2018-01-07

任何語言都離不開對檔案的操作,Python語言是如何來操作和管理檔案的。掌握檔案的基本概念、基本操作,瞭解檔案屬性、 linux 系統的檔案管理機制及os模組對檔案和目錄的處理等相關內容。

  • [√]慕課網Meshare_huang老師:Python檔案處理

檔案簡介

Python的檔案處理:

初學者:推薦使用虛擬機器VMwareStation + Deepin

必備前置條件知識儲備:

  1. Linux系統知識
  2. Linux下Python開發環境
  3. 掌握Python基礎知識

Python檔案概念:

檔案: Python中檔案是物件依賴我們的具體系統。

Linux檔案:一切裝置都可以看成檔案

  • 例如:磁碟檔案,管道,網路Socket,外設等。

檔案屬性:使用者執行許可權

在Linux控制檯輸入命令:

#cd進一個空目錄
cd Desktop/ 
#建立一個資料夾
mkdir PythonFile 
cd PythonFile 
touch hello.py
vim hello.py
mark

使用i進入insert模式進行插入程式碼。
插入完成後使用esc退出編輯模式,輸入:wq進行儲存。

mark
mark

通過ls -l命令可以看到檔案的一些基本屬性:

  • 第一個橫槓代表是一個檔案,還是一個目錄。-代表是一個檔案,d代表是一個目錄
    如下圖1為一個目錄。
    mark
mark
  • 第一組rw-代表當前使用者對於當前檔案有沒有執行許可權。(-代表沒有許可權,x為可執行)

  • 第二組r--:表示當前使用者組對該檔案有讀r 沒有寫w的許可權,沒有執行許可權x

  • 第三組r--:表示其他使用者對該檔案有讀r許可權,但沒有寫w執行x許可權

  • 第一個mtianyan代表檔案的擁有者mtianyan

  • 第二個mtianyan 代表這個檔案所屬的使用者組mtianyan

  • 21代表檔案的大小是21位元組 後面的時間是檔案的建立時間。後面是檔名。

mark

可以看到我們最為當前使用者現在沒有該檔案執行許可權。

使用chmod為檔案增加許可權說明:

  • 如果給所有人新增可執行許可權:chmod a+x 檔名
  • 如果給檔案所有者新增可執行許可權:chmod u+x 檔名
  • 如果給所在組新增可執行許可權:chmod g+x 檔名
  • 如果給所在組以外的人新增可執行許可權:chmod o+x 檔名
mark

可以看到我們通過chmod +x hello.py為當前使用者使用者組其他使用者都新增了執行許可權

Linux下首行一定要加這句話:!/usr/bin/Python!/usr/bin/Python是告訴作業系統執行這個指令碼的時候,呼叫/usr/bin下的python直譯器。

mark

列印出了Helloworld好像有點問題。以後對Linux熟了再回來看。

mark

python命令是可以正常執行的。

檔案基礎操作

主要講解檔案開啟及檔案不同的開啟方式、使用read,readlines迭代器訪問檔案、檔案寫入方式及寫快取處理、檔案關閉及由不關閉導致的問題、檔案指標及檔案指標操作

檔案開啟方式

open()

open(name [,mode[buf]])
  • name : 檔案路徑
  • mode : 開啟方式
  • buf : 緩衝buffering大小

mode方式有:只讀只寫讀寫

read()

read([size]) 

讀取檔案(讀取size個位元組,預設讀取全部)

  • 如果檔案長度大於size,我們就讀size個長度。
  • 如果檔案長度小於size,我們就把檔案讀完。
  • 如果沒有設定size,預設全部讀完。

readline()

readline([size])

讀取一行

readlines()

readlines([size])

讀取完檔案(看到後面你就知道並不是),返回每一行所組成的列表。我們就可以使用列表訪問的方式,使用檔案。

弊端:一次性讀完,佔用很大記憶體。

write()

write(str)

將字串寫入檔案

writelines()

writelines(sequence_of_strings) : 寫多行到檔案

sequence_of_strings是一個由字元型組成的列表。一次性寫入多行。

輸入ipython進入ipython環境

mark

type(f)可以看到開啟的檔案型別就是一個檔案物件。

mark

擁有的方法可以使用f. + Tab自動補齊

mark
Error:File not open for writing

這裡因為我們使用的是預設無引數開啟,此時我們並不擁有寫許可權。

Python檔案開啟方式

mode 說明 說明
`r` 只讀方式開啟 檔案必須存在
`w` 只寫方式開啟 檔案不存在建立檔案;存在則清空內容
`a` 追加方式開啟 檔案不存在建立檔案
`r+`/`W+` 讀寫方式開啟
`a+` 追加和讀寫方式開啟

`rb`,`wb`,`ab`,`rb+`,`wb+`,`ab+`:都是採用二進位制方式開啟。

練習`r` `w` `a` `r+`

mark
  • 不帶引數open是用只讀方式開啟的,所以檔案不存在會報錯
mark
  • 注意關閉檔案f.close()否則cat時檔案內容會為空。
mark
  • 當我們再次開啟檔案使用w方式。已存在的檔案會被清空才開啟。
mark
  • 我們以a方式開啟此時我們可以對於檔案進行追加操作,原有內容並沒有被清空
    新增內容被新增到了最後
mark
  • 當我們以r+方式開啟,我們新增的內容會從檔案開始替換原有內容的一部分。
mark
  • 當我們以w+方式開啟,檔案可以被讀寫。但是檔案內容被清空。
mark
  • 此時新檔案可以被讀寫。

使用二進位制方式開啟的使用方法與上面介紹的基本一致。

意義在於: 比如讀取一張圖片的EXIF資訊讀取圖片的長寬拍照日期

如果我們以普通方式開啟是以文字方式開啟,不能讀取到正確的內容。
當我們以二進位制開啟我們就可以讀取到正確的EXIF資訊

學習了開啟檔案,讀取檔案,寫入檔案。檔案開啟方式的不同。

檔案讀取方式

read()

read([size]) 

讀取檔案(讀取size個位元組,預設讀取全部)

  • 如果檔案長度大於size,我們就讀size個長度。
  • 如果檔案長度小於size,我們就把檔案讀完。
  • 如果沒有設定size,預設全部讀完。

readline()

readline([size])

讀取一行

示例操作

首先使用touch mtianyan.txtvi mtianyan.txt
建立並寫入內容:

blog.mtianyan.cn
blog.mtianyan.cn
blog.mtianyan.cn
mark

可以看出

readline(size):
    if len(line) > size:
        return size
    if len(line) < size:
        return len(line)

readlines()

readlines([size])

讀取完檔案(整數個buffer左右位元組),返回每一行所組成的列表。我們就可以使用列表訪問的方式,使用檔案。

弊端:一次性讀完,佔用很大記憶體。

示例操作:

mark

雖然設定了size1,但是還是把所有都讀出來了。

mark

輸入help(f.readlines)檢視定義:重複的呼叫readline()然後返回一個讀到的行所組成的列表。

讀取檔案的大小:

  • 如果size小於緩衝區的大小,則讀取緩衝區大小的資料
  • 如果size大於緩衝區的大小,讀取size大小的資料,但不是完全的等於size的大小,一般讀取比size大的整行的資料.

這個設定是python直譯器內部的一個變數。

import io
io.DEFAULT_BUFFER_SIZE
mark

設定引數小於8192每次能讀取8192個位元組。

  • 設定引數大於一個8192小於2*8192=16384。則讀取兩個buffer值.
  • BUFFER都是一整個一整個讀

Linux命令使用

使用vi開啟檔案。y是複製當前行,yy是複製游標所在的一整行.
如果複製了一行,則p是在游標所在行的下一行貼上,10000p就是貼上10000次

  • 命令yy,10000p複製10000行 .然後:set nu 顯示行數

  • gg

    • 跳轉到檔案頭 :1gg
    • 跳轉到檔案末尾行數+gg :$ Shift+g
    • 跳轉到指定行,例跳轉到123行:123gg:123.

使用ctrl+z可將程式切換到後臺。用fg可將後臺程式切換到前臺。

mark

可以看到此時的檔案以及有十幾萬個位元組了

mark

可以看出這時候不再是一次性把檔案讀取完了,而是讀取了482行。

170051/100003行,每行17個位元組。

mark

這時我們的size大於了緩衝區的大小8192。因此我們的size到了819216384
之間。

重點:系統資料是按著整個buffer,整個buffer來讀的。此時系統會讀取差不多兩個buffer的資料。也就是963*17 =16371

待探究

iter: 使用迭代器讀取檔案

mark

迭代器並不是把我們的檔案全部存入了記憶體中。而是我們每使用一次next方法就會自動讀取下一行。所以能在不消耗大量記憶體的情況下進行讀取整個檔案。

檔案寫入與寫快取

write()

write(str)

將字串寫入檔案

writelines()

writelines(sequence_of_strings) : 寫多行到檔案

sequence_of_strings引數是一個可迭代物件:

  • 可以是字串
  • 可以是字串所組成的元組。
  • 可以是一個字串所組成的迭代器。
  • 也可是一個字串組成的列表

實際操作

mark
In [37]: cat test.txt
test write
In [38]: f = open("mtianyan.txt",`w`)
In [39]: f.writelines(`123456`)
In [40]: f.writelines((1,2,3))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-40-0a3726375675> in <module>()
----> 1 f.writelines((1,2,3))

TypeError: writelines() argument must be a sequence of strings

寫入一個元組會報錯 :提示必須是由字串組成的序列。

In [41]: f.writelines((`1`,`2`,`3`))
In [42]: f.writelines([`1`,`2`,`3`])
In [43]: f.close()
In [44]: cat test.txt
test write
In [45]: cat mtianyan.txt
123456123123

可以看到我們都可以正常的寫入由字串組成的列表。

In [46]: f = open("mtianyan.txt",`w`)
In [47]: f.write("11111")
In [48]: cat mtianyan.txt

In [49]: 

檔案內容為空,因為我們沒有使用f.close()將檔案關閉。

Linux系統有一種寫快取機制。如果我們不主動呼叫f.close()flush
他是不會寫到磁碟檔案的。

mark

寫檔案的過程中Python直譯器進行系統呼叫。Linux系統核心將檔案寫入系統檔案緩衝區。
當我們呼叫f.close()時,系統會更新檔案到硬碟。

Python寫磁碟的快取機制

  1. 主動呼叫closed()flush方法。寫快取同步到磁碟
  2. 寫入資料量大於或等於寫快取,寫快取同步到磁碟。(此時系統寫快取又清空,然後就又繼續快取直到close())

實際操作

In [62]: f = open("mtianyan.txt",`w`)
In [63]: f.write(`test write`)
In [64]: cat mtianyan.txt
In [65]: f.flush()
In [66]: cat mtianyan.txt
test write

進行flush()操作時內容更新。

mark
mark

說明我的Linux的緩衝區就是這麼大147456
當我們執行close檔案會變大。因為全部寫進去了。

檔案關閉

  1. 寫快取同步到磁碟;
  2. linux系統中每個程式開啟的檔案個數是有限的;
  3. 如果開啟檔案數到了系統限制,在開啟檔案就會失敗;
mtianyan@mtianyan-deep:~/Desktop/PythonFile$ ps
   PID TTY          TIME CMD
  5587 pts/1    00:00:00 bash
  5918 pts/1    00:00:02 ipython

看到ipython程式:

mtianyan@mtianyan-deep:~/Desktop/PythonFile$ cat /proc/5918/limits 
Limit                     Soft Limit           Hard Limit           Units     
Max open files            1024                 1048576              files     
  • file.fileno():檔案描述符;每開啟一個就會加1,直到1024失敗
n [3]: for i in range(1027):
   ...:     f = open("mtianyan.txt",`r`)
   ...:     print ("%d:%d" %(i,f.fileno()))

mark

並沒有起到效果。fileno1012不斷來回

實現程式碼:


In [11]: for i in range(1025):
    ...:     list_f.append(open("mtianyan.txt",`w`))
    ...:     print ("%d:%d" %(i,list_f[i].fileno()))
    ...:     

執行結果:

1010:1022
---------------------------------------------------------------------------
IOError                                   Traceback (most recent call last)
<ipython-input-10-dd5ee4024396> in <module>()
      1 for i in range(1025):
----> 2     list_f.append(open("mtianyan.txt",`w`))
      3     print ("%d:%d" %(i,list_f[i].fileno()))
      4 

IOError: [Errno 24] Too many open files: `mtianyan.txt`

因此我們需要養成良好編碼習慣記得關閉檔案。

檔案指標。

  1. 寫入檔案後,必須開啟才能讀取寫入的內容
  2. 讀取檔案後,無法重新再次讀取讀取過的內容

比如:寫完檔案必須關閉才能讀到我們寫入的內容。

檔案指標用於定位我們當前檔案所執行到的位置:

  • 使用open()開啟時檔案指標就在起始位置。
  • read(3)檔案指標就會往後挪3個位置。指向第四個位置
  • write(`mtianyan`)就會從當前檔案指標4開始寫。檔案指標就會移動8個位元組

我們之前的問題都是因為我們無法使檔案指標歸於起始位置的,
下面我們學習自由操作檔案指標:

seek(offset[,whence]):移動檔案指標
    offset 偏移量,可以為負數
    whence:偏移相對位置:
os.SEEK_SET:相對檔案的起始位置0
os,SEEK_CUR:相對檔案的當前位置1
os.SEEK_END:相對檔案的結尾位置2
In [1]: f = open(`mtianyan.txt`,`r+`)
In [2]: import os
In [3]: f.tell() 
Out[3]: 0

f.tell()(函式)返回當前檔案的偏移返回的是一個整數,也許是一個長整數

In [4]: f.read(3)
Out[4]: `tes`
In [5]: f.tell()
Out[5]: 3
In [6]: f.seek(0, os.SEEK_SET)
In [7]: f.tell()
Out[7]: 0

可以看出我們可以使用f.seek(0, os.SEEK_SET)來進行指標歸零。

In [8]: f.seek(0,os.SEEK_END)
In [9]: f.tell()
Out[9]: 3139
In [10]: ls -l mtianyan.txt
-rw-r--r-- 1 mtianyan mtianyan 3139 1月   6 03:58 mtianyan.txt
In [11]: f.read(5)
Out[11]: ``

當然我們也可以把檔案指標指到末尾去。檔案指標已經在末尾了,向後繼續讀取會為空。

In [12]: f.seek(-5,os.SEEK_END)
In [13]: f.read(5)
Out[13]: `231

`

使用f.seek(-5,os.SEEK_END)可以將檔案指標倒著移回去。

In [14]: f.tell()
Out[14]: 3139
In [15]: f.seek(-5,os.SEEK_CUR)
In [16]: f.tell()
Out[16]: 3134

讀取5個之後,指標又跑到了3139:使用f.seek(-5,os.SEEK_CUR)
又可以從當前位置往前移。

當檔案偏移大於檔案長度會出現的問題:

In [17]: f.seek(0,os.SEEK_END)
In [18]: f.tell()
Out[18]: 3139
In [19]: f.seek(-3140,os.SEEK_END)
---------------------------------------------------------------------------
IOError                                   Traceback (most recent call last)
<ipython-input-19-331d0bc629ca> in <module>()
----> 1 f.seek(-3140,os.SEEK_END)

IOError: [Errno 22] Invalid argument

會報錯:告訴我們這是一個無效的引數。類似於索引越界等。

檔案屬性及OS模組使用

講解檔案屬性、標準檔案、檔案命令列引數、檔案編碼格式、使用os模組中方法對檔案進行操作、使用os模組中的方法對目錄進行操作

檔案屬性編碼格式

Python檔案屬性

  • file.fileno():檔案描述符;每開啟一個就會加1,直到1024失敗
  • file.mode:檔案開啟許可權
  • file.encoding:檔案編碼格式
  • file.closed:檔案是否關閉
In [20]: f.fileno()
Out[20]: 12
In [21]: f.closed
Out[21]: False
In [22]: f.mode
Out[22]: `r+`
In [23]: f.encoding

沒有返回值:為ASCII碼檔案。應該與系統相關。

Python標準檔案

  • 檔案標準輸入:sys.stdin
  • 檔案標準輸出:sys.stdout
  • 檔案標準錯誤:sys.stderr;
In [26]: import sys
In [27]: type(sys.stdin)
Out[27]: file
In [28]: sys.stdin.mode
Out[28]: `r`
In [29]: sys.stdin.read()

系統的sys.stdinread()是指從控制檯讀入資料。

In [32]: sys.stdin.fileno()
Out[32]: 0

系統的sys.stdinfileno為0。也就是我們啟動一個程式,他首先開啟標準輸入:從控制檯讀資料。

a = raw_input() 其實內部是呼叫sys.stdin來讀取資料的。

In [37]: sys.stdout.mode
Out[37]: `w`
In [38]: sys.stdout.write("1000")
1000

sys.stdout只可以寫,不可以讀。寫入相當於寫入到控制檯,會直接顯示

print實際內部呼叫的是stdout

In [39]: sys.stdout.fileno()
Out[39]: 1

sys.stdout是系統在啟動程式時第二個開啟的。

也就是系統會先為我們開啟輸入再開啟輸出

In [40]: sys.stderr.mode
Out[40]: `w`
In [41]: sys.stderr.write("error")
error
In [42]: sys.stderr.fileno()
Out[42]: 2

sys.stderr是系統在啟動程式時第二個開啟的。
先開輸入,再開輸出。第三err輸出。

Python檔案命令列引數

sys模組提供了sys.argv,通過該屬性可以得到命令列引數

很多程式都是這麼做的:比如加上-v,根據不同的引數完成不同的功能。

sys.argv是一個字串組成的列表

import sys

if __name__ == `__main__`:
    print len(sys.argv)
    for arg in sys.argv:
        print arg

執行結果:

1
D:mtianBloghexoBlog-Githubsource\_posts	est.py

直接執行,我們沒有提供引數,列印出了當前檔案的絕對路徑檔名。

Python檔案編碼方式。

使用普通方式開啟檔案:寫入u天涯明月笙

In [3]: f.write(u`天涯明月笙`)
---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
<ipython-input-3-3cd15b559bd9> in <module>()
----> 1 f.write(u`天涯明月笙`)

UnicodeEncodeError: `ascii` codec can`t encode characters in position 0-4: ordinal not in range(128)

會報錯:UnicodeEncodeError因為我們的檔案是ascii格式的。不能將Unicode字串寫入。

實現程式碼:

In [1]: a = unicode.encode(u`天涯`,`utf-8`)
In [2]: a
Out[2]: `xe5xa4xa9xe6xb6xaf`
In [3]: f.write(a)

執行結果:

In [7]: cat mtianyan.txt
天涯

我們可以用codecs模組提供方法建立制定編碼格式檔案.

import codecs
f = codecs.open(`test.txt`,`w`,`utf-8`)
f.encoding

open(fname,mode,encoding,errors,buffering):使用指定編碼格式開啟檔案

linux檔案系統

所有裝置都可以看為檔案:

  • 包括磁碟(ext2,ext4)檔案,NFS檔案系統,各種外設
mark

虛擬檔案系統為檔案提供檔案節點

每一個外設在核心中都有一個對應的驅動程式。通過系統呼叫對檔案節點進行訪問。把所有外設作為檔案處理。

mark

在作業系統之上的Python直譯器,Python程式跑在直譯器中。

mark

使用open()會訪問檔案節點。裝置節點對應虛擬檔案系統的方法,這個檔案系統與驅動繫結。
驅動會操作硬體裝置。

os模組對檔案和目錄進行操作

使用os模組來開啟檔案:

os.open(filename, flag [,mode])開啟檔案

- os.O_CREAT:建立檔案
- os.O_RDONLY:只讀方式開啟
- os.O_WRONLY:只寫方式開啟
- os.O_RDWR:讀寫方式開啟

os.read(fd, buffersize):讀取檔案,返回值為讀取的內容
os.write(fd, string):寫入檔案 ,返回值是寫入資料的大小
os.lseek(fd, pos, how): 檔案指標操作
os.close(fd):關閉檔案

umask獲取系統的預設許可權。我的是0022

In [14]:fd = os.open(`mtianyan.txt`,os.O_CREAT | os.O_RDWR)
In [15]: n = os.write(fd,"mtianyan")
In [16]: n
Out[16]: 8
In [17]: os.lseek(fd,0,os.SEEK_SET)
Out[17]: 0
In [18]: os.read(fd,5)
Out[18]: `mtian`
Out[19]: os.close()

使用os是為了跨平臺。

os方法

os方法 說明
os.access(path, mode) 判斷該檔案許可權:F_OK存在,許可權:R_OK ,W_OK, X_OK
os.listdir(path) 返回當path路徑下所有檔名組成的列表
os.remove(path) 刪除檔案
os.rename(old, new) 修改檔案或者目錄名
os.mkdir(path[, mode]) 建立目錄
os.makedirs(path[, mode]) 建立多級目錄
os.removedirs(path) 刪除多級目錄
os.rmdir(path) 刪除目錄(目錄必須空目錄)
os.access(`mtianyan,txt`,os.R_OK)
os.listdir(`./`)
os.rename(`test/`,`test1`)

os.path方法

os.path方法 說明
os.path.exists(path) 當前路徑是否存在,也可以判斷是否有該檔案
os.path.isdir(s) 是否是一個目錄
os.path.isfile(path) 是否是一個檔案
os.path.getsize(filename) 返回檔案大小,返回目錄檔案大小
os.path.dirname(p) 返回路徑的目錄
os.path.basename(p) 返回路徑的檔名
os.path.exists(`./mtianyan.txt`)
# True
os.path.isdir(`./``)
# True
os.path.isdir(`mtianyan.txt`)
# False
os.path.isfile(`mtianyan.txt`)
# True

檔案練習

使用Python來管理ini檔案:實現查詢新增刪除儲存

  • 掌握檔案的基本操作
  • 認識ini檔案
  • 瞭解ConfigPaser

ini配置檔案格式:

節: [session]
引數(鍵=值)  name=value

[port]
    port1 = 800
    port2 = 900

定位帶port這個節,然後找到。

ConfigParser

mtianyan.txt內容:

[userinfo]
name = mtianyan
pwd = abc

[study]
python = 15
java_base = 20

兩個概念:sections(大的分類)option小的選項

import ConfigParser
cfg = ConfigParser.ConfigParser()

help(cfg.read)

cfg.read(`mtianyan.txt`)
# [`mtianyan.txt`]
cfg.sections() #返回節點值:userinfo,study
# [`userinfo`, `study`]
for se in cfg.sections():
    print se
    print cfg.items(se)
#遍歷出整個檔案

遍歷結果如下

userinfo
[(`name`, `johntian`), (`pwd`, `abc`)]
study
[(`python_base`, `15`), (`python_junior`, `20`), (`linux_base_`, `15`)]
cfg.set(`userinfo`, `pwd`, `1234567`)     #修改鍵值
cg.set(`userinfo`,`email`,`1147727180.com`)//增加鍵值

cfg.remove_option(`user info`,`email`)  //刪除鍵值

檔案操作練習

import os
import ConfigParser

```
1: dump ini
2: del section
3: del item
4: modify item
5: add section
6: save modify

```


class student_info(object):

    def __init__(self, recordfile):
        self.logfile = recordfile
        self.cfg = ConfigParser.ConfigParser()

    def cfg_load(self):
        self.cfg.read(self.logfile)

    def cfg_dump(self):
        se_list = self.cfg.sections()
        print "**********************>"
        for se in se_list:
            print se
            print self.cfg.items(se)
            print "<*********************"

    def delete_item(self, section, key):
        self.cfg.remove_option(section, key)

    def delete_section(self, sescion):
        self.cfg.remove_option(section)

    def add_section(self, section):
        self.cfg.add_section(section)

    def set_item(self, section, key, value):
        self.cfg.set(section, key, value)

    def save(self):
        fp = open(self.logfile, `w`)
        self.cfg.write(fp)
        fp.close()

if __name__ == `__main__`:
    info = student_info(`mtianyan.txt`)
    info.cfg_load()
    info.cfg_dump()
    info.add_section(`userinfo`)
    info.set_item(`userinfo`, `pwd`, `abc`)
    info.cfg_dump()
    info.add_section(`login`)
    info.set_item(`login`, `2018-0106`, `20`)
    info.cfg_dump()
    info.save()

執行結果:

**********************>
**********************>
userinfo
[(`pwd`, `abc`)]
<*********************
**********************>
userinfo
[(`pwd`, `abc`)]
<*********************
login
[(`2018-0106`, `20`)]
<*********************
[Finished in 0.3s]


相關文章