2020年9月15日星期二

008 零基礎入門學習Python 第8章 永久存儲

申明本站飛宇網 https://feiyetopro.blogspot.com/自網路收集整理之書籍文章影音僅供預覽交流學習研究,其[書籍、文章、影音]情節內容, 評論屬其個人行為, 與本網站無關。版權歸原作者和出版社所有,請在下載 24 小時內刪除,不得用作商業用途;如果您喜歡其作品,請支持訂閱購買[正版]謝謝!

8章 永久存儲
8.1 文件:因為懂你,所以永恆
大多數的程式都遵循著:輸入->處理->輸出的模型,首先接收輸入資料,然後按照要求進行處理,最後輸出資料。到目前為止,我們已經很好地瞭解了如何處理資料,然後列印出需要的結果。不過你可能已經胃口大開,不再只滿足於使用input接收用戶輸入,使用print輸出處理結果了。你迫切想要的是關注到系統的方方面面,你需要自己的代碼可以自動分析系統的日誌,你需要分析的結果可以保存為一個新的日誌,甚至你需要跟外面的世界進行交流。
相信大家都曾經有這樣的經歷:當你在編寫代碼寫起勁兒的時候,系統突然藍屏崩潰了,重啟之後發現剛才寫入的代碼都不見了,這時候你就會吐槽這破系統怎麼這麼不穩定等等。
在你編寫代碼的時候,作業系統為了更快地做出回應,把所有當前的資料都放在記憶體中,因為記憶體和CPU資料傳輸的速度要比在硬碟和CPU之間傳輸的速度快很多倍。但記憶體有一個天生的不足,就是一旦斷電就沒戲,所以小甲魚在這裡再一次呼籲廣大未來即將成為偉大程式師的讀者們:請養成一個優雅的習慣,隨時使用快速鍵Ctrl+S保存你的資料。
由於Windows是以副檔名來指出檔是什麼類型,所以相信很多習慣使用Windows的朋友很快就反應過來了,.exe是可執行檔格式,.txt是文字檔,.pptPowerPoint的專用格式……所有這些都稱為檔。

8.1.1 打開文件
Python中,使用open()這個函數來打開檔並返回檔物件:
open(file, mode = 'r', buffering = -1, encoding = None, errors = None, newline = None, closefd = True, opener = None)
open()這個函數有很多參數,但作為初學者的我們,只需要先關注第一個和第二個參數即可。第一個參數是傳入的檔案名,如果只有檔案名,不帶路徑的話,那麼Python會在當前資料夾中去找到該檔並打開。有的讀者可能會問:如果我要打開的檔事實上並不存在呢?
那就要看第二個參數了,第二個參數指定檔打開模式,如表8-1所示。
8-1 檔的打開模式
使用open()成功打開一個檔之後,它會返回一個檔物件,拿到這個檔物件,就可以讀取或修改這個檔:
沒有消息就是好消息,說明我們的檔成功被打開了。

8.1.2 檔物件的方法
打開檔並取得檔物件之後,就可以利用檔物件的一些方法對檔進行讀取或修改等操作。表8-2列舉了平時常用的一些檔物件方法。
8-2 檔物件方法

8.1.3 文件的關閉
close()方法用於關閉檔。如果是講C語言程式設計教學,小甲魚一定會一萬次地強調文件的關閉非常重要。而Python擁有垃圾收集機制,會在檔物件的引用計數降至零的時候自動關閉檔,所以在Python程式設計裡,如果忘記關閉檔並不會造成記憶體洩露那麼危險的結果。
但並不是說就可以不要關閉檔,如果你對檔進行了寫入操作,那麼應該在完成寫入之後關閉檔。因為Python可能會緩存你寫入的資料,如果中途發生類似斷電之類的事故,那些緩存的資料根本就不會寫入到檔中。所以,為了安全起見,要養成使用完檔後立刻關閉的好習慣。

