Featured image of post 從零開始學Python (15) — 檔案讀寫:妳出現在我詩的每一頁(上)

從零開始學Python (15) — 檔案讀寫:妳出現在我詩的每一頁(上)

Day 15 檔案讀寫:妳出現在我詩的每一頁(上)

註:本篇文章同步刊載於iT邦幫忙,為鐵人賽之系列文章。
https://ithelp.ithome.com.tw/articles/10245133

先來解答昨天的問題吧!

按部就班仔細寫完應該就沒問題啦!
下面有省略掉前面的一部分東西,讀者也可以自行選擇保留。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Student():
    cp_cnt = 0 # 這個變數是屬於整個Student類別的
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __eq__(self, b):
        # 為了方便讀者檢查是否有問題
        print('self = %d, b = %d' % (self.score['數學'] * 2 + self.score['英文'] * 5, b.score['數學'] * 2 + b.score['英文'] * 5))
        return self.score['數學'] * 2 + self.score['英文'] * 5 == b.score['數學'] * 2 + b.score['英文'] * 5

    def __gt__(self, b):
        return self.score['數學'] * 2 + self.score['英文'] * 5 > b.score['數學'] * 2 + b.score['英文'] * 5

    def readMyName(self):
        print('聽清楚了,我的名字是' + self.name + '!!!')

    def compare(self, b):
        Student.cp_cnt += 1
        # 同樣,印出diff方便檢查正確性,讀者可自行註解掉。
        diff = sum(self.score.values()) - sum(b.score.values())
        print('diff = %d' % diff)
        # 有冒號的式子如果底下程式碼只有一行,也可以選擇直接和判斷式寫成同一行
        if diff > 0: print(self.name + '贏了!')
        elif diff == 0: print('什麼?竟然平手?!')
        else: print('可...可惡,難道,這就是' + b.name + '真正的實力嗎?')
        print('已比較 %d 次!\n' % Student.cp_cnt)

    def compareE(self, b):
        Student.cp_cnt += 1
        if self > b: print(self.name + ' >  ' + b.name)
        elif self == b: print(self.name + ' == ' + b.name)
        else: print(self.name + ' <  ' + b.name)
        print('已比較 %d 次!\n' % Student.cp_cnt)

    @classmethod
    def getCpCount(cls):
        print('目前的比較次數:%d' % cls.cp_cnt) # 別忘了print也可以用format類型的形式
1
2
print('開始之前')
Student.getCpCount() # 開始前先看看是不是0
1
2
3
4
5
6
ming = Student('阿明', {'數學':55, '英文':70, '物理':55})
ming.readMyName()
mei = Student('小美', {'數學':90, '英文':88, '物理':100})
mei.readMyName()
howhow = Student('HowHow', {'數學':80, '英文':60, '物理':40})
howhow.readMyName()
1
2
3
4
5
6
7
print('\n來PK吧! 先比總分,再比加權分!\n')
ming.compare(howhow)
ming.compareE(howhow)
ming.compare(mei)
ming.compareE(mei)
mei.compare(howhow)
mei.compareE(howhow)

結果應該如下:

1
2
3
4
5
6
C:\Users\Desolve>python fromzero.py
開始之前
目前的比較次數0
聽清楚了我的名字是阿明!!!
聽清楚了我的名字是小美!!!
聽清楚了我的名字是HowHow!!!
1
來PK吧! 先比總分再比加權分!
1
2
3
diff = 0
什麼竟然平手?!
已比較 1!
1
2
3
self = 460, b = 460
阿明 == HowHow
已比較 2!
1
2
3
diff = -98
...可惡難道這就是小美真正的實力嗎
已比較 3!
1
2
3
self = 460, b = 620
阿明 <  小美
已比較 4!
1
2
3
diff = 98
小美贏了
已比較 5!
1
2
小美 >  HowHow
已比較 6!

今天我們來談談資料的讀寫。
對Python來說,一旦程式結束,
或退出Python直譯器,前面做過的事情,
產生的變數等都會被清理一空,消失不見。
為什麼呢?程式執行途中,所有的變數等東西要存放在記憶體中,
在離開時,空間自然要還回去才不會浪費嘛!

那麼,如果我們想保留中途取得的結果該怎麼辦呢?
可以選擇存放到檔案中,或者是資料庫裡,
我們先簡單介紹一些檔案存取的方法。

對Python來說,要讀寫一個檔案前,
要先打開它(開啟舊檔嘛XD!)。

1
2
3
4
# 方法一
file = open(name, mode)
... (使用file來處理檔案)
file.close() # 用完要關閉檔案
1
2
3
4
# 方法二
with open(name, mode) as file:
    ...(使用file來處理檔案)
# 離開這個with的區塊以後,file自動關閉。

open會找尋name的檔案,並以mode的模式開啟,
開啟成功的話,會回傳一個檔案物件,我們用file的名字來接取它。
mode模式是一個字串,通常狀況下會有1~2個字母,其代表涵義如下:
第一個字母:
‘r’ -> 讀取(read)
‘w’ -> 寫入(write)(但不給r預設還是會可讀)
‘x’ -> 新增檔案(exclusive creation),如果檔案已存在則回傳錯誤
‘a’ -> 在結尾處寫入(append)
第二個字母:
‘b’ -> 用二進位的方式來處理
(預設則是當成文字來處理)
‘+’號: -> 更新(updating) (可讀可寫)
通常會用’r+’,代表可讀可寫。

我們先來看看怎麼樣將七里香的片段寫進詩裡:

