iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
0
Software Development

從零開始學Python系列 第 15

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

  • 分享至 

  • xImage
  •  

註:本文同步刊載在Medium,若習慣Medium的話亦可去那邊看呦!

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

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類型的形式

print('開始之前')
Student.getCpCount() # 開始前先看看是不是0

ming = Student('阿明', {'數學':55, '英文':70, '物理':55})
ming.readMyName()
mei = Student('小美', {'數學':90, '英文':88, '物理':100})
mei.readMyName()
howhow = Student('HowHow', {'數學':80, '英文':60, '物理':40})
howhow.readMyName()

print('\n來PK吧! 先比總分,再比加權分!\n')
ming.compare(howhow)
ming.compareE(howhow)
ming.compare(mei)
ming.compareE(mei)
mei.compare(howhow)
mei.compareE(howhow)

結果應該如下:

C:\Users\Desolve>python fromzero.py
開始之前
目前的比較次數:0
聽清楚了,我的名字是阿明!!!
聽清楚了,我的名字是小美!!!
聽清楚了,我的名字是HowHow!!!

來PK吧! 先比總分,再比加權分!

diff = 0
什麼?竟然平手?!
已比較 1 次!

self = 460, b = 460
阿明 == HowHow
已比較 2 次!

diff = -98
可...可惡,難道,這就是小美真正的實力嗎?
已比較 3 次!

self = 460, b = 620
阿明 <  小美
已比較 4 次!

diff = 98
小美贏了!
已比較 5 次!

小美 >  HowHow
已比較 6 次!

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

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

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

# 方法一
file = open(name, mode)
... (使用file來處理檔案)
file.close() # 用完要關閉檔案

# 方法二
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+',代表可讀可寫。

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

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

讀者可以在python執行的位置或者執行.py檔的位置,
找到一個poem.txt,其內容如下:
https://ithelp.ithome.com.tw/upload/images/20200927/20119871uOKFvGp3Ms.jpg
讀者如果多加嘗試的話,可以發現如果再次讀檔寫檔,
前面的內容會被全數覆蓋
除了用了write()以外,print()也可以拿來輸出到檔案,
其方法如下:

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

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

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

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

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

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

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

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

>>> 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更改如下:
https://ithelp.ithome.com.tw/upload/images/20200927/20119871bRa3MBuPL5.jpg

>>> 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。

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

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

那我們就明天見囉!


上一篇
[Day 14] 從零開始學Python - 物件與類別:我們不一樣,每個人都有不同的際遇(下)
下一篇
[Day 16] 從零開始學Python - 檔案讀寫:妳出現在我詩的每一頁(中)
系列文
從零開始學Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言