8.1.4 文件的讀取和定位
檔的讀取方法很多,可以使用檔物件的read()readline()方法,也可以直接list(f)或者直接使用反覆運算來讀取。read()是按位元組為單位讀取,如果不設置參數,那麼會全部讀取出來,檔指標指向檔末尾。tell()方法可以告訴你當前檔指標的位置:
剛才提到的文件指針是啥?你可以認為它是一個書簽,起到定位的作用。使用seek()方法可以調整檔指標的位置。seek(offset, from)方法有兩個參數,表示從from0代表檔起始位置,1代表當前位置,2代表檔末尾)偏移offset位元組。因此將檔指針設置到檔起始位置,使用seek(0, 0)即可:
(注:因為1個中文字元佔用2個位元組的空間,所以4個中文加1個英文冒號剛好到位置9。)
readline()方法用於在檔中讀取一整行,就是從檔指標的位置向後讀取,直到遇到分行符號(\n)結束:
此前介紹過列表的強大,說什麼都可以往裡放,這不,也可以把整個檔的內容放到清單中:
對於反覆運算讀取文字檔中的每一行,有些讀者可能會這麼寫:
這樣寫並沒有錯,但給人的感覺就像是你拿酒精燈去燒開水,水是燒得開,不過效率不是很高。因為檔物件自身是支援反覆運算的,所以沒必要繞圈子,直接使用for語句把內容反覆運算讀取出來即可:

8.1.5 文件的寫入
如果需要寫入檔,請確保之前的打開模式有'w''a',否則會出錯:
然而一定要小心的是:使用'w'模式寫入檔,此前的檔內容會被全部刪除!如圖8-1所示,小甲魚和小客服的對話備份已經不在了。
8-1 'w'打開模式會刪除原來的檔內容
如果要在原來的內容上追加,一定要使用'a'模式打開檔哦。這是血淋淋的教訓,不要問我為什麼(想想都是淚啊)!

8.1.6 一個任務
本節要求讀者朋友獨立來完成一個任務——將檔(record2.txt)中的資料進行分割並按照以下規則保存起來:
1)將小甲魚的對話單獨保存為boy_*.txt的文件(去掉小甲魚:)。
2)將小客服的對話單獨保存為girl_*.txt的文件(去掉小客服:)。
3)檔中總共有三段對話,分別保存為boy_1.txtgirl_1.txtboy_2.txtgirl_2.txtboy_3.txtgirl_3.txt6個檔(提示:檔中不同的對話間已經使用=======================================分割)。
大家一定要自己先動動手再參考答案哦:
事實上可以利用函數封裝得更好看一些:

