Open函數

open函數可以讓我們開啟檔案,並以各種方式存取

1
file = open("result.txt", mode='r', encoding='utf8')
  • 第一個參數放檔案名稱,需要加副檔名
  • 第二個參數指定為mode
    • r (read)只讀取
    • w (write)只寫入(任何寫入會直接覆蓋掉所有內容)
    • r+ 讀取後會需要重新寫入 (會加在檔案結尾,原本內容會保留)
    • w+ 寫入後需要重新讀取 (任何寫入也會覆蓋原本內容)
  • 第三個參數指定為encoding,若沒有其他特殊編碼需求一律填utf8

file會變成一個物件,內含檔案的資訊,還有檔案的內容
但不能直接print出來,需要用內建的方法讀取
但檔案會一直保持開啟,為了良好的電腦資源管理,結束操作需要關閉

1
file.close()

自動關閉檔案

如果你覺得每次都要手動關閉檔案很麻煩,而且你開啟檔案也只做一兩個動作,可以像這樣寫:

1
2
3
with open("result.txt", mode='r', encoding='utf8') as file:
# 做動作
# 這裡是有縮排的喔

像這樣用with來執行的句子,在動作結束後會自動關閉這個區塊佔用的資源,所以file在執行結束後會自動被消滅

讀取純文字檔

1
2
3
hello
你好
konichiwa

read

1
2
3
4
5
6
7
content = file.read()
print(content)

# 印出以下內容:
# hello
# 你好
# konichiwa

這個方法可以一次讀取全部內容並存成字串,換行也會儲存

readline

1
2
3
4
line = file.readline()
while line:
print(line)
line = file.readline()

一次readline() 會讀取一行,所以用迴圈來不斷重複讀取下一行
要注意的是,會讀取到換行符,所以如果有需要的話可以改成print(line.strip())來去除

readlines

1
2
3
4
5
6
file = open('main.txt', mode='r', encoding='utf8')
content = file.readlines()
print(content)

# 會印出以下內容:
['hello\n', '你好\n', 'konichiwa']

這個方法會把每一行內容都存到一個陣列裡面,用這個方法可以做更靈活的應用

也可以從這裡看到\n 被存進去了,這就是換行符

write

1
2
result = "pneumonoultramicroscopicsilicovolcanoconiosis"
file.write(result)

用這個方法可以寫入內容,寫入方式根據開啟的方法會有不同

讀取json檔

json檔就是字典格式的檔案,你可以想像成把字典存到檔案裡,當然也可以存陣列
不過如果像文字檔直接讀取,讀出來會變一行一行文字而不是可以用Python操作的字典

所以需要特別的模組來處理

1
2
3
4
import json

file = open('main.json', mode='r+', encoding='utf8')
data = json.load(file) # 這個data會變成一個字典

像這樣就可以做字典讀取了
通常開啟json檔案會同時需要讀取跟寫入,所以用r+開啟

利用data的修改可以對資料進行更改和讀取
但寫入時我們需要覆蓋之前的所有內容,因為data已經包含有改動和沒有改動的部分了

這時候我們需要另外的方法來在r+模式下蓋掉原本的內容

1
2
3
4
# 前面已經做完修改了

file.seek(0) # 把讀取的指針移到檔案開頭
file.truncate() # 把指針後面的內容清掉,因為指針在開頭所以清掉整份檔案

最後我們要寫入時,也不是用write(),而是用json的方法

1
json.dump(data, file, ensure_ascii=False, indent=4)

前兩個參數填data和file,表示把data推進去file裏面(此時檔案是空的)

  • ensure_ascii: 讓非英文部分變成ASCII碼,是基於拉丁字母的一套電腦字元編碼標準。預設開啟的話,非英文的部分會變成特殊編碼,直接開啟檔案的時候不易閱讀
  • indent: 縮排,自動幫你排版,設為4可以跟Python一樣4格

機器人應用:簡單計分系統

用使用者ID當作字典鍵值儲存
但使用者ID會是整數,需要用str()來轉換

前置工作

先創建一個score.json ,並在裡面打兩個大括號{}表示空的字典
如果裡面沒有東西,json.load()的時候辨識不到這是一個字典,會報錯

寫入分數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@bot.tree.command()
async def write_score(interaction:discord.Interaction, user:discord.User, score:int):
await interaction.response.defer()
file = open('score.json', mode='r+', encoding='utf8')
data = json.load(file)

userId = str(user.id) # 把使用者的id轉成字串,不然不能儲存成字典
data[userId] = score # 修改該使用者的分數

file.seek(0)
file.truncate()
json.dump(data, file, ensure_ascii=False, indent=4)

file.close()

await interaction.followup.send(f"{user.name}的分數已經修改為{score}")

讀取分數

1
2
3
4
5
6
7
8
9
10
11
@bot.tree.command()
async def get_score(interaction:discord.Interaction, user:discord.User):
await interaction.response.defer()
file = open('score.json', mode='r', encoding='utf8')
data = json.load(file)
file.close()

userId = str(user.id)
score = data.get(userId) # 這裡使用get()函數,下方講解

await interaction.followup.send(f"{user.name}的分數是{score}")

get() 是字典專屬的方法,也是取得值,所以以下兩種方法的結果是一樣的

1
2
score = data[userId]
score = data.get(userId)

但重點是,如果該使用者之前沒有分數,也就是字典裡面沒有他的資料,這時候使用第一種方法會報錯,因為找不到該使用者

如果使用get() ,找不到資料時會返回None ,而不會報錯,用這個方法可以確保result一定有東西且不會產生錯誤,最後可能會返回老趙的分數是None

如果想要在None的時候設為0,可自行增加條件判斷

1
2
if not score:
score = 0