文章目錄
- 讀取文件
- 讀取文件的全部內容
- 相對路徑和絕對路徑
- 訪問文件中的各行
- 使用文件的內容
- 包含100萬位的大型文件
- 圓周率值中包含你的生日嗎?
- 寫入文件
- 寫入一行
- 寫入多行
- 異常
- 處理ZeroDivisionError異常
- 使用try-except代碼塊
- else代碼塊
- 處理FileNotFoundError異常
- 分析文本
- 使用多個文件
- 靜默失敗
- 決定報告哪些錯誤
- 存儲數據
- 使用json.dump()和json.load()
- 保存和讀取用戶生成的數據
- 重構
- 往期
- 代碼倉庫
從第一到第十章,我們學習了編寫程序的所需的基本技能。
在本章節中,我們將學習如何處理文件,以及如何使用異常處理錯誤。
讀取文件
文本文件可以存儲許多數據:天氣數據、交通數據、社會經濟數據、文學作品等。
要使用文本文件中的信息,首先需要將信息讀取到內存中。既可以一次性讀取文件的全部內容,也可以逐行讀取。
讀取文件的全部內容
我們先準備一個文件,名為pi_digits.txt,內容如下:
3.141592653589793238462643383279
然后我們開始編寫第一個讀取文件的代碼file_reader.py:
from pathlib import Pathpath = Path('pi_digits.txt')
contents = path.read_text()
print(contents)
要使用文件的內容,需要將其路徑告知 Python。
路徑(path)是文件或文件夾在系統中的準確位置。
Python 提供了 pathlib 模塊,能處理各種操作系統中處理文件和目錄。
首先中 pathlib 模塊導入 Path 類,再使用 Path 對像指向一個文件。
這里創建了一個表示文件 pi_digits.txt 的 Path 對象,并將其賦給了變量 path由于這個文件與當前編寫的 .py 文件位于同一個目錄中,因此 Path 只需要知道其文件名就能訪問它。
創建表示文件 pi_digits.txt 的 Path 對象后,使用 read_text() 方法來讀取這個文件的全部內容。
**read_text()**將該文件的全部內容作為一個字符串返回。我們將這個字符串賦給了變量 contents,再將其打印出來。
相比原始原件,輸出唯一不同的地方是末尾多了一個空行。
因為 read_text()在到達文件末尾時會返回一個空字符串,而這個空字符串會被顯示一個空行。
要刪除這個多出來的空行,可對字符串變量contents調用 strip() 方法。
contents = path.read_text().rstrip()
這種方式稱為方法鏈式調用。
博主使用的 3.13版本已經沒有了這個問題。
相對路徑和絕對路徑
指定路徑的方式有兩種:
- 相對文件路徑讓 Python 到相對于當前運行的程序文件所在的目錄去查找。
- 絕對文件路徑讓 Python 去系統的準確位置去查找。
在相對路徑行不通時,可使用絕對路徑。
絕對路徑通常比相對路徑長,因為以系統的根文件夾為起點。
在 Linux 系統中,系統的根文件夾為 /,而在 Windows 系統中,系統的根文件夾為 C:\。
path = Path('/home/eric/data_files/text_files/filename.txt')
在 Windows 系統中,可使用反斜杠(\)而不是斜杠(/)來分隔路徑中的文件夾。
訪問文件中的各行
使用 splitlines() 方法將冗長的字符串轉換為一系列行,再使用 for 循環以每次一行的方式檢查文件中的各行
from pathlib import Pathpath = Path('pi_digits.txt')
contents = path.read_text()lines = contents.splitlines()
for line in lines:print(line)
和前面一樣,讀取文件的全部內容,由于沒有修改行,因此輸出與文件內容相同。
使用文件的內容
將文件的內容讀取到內存中后,就可以以任何方式使用這些數據了。
首先,創建一個字符串,它包含文件中存儲的所有數字。
from pathlib import Pathpath = Path('pi_digits.txt')
contents = path.read_text()lines = contents.splitlines()
# 創建變量 pi_string
pi_string = ''
for line in lines:pi_string += lineprint(pi_string)
print(len(pi_string))
輸出如下:
3.1415926535 8979323846 2643383279
32
變量pi_string存儲的字符串包含原來位于每行左端的空格。要刪除這些空格,可對每行調用lstrip()方法。
for line in lines:pi_string += line.lstrip()
輸出如下:
3.141592653589793238462643383279
32
注意:在讀取文本文件時,Python將其中的所有文本都解釋為字符串。
如果讀取的是數字,并要將其作為數值使用,就必須使用函數 int() 將其轉換為整數,或使用函數 float() 將其轉換為浮點數。
包含100萬位的大型文件
如果一個文本文件包含精確到小數點后 1 000 000 位而不是 30 位的圓周率值,也可以創建一個包含所有這些數字的字符串。
在這個例子中,我們將使用一個包含 100 000 位的圓周率值的文本文件,該文件的第一行只包含前 50 位。
from pathlib import Pathpath = Path('pi_million_digits.txt')
contents = path.read_text()
lines = contents.splitlines()
pi_string = ''
for line in lines:pi_string += line.lstrip()print(f"{pi_string[:52]}...")
print(len(pi_string))
圓周率值中包含你的生日嗎?
現在我們知道如何使用 Python 來讀取文件,我們來編寫一個程序,看看圓周率值中包含你的生日嗎?
from pathlib import Pathpath = Path('pi_million_digits.txt')
contents = path.read_text()
lines = contents.splitlines()
pi_string = ''
for line in lines:pi_string += line.lstrip()
birthday = input("Enter your birthday, in the form mmddyy: ")
if birthday in pi_string:print("Your birthday appears in the first million digits of pi!")
else:print("Your birthday does not appear in the first million digits of pi.")
寫入文件
保存數據的最簡單的方式之一是將其寫入文件。
寫入一行
定義一個文件的路徑后,就可使用 write_text() 將數據寫入該文件了。
from pathlib import Pathpath = Path("programming.txt")
# write_text() 方法接受單個實參,即要寫入文件的字符串。
path.write_text("I love programming.\n")
。這個程序沒有終端輸出,但你如果打開文件 programming.txt。
注意:Python 只能將字符串寫入文本文件。要將數值數據存儲到文本文件中,必須先使用函數 str() 將其轉換為字符串格式。
寫入多行
write_text() 方法會在幕后完成幾項工作。
首先,如果 path 變量對應的路徑指向的文件不存在,就創建它。
其次,將字符串寫入文件后,它會確保文件得以妥善地關閉。
要將多行寫入文件,需要先創建一個字符串(其中包含要寫入文件的全部內容),再調用 write_text() 并將這個字符串傳遞給它。
from pathlib import Path
contents = "I love programming.\n"
contents += "I love creating new games.\n"
contents += "I also love working with data.\n"path = Path('programming.txt')
path.write_text(contents)
在對 path 對象調用 write_text() 方法時,務必謹慎。如果指定的文件已存在, write_text() 將刪除其內容,再將指定的文本寫入其中。
異常
Python 使用稱為異常(exception)的特殊對象來管理程序執行期間發生的錯誤。
異常是使用 try-except 代碼塊處理的。
處理ZeroDivisionError異常
print(5/0)
輸出如下:
Traceback (most recent call last):File "C:\Users\Administrator\PycharmProjects\pythonProject\demo.py", line 1, in <module>print(5/0) # 報錯
ZeroDivisionError: division by zero
在上述 traceback 中,錯誤 ZeroDivisionError 是個異常對象。
Python 在無法按你的要求做時,就會創建這種對象。
使用try-except代碼塊
try:print(5/0)
except ZeroDivisionError:print("You can't divide by zero!")
輸出如下:
You can't divide by zero!
當你認為可能發生了錯誤時,可編寫一個 try 語句來告訴 Python,你要執行的操作可能引發這種錯誤。
else代碼塊
只有 try 代碼塊成功執行才需要繼續執行的代碼,都應放到 else 代碼塊中
--snip - -
while True:--snip - -if second_number == 'q':break
try:answer = int(first_number) / int(second_number)
except ZeroDivisionError:print("You can't divide by 0!")
else:print(answer)
如果除法運算成功,就使用 else 代碼塊來打印結果
處理FileNotFoundError異常
在使用文件時,一種常見的問題是找不到文件:要查找的文件可能在其他地方,文件名可能不正確,或者這個文件根本就不存在。
from pathlib import Pathpath = Path('alice.txt')
contents = path.read_text(encoding='utf-8')
這里使用 read_text() 的方式與前面稍有不同。如果系統的默認編碼與要讀取的文件的編碼不一致,參數 encoding 必不可少。
Python 無法讀取不存在的文件,因此引發了一個異常FileNotFoundError
通常最好從 traceback 的末尾著手。從最后一行可知,引發了異常 FileNotFoundError。
from pathlib import Pathpath = Path('alice.txt')
try:contents = path.read_text(encoding='utf-8')
except FileNotFoundError:print(f"Sorry, the file {path} does not exist.")
這個示例中,try 代碼塊中的代碼引發了 FileNotFoundError 異常,因此要編寫一個與該異常匹配的 except 代碼塊。
這樣,當找不到文件時,Python 將運行 except 代碼塊中的代碼,從而顯示一條友好的錯誤消息,而不是 traceback。
分析文本
from pathlib import Pathpath = Path('alice.txt')
try:contents = path.read_text(encoding='utf-8')
except FileNotFoundError:print(f"Sorry, the file {path} does not exist.")
else:# 計算文件大致包含多少個單詞words = contents.split()num_words = len(words)print(f"The file {path} has about {num_words} words.")
僅當 try 代碼塊成功執行時才會執行它們。輸出指出了文件 alice.txt 包含多少個單詞。
使用多個文件
下面分析幾本書
from pathlib import Pathdef count_words(filename):"""計算一個文件大致包含多少個單詞。"""try:contents = Path(filename).read_text(encoding='utf-8')except FileNotFoundError:msg = f"Sorry, the file {filename} does not exist."print(msg)else:# 計算文件大致包含多少個單詞。words = contents.split()num_words = len(words)print(f"The file {filename} has about {num_words} words.")filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:count_words(filename)
先將文件名存儲為簡單字符串,然后將每個字符串轉換為 Path 對象,再調用 count_words()。
在這個示例中,使用 try-except 代碼塊有兩個重要的優點:一是避免用戶看到 traceback,二是讓程序可以繼續分析能夠找到的其他文件。
靜默失敗
Python 有一個 pass 語句,可在代碼塊中使用它來讓 Python 什么都不做。
def count_words(path):"""計算一個文件大致包含多少個單詞"""try:--snip--except FileNotFoundError:passelse:--snip--
當這種錯誤發生時,既不會出現 traceback,也沒有任何輸出。用戶將看到存在的每個文件包含多少個單詞,但沒有任何跡象表明有一個文件未找到
決定報告哪些錯誤
只要程序依賴于外部因素,如用戶輸入、是否存在指定的文件、是否有網絡連接,就有可能出現異常。憑借經驗可判斷該在程序的什么地方包含異常處理塊,以及出現錯誤時該向用戶提供多少相關的信息。
存儲數據
一種簡單的方式是使用模塊 json 來存儲數據。
使用json.dump()和json.load()
第一個程序將使用 json.dumps() 來存儲這組數,而第二個程序將使用 json.loads() 來讀取它們。
from pathlib import Path
import jsonnumbers = [2,3,5,7,11,13]path = Path('numbers.json')
contents = json.dumps(numbers)
path.write_text(contents)
首先導入模塊 json,并創建一個數值列表。然后選擇一個文件名,指定要將該數值列表存儲到哪個文件中。
接下來,使用 json.dumps() 函數生成一個字符串,將其存儲到變量 contents 中。
最后,使用 Path 對象的 write_text() 方法將這個字符串寫入到文件 numbers.json 中。
使用 json.loads() 將這個列表讀取到內存中
from pathlib import Path
import jsonpath = Path('numbers.json')
contents = path.read_text()
numbers = json.loads(contents)
print(numbers)
這個數據文件是使用特殊格式的文本文件,因此可使用 read_text() 方法來讀取它。然后將這個文件的內容傳遞給 json.loads()。這個函數將一個 JSON 格式的字符串作為參數,并返回一個 Python 對象(這里是一個列表),而我們將這個對象賦給了變量 numbers。最后,打印恢復的數值列表
保存和讀取用戶生成的數據
使用 json 保存用戶生成的數據很有必要
from pathlib import Path
import json# 首先,提示用戶輸入名字
username = input("請輸入你的用戶名:")# 接下來,將收集到的數據寫入文件 username.json
path = Path('username.json')
contents = json.dump(username)
path.write_text(contents)# 然后,打印一條消息,指出存儲了用戶輸入的信息
print(f"我們將記住你的用戶名,{username}")
再編寫一個程序,向名字已被存儲的用戶發出問候
from pathlib import Path
import jsonpath = Path('username.json')
contents = path.read_text()
username = json.loads(contents)print(f"歡迎回來,{username}!")
我們讀取數據文件的內容,并使用 json.loads() 將恢復的數據賦給變量 userusername
from pathlib import Path
import jsonpath = Path('username.json')
# 如果指定的文件或文件夾存在,exists() 方法返回 True,否則返回 False。
# 這里使用 path.exists() 來確定是否存儲了用戶名
if path.exists():# 如果文件 username.json 存在,就加載其中的用戶名,并向用戶發出個性化問候contents = path.read_text()username = json.loads(contents)print(f"歡迎回來,{username}!")
else:# 如果文件 username.json 不存在,就提示用戶輸入用戶名,并存儲用戶輸入的值。username = input("請輸入你的用戶名:")contents = json.dumps(username)path.write_text(contents)print(f"我們將記住你的用戶名,{username}")
重構
雖然代碼能夠正確地運行,但還可以將其劃分為一系列完成具體工作的函數來進行改進。這樣的過程稱為重構。
重構讓代碼更清晰、更易于理解、更容易擴展。
from pathlib import Path
import jsondef greet_user():"""問候用戶,并指出其名字。"""path = Path('users.json')if path.exists():contents = path.read_text()username = json.loads(contents)print(f'歡迎回來{username}')else:username = input('請輸入你的名字:')contents = json.dumps(username)path.write_text(contents)print(f'我們將記住你的名字{username}')greet_user()
greet_user() 函數所做的不僅是問候用戶,還在存儲了用戶名時獲取它,在沒有存儲用戶名時提示用戶輸入。
下面重構 greet_user(),不讓它執行這么多任務。
from pathlib import Path
import json# 如果存儲了用戶名,就獲取并返回它;如果傳遞給 get_stored_username() 的路徑不存在,就返回 None
def get_stored_username(path):"""如果存儲了用戶名,就獲取它"""if path.exists():contents = path.read_text()username = json.loads(contents)return usernameelse:# 這是一種不錯的做法:函數要么返回預期的值,要么返回 None。return Nonedef get_stored_username(path):"""提示用戶輸入用戶名"""username = input('請輸入你的名字? ')contents= json.dumps(username)path.write_text(contents)return usernamedef greet_user():"""問候用戶,并指出其名字"""path = Path('username.json')username = get_stored_username(path)if username:print(f"Welcome back, {username}!")else:username = get_stored_username(path)print(f"We'll remember you when you come back, {username}!")greet_user()
在 remember_me.py 的這個最終版本中,每個函數都執行單一而清晰的任務。我們調用 greet_user(),它打印一條合適的消息:要么歡迎老用戶回來,要么問候新用戶。
首先調用 get_stored_username(),這個函數只負責獲取已存儲的用戶名(如果存儲了),再在必要時調用 get_new_username(),這個函數只負責獲取并存儲新用戶的用戶名。要編寫出清晰且易于維護和擴展的代碼,這種劃分必不可少。
往期
- Python3完全新手小白的學習手冊 9 類
- Python3完全新手小白的學習手冊 8 函數
- Python3完全新手小白的學習手冊 7 用戶輸入和while循環
- Python3完全新手小白的學習手冊 6 字典
- Python3完全新手小白的學習手冊 5 if語句
- Python3完全新手小白的學習手冊 4 操作列表
- Python3完全新手小白的學習手冊 3 列表
- Python3完全新手小白的學習手冊 2 變量和簡單數據類型
- Python3完全新手小白的學習手冊 1 Python 的安裝
代碼倉庫
代碼倉庫