1
2
3
4
>>> f = open('poem.txt', 'w')
>>> f.write('院子落葉\n跟我的思念厚厚一疊') # write結束時會回傳寫入的字數(byte數)
14
>>> f.close() # 要先close()以後,才會真的寫入完畢!

讀者可以在python執行的位置或者執行.py檔的位置,
找到一個poem.txt,其內容如下:

讀者如果多加嘗試的話,可以發現如果再次讀檔寫檔,
前面的內容會被全數覆蓋
除了用了write()以外,print()也可以拿來輸出到檔案,
其方法如下:

1
2
3
>>> f = open('poem.txt', 'w')
>>> print('院子落葉\n跟我的思念厚厚一疊', file=f, sep='', end='')
>>> f.close() # 要先close()以後,才會真的寫入完畢!

我們將print()的目標指向到檔案去,
所以它就不會把字串印在命令列;
同時,由於print()預設會有sep(分隔符號)和end(結束符號)
sep預設會是一個空格,end預設會是換行
所以我們要自行將其設定為空字串,避免寫進去的東西不如預期。

另一方面,如果字串很大,不適合直接一口氣處理完,
也可以考慮使用slice的方法分段處理。
(自己決定每段要寫入多少字元,搭配迴圏操作)

由於檔案讀取使用**’x’**的話會有機會產生錯誤,
在其他很多狀況下也有產生錯誤的可能,
一般我們在使用檔案處理的相關函式時,
會用try…except…來將其包含進來。

1
2
3
4
5
6
7
>>> try:
...    f = open('poem.txt', 'x')
...    f.write('窗外的麻雀')
... except FileExistsError: # 想直接全包也行啦XD!
...    print('檔案已存在!')
...
檔案已存在!

接下來是讀取檔案的部分:
讀檔就比寫入還要更在意一次讀的內容了,
Python提供了三個用來讀取的函式:
read(), readline(), readlines()
read()的本質和write()接近,就是有什麼讀什麼,
就算今天是全本小說也照讀不誤,所以特別要留意這點!
當不確定自己要讀的檔案大小時,千萬不要直接一口氣讀全部啊!

1
2
3
4
5
6
>>> f = open('poem.txt', 'r')
>>> poem = f.read()
>>> print(poem)
院子落葉
跟我的思念厚厚一疊
>>> f.close()

read()也可以傳入字數來限制每次要讀幾個字元:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
>>> f = open('poem.txt', 'r')
>>> poem = ''
>>> while True:
...     next = f.read(3) # 每次只讀3個字
...     if not next: # 讀到結尾時,會產生空字串給next
...         break
...     poem += next
...
>>> print(poem)
院子落葉
跟我的思念厚厚一疊
>>> f.close()

讀者也可以自行嘗試在迴圈中印看看next。

接下來是readline(),
readline()每次會以行為單位將檔案讀出來:
為了更明確表現整個狀況,我們先將poem.txt更改如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
>>> f.close()
>>> f = open('poem.txt', 'r')
>>> cnt = 0
>>> poem = ''
>>> while True:
...     cnt += 1
...     line = f.readline()
...     if not line: break
...     print('Line %d: %s' % (cnt, line), end='')
...     poem += line
...
Line 1: 院子落葉
Line 2: 跟我的思念厚厚一疊
Line 3:
Line 4: 幾句誓言
Line 5: 也無法將我的熱情冷卻
Line 6:    >>> f.close()
>>>

我們可以看到在readline()時,換行的符號是被算進去的,
就算尾端只有幾個空白字元 ,照樣也會被算做一行。

readlines()則又再友善一點,
會按行將讀出來的每一行為單位組成list。

1
2
3
4
>>> f = open('poem.txt', 'r')
>>> lines = f.readlines()
>>> lines
['院子落葉\n', '跟我的思念厚厚一疊\n', '\n', '幾句誓言\n', '也無法將我的熱情冷卻\n', '   ']

今天我們已經介紹了基礎的讀寫檔案,
下一篇我們再來介紹比較進一步地使用模組來讀一些常見的檔案格式。

那我們就明天見囉!

工商時間:

抽獎活動還在繼續累積人數(現在好像沒有人想抽XD)
Python Taiwan的連結第100篇的文章 底下,
公開分享到你的臉書、按讚該篇文章、並留言告訴我說,
「你最喜歡這一整個系列的哪一篇?為什麼?」或
「除了從LeetCode學演算法系列以外,
你還想要看到關於什麼方向的文章?」

超過20則留言的話 (有完成以上步驟的才算),我們就抽一組
「從Leetcode學演算法|進階篇」+「從Leetcode學演算法|面試篇」
課程的免費兌換券進行贈送!

期限嘛…就延長到滿人數吧XDD (不然也沒辦法哈哈)

容筆者工商一下,
「從Leetcode學演算法|進階篇」 開放預購啦!
這次選了40道難度加深的LeetCode題目,
同樣也會細部解說對應的技巧及須要掌握的演算法!
同時這次購買進階篇的話,
額外還加贈**「從Leetcode學演算法|面試篇」** !
當中包含了面試準備須知分享及訪談國內外不同經驗的工程師
讓你不論是想走前端/後端/一般軟工 或者是想找國外的工作
初學想轉職 還是正在工作 ,都能夠從中得到收穫呦!
有興趣的朋友可以使用下面的早鳥優惠~
「從Leetcode學演算法|進階篇」+「從Leetcode學演算法|面試篇」
https://bit.ly/advleetcode

「從Leetcode學演算法」全套(基礎/進階/面試篇)同捆優惠:
https://bit.ly/allleetcode

共發表了 171 篇文章 ‧ 總計 311.6k
使用 Hugo 建立
主題 StackJimmy 設計