8.2 檔案系統:介紹一個高大上的東西
接下來會介紹跟Python的檔相關的一些十分有用的模組。模組是什麼?其實我們寫的每一個原始程式碼檔(*.py)都是一個模組。Python自身帶有非常多實用的模組,在日常程式設計中,如果能夠熟練地掌握它們,將事半功倍。
比如剛開始介紹的文字小遊戲,裡邊就用random模組的randint()函數來生成亂數。然而要使用這個randint()函數,直接就調用可不行:
正確的做法應該是先使用import語句導入模組,然後再使用:
首先要介紹的是高大上的OS模組,OS就是Operating System的縮寫,意思是作業系統,而平時經常說iOS就是iPhone OS的意思,即蘋果手機的作業系統。但這裡小甲魚說OS模組高大上,並不是因為跟蘋果或土豪金拉邊才這麼說。之所以說OS模組高大上,是因為對於檔案系統的訪問,Python一般是通過OS模組來實現的。我們所知道常用的作業系統就有WindowsMac OSLinuxUNIX等,這些作業系統底層對於檔案系統的訪問工作原理是不一樣的,因此你可能就要針對不同的系統來考慮使用哪些檔案系統模組……這樣的做法是非常不友好且麻煩的,因為這意味著當你的程式運行環境一旦改變,你就要相應地去修改大量的代碼來應付。
但是Python是跨平臺的語言,也就是說,同樣的原始程式碼在不同的作業系統不需要修改就可以同樣實現。有了OS模組,不需要關心什麼作業系統下使用什麼模組,OS模組會幫你選擇正確的模組並調用。
8-3列舉了OS模組中關於檔/目錄常用的函數使用方法。
8-3 OS模組中關於檔/目錄常用的函數使用方法
1getcwd()
在有些情況下我們需要獲得應用程式當前的工作目錄(比如要保存暫存檔案),那麼可以使用getcwd()函數獲得:
2chdir(path)
chdir()函數可以改變當前工作目錄,比如可以切換到E盤:
3listdir(path='.')
有時候你可能需要知道目前的目錄下有哪些檔和子目錄,那麼listdir()函數可以幫你列舉出來。path參數用於指定列舉的目錄,預設值是'.',代表根目錄,也可以使用'..'代表上一層目錄:
4mkdir(path)
mkdir()函數用於創建資料夾,如果該資料夾存在,則拋出FileExistsError異常:
5makedirs(path)
makedirs()函數可以用於創建多層目錄:
效果如圖8-2所示。
8-2 makedirs()函數
6remove(path)rmdir(path)removedirs(path)
remove()函數用於刪除指定的檔,注意是刪除檔,不是刪除目錄。如果要刪除目錄,則用rmdir()函數;如果要刪除多層目錄,則用removedirs()函數。
7rename(old, new)
rename()函數用於重命名檔或資料夾:
8system(command)
幾乎每個作業系統都會提供一些小工具,system()函數用於使用這些小工具:
回車後即彈出計算器,效果如圖8-3所示。
8-3 system()函數
9walk(top)
最後是walk()函數,這個函數在有些時候確實非常有用,可以省去你很多麻煩。該函數的作用是遍歷top參數指定路徑下的所有子目錄,並將結果返回一個三元組(路徑,[包含目錄],[包含檔])。來看下面的例子:
為了便於理解,我畫個實際的資料夾分佈圖給大家對比一下,如圖8-4所示。
8-4 walk()函數
另外path模組還提供了一些很實用的定義,分別是:os.curdir表示目前的目錄;os.pardir表示上一級目錄('..');os.sep表示路徑的分隔符號,比如Windows系統下為'\\'Linux下為'/'os.linesep表示當前平臺使用的行結束字元(在Windows下為'\r\n'Linux下為'\n');os.name表示當前使用的作業系統。
另一個強大的模組是os.path,它可以完成一些針對路徑名的操作。表8-4列舉了os.path中常用到的函數使用方法。
8-4 os.path模組中關於路徑常用的函數使用方法
10basename(path)dirname(path)
basename()dirname()函數分別用於獲得檔案名和路徑名:
11join(path1[, path2[, ...]])
join()函數跟BIF的那個join()函數不同,os.path.join()是用於將路徑名和檔案名組合成一個完整的路徑:
12split(path)splitext(path)
split()splitext()函數都用於分割路徑,split()函數分割路徑和檔案名(如果完全使用目錄,它也會將最後一個目錄作為檔案名分離,且不會判斷檔或者目錄是否存在);splitext()函數則是用於分割檔案名和副檔名:
13getsize(file)
getsize()函數用於獲取檔的尺寸,返回值是以位元組為單位:
14getatime(file)getctime(file)getmtime(file)
getatime()getctime()getmtime()分別用於獲得檔的最近存取時間、創建時間和修改時間。不過返回值是浮點型秒數,可用time模組的gmtime()localtime()函數換算:
還有一些函數返回布林類型的值,具體的解釋見表8-4,這裡就不一一舉例了。
8.3 pickle:醃制一缸美味的泡菜
從一個檔裡讀取字串非常簡單,但如果想要讀取出數值,那就需要多費點兒周折。因為無論是read()方法,還是readline()方法,都是返回一個字串,如果希望從字串裡邊提取出數值的話,可以使用int()函數或float()函數把類似'123''3.14'這類字串強制轉換為具體的數值。
此前一直在講保存文本,然而當要保存的資料像清單、字典甚至是類的實例這些更複雜的資料類型時,普通的檔操作就會變得不知所措。也許你會把這些都轉換為字串,再寫入到一個文字檔中保存起來,但是很快你就會發現要把這個過程反過來,從文字檔恢復資料物件,就變得異常麻煩了。
所幸的是,Python提供了一個標準模組,使用這個模組,就可以非常容易地將清單、字典這類複雜資料類型存儲為檔了。這個模組就是本節要介紹的pickle模組。
pickle就是泡菜,醃菜的意思,相信很多女讀者都對韓國泡菜尤其情有獨鍾。至於Python的作者為何把這麼一個高大上模組命名為泡菜,我想應該是跟韓劇脫不了干係。
好,說回這個泡菜。用官方文檔中的話說,這是一個令人驚歎(amazing)的模組,它幾乎可以把所有Python的物件都轉化為二進位的形式存放,這個過程稱為pickling,那麼從二進位形式轉換回物件的過程稱為unpickling
說了這麼多,還是來點乾貨吧:
分析一下:這裡希望把這個列表永久保存起來(保存成檔),打開的檔一定要以二進位的形式打開,尾碼名倒是可以隨意,不過既然是使用pickle保存,為了今後容易記憶,建議還是使用.pkl.pickle。使用dump方法來保存資料,完成後記得保存,跟操作普通文字檔一樣。
程式執行之後E盤會出現一個my_list.pkl的檔,用記事本打開之後顯示亂碼(因為它保存的是二進位形式),如圖8-5所示。
那麼在使用的時候只需用二進位模式先把檔打開,然後用load把資料載入進來:
8-5 保存為pickle
程式執行後又取回我們的列表啦:
利用pickle模組,不僅可以保存清單,事實上pickle可以保存任何你能想像得到的東西。


0 留言:

發佈留言