Python100Days學習筆記---Day11文件和異常

實際開發中常常會遇到對數據進行持久化操作的場景,而實現數據持久化最直接簡單的方式就是將數據保存到文件中。說到“文件”這個詞,可能需要先科普一下關於文件系統的知識,但是這裏我們並不浪費筆墨介紹這個概念,請大家自行通過維基百科進行了解。

在Python中實現文件的讀寫操作其實非常簡單,通過Python內置的open函數,我們可以指定文件名、操作模式、編碼信息等來獲得操作文件的對象,接下來就可以對文件進行讀寫操作了。這裏所說的操作模式是指要打開什麼樣的文件(字符文件還是二進制文件)以及做什麼樣的操作(讀、寫還是追加),具體的如下表所示。

操作模式	具體含義
'r'	    讀取 (默認)
'w'	    寫入(會先截斷之前的內容)
'x'	    寫入,如果文件已經存在會產生異常
'a'	    追加,將內容寫入到已有文件的末尾
'b'  	二進制模式
't'	    文本模式(默認)
'+'	    更新(既可以讀又可以寫)

下面這張圖來自於菜鳥教程網站,它展示瞭如果根據應用程序的需要來設置操作模式。

在這裏插入圖片描述
讀寫文本文件
讀取文本文件時,需要在使用open函數時指定好帶路徑的文件名(可以使用相對路徑或絕對路徑)並將文件模式設置爲’r’(如果不指定,默認值也是’r’),然後通過encoding參數指定編碼(如果不指定,默認值是None,那麼在讀取文件時使用的是操作系統默認的編碼),如果不能保證保存文件時使用的編碼方式與encoding參數指定的編碼方式是一致的,那麼就可能因無法解碼字符而導致讀取失敗。下面的例子演示瞭如何讀取一個純文本文件。

def main():
    f = open('致橡樹.txt', 'r', encoding='utf-8')
    print(f.read())
    f.close()


if __name__ == '__main__':
    main()

請注意上面的代碼,如果open函數指定的文件並不存在或者無法打開,那麼將引發異常狀況導致程序崩潰。爲了讓代碼有一定的健壯性和容錯性,我們可以使用Python的異常機制對可能在運行時發生狀況的代碼進行適當的處理,如下所示。

def main():
    f = None
    try:
        f = open('致橡樹.txt', 'r', encoding='utf-8')
        print(f.read())
    except FileNotFoundError:
        print('無法打開指定的文件!')
    except LookupError:
        print('指定了未知的編碼!')
    except UnicodeDecodeError:
        print('讀取文件時解碼錯誤!')
    finally:
        if f:
            f.close()


if __name__ == '__main__':
    main()

在Python中,我們可以將那些在運行時可能會出現狀況的代碼放在try代碼塊中,在try代碼塊的後面可以跟上一個或多個except來捕獲可能出現的異常狀況。例如在上面讀取文件的過程中,文件找不到會引發FileNotFoundError,指定了未知的編碼會引發LookupError,而如果讀取文件時無法按指定方式解碼會引發UnicodeDecodeError,我們在try後面跟上了三個except分別處理這三種不同的異常狀況。最後我們使用finally代碼塊來關閉打開的文件,釋放掉程序中獲取的外部資源,由於finally塊的代碼不論程序正常還是異常都會執行到(甚至是調用了sys模塊的exit函數退出Python環境,finally塊都會被執行,因爲exit函數實質上是引發了SystemExit異常),因此我們通常把finally塊稱爲“總是執行代碼塊”,它最適合用來做釋放外部資源的操作。如果不願意在finally代碼塊中關閉文件對象釋放資源,也可以使用上下文語法,通過with關鍵字指定文件對象的上下文環境並在離開上下文環境時自動釋放文件資源,代碼如下所示。

def main():
    try:
        with open('致橡樹.txt', 'r', encoding='utf-8') as f:
            print(f.read())
    except FileNotFoundError:
        print('無法打開指定的文件!')
    except LookupError:
        print('指定了未知的編碼!')
    except UnicodeDecodeError:
        print('讀取文件時解碼錯誤!')


if __name__ == '__main__':
    main()

除了使用文件對象的read方法讀取文件之外,還可以使用for-in循環逐行讀取或者用readlines方法將文件按行讀取到一個列表容器中,代碼如下所示

import time


def main():
    # 一次性讀取整個文件內容
    with open('致橡樹.txt', 'r', encoding='utf-8') as f:
        print(f.read())

    # 通過for-in循環逐行讀取
    with open('致橡樹.txt', mode='r') as f:
        for line in f:
            print(line, end='')
            time.sleep(0.5)
    print()

    # 讀取文件按行讀取到列表中
    with open('致橡樹.txt') as f:
        lines = f.readlines()
    print(lines)
    

if __name__ == '__main__':
    main()

要將文本信息寫入文件文件也非常簡單,在使用open函數時指定好文件名並將文件模式設置爲’w’即可。注意如果需要對文件內容進行追加式寫入,應該將模式設置爲’a’。如果要寫入的文件不存在會自動創建文件而不是引發異常。下面的例子演示瞭如何將1-9999之間的素數分別寫入三個文件中(1-99之間的素數保存在a.txt中,100-999之間的素數保存在b.txt中,1000-9999之間的素數保存在c.txt中)。

from math import sqrt


def is_prime(n):
    """判斷素數的函數"""
    assert n > 0
    for factor in range(2, int(sqrt(n)) + 1):
        if n % factor == 0:
            return False
    return True if n != 1 else False


def main():
    filenames = ('a.txt', 'b.txt', 'c.txt')
    fs_list = []
    try:
        for filename in filenames:
            fs_list.append(open(filename, 'w', encoding='utf-8'))
        for number in range(1, 10000):
            if is_prime(number):
                if number < 100:
                    fs_list[0].write(str(number) + '\n')
                elif number < 1000:
                    fs_list[1].write(str(number) + '\n')
                else:
                    fs_list[2].write(str(number) + '\n')
    except IOError as ex:
        print(ex)
        print('寫文件時發生錯誤!')
    finally:
        for fs in fs_list:
            fs.close()
    print('操作完成!')


if __name__ == '__main__':
    main()

讀寫二進制文件
知道了如何讀寫文本文件要讀寫二進制文件也就很簡單了,下面的代碼實現了複製圖片文件的功能。

def main():
    try:
        with open('guido.jpg', 'rb') as fs1:
            data = fs1.read()
            print(type(data))  # <class 'bytes'>
        with open('吉多.jpg', 'wb') as fs2:
            fs2.write(data)
    except FileNotFoundError as e:
        print('指定的文件無法打開.')
    except IOError as e:
        print('讀寫文件時出現錯誤.')
    print('程序執行結束.')


if __name__ == '__main__':
    main()

讀寫JSON文件
通過上面的講解,我們已經知道如何將文本數據和二進制數據保存到文件中,那麼這裏還有一個問題,如果希望把一個列表或者一個字典中的數據保存到文件中又該怎麼做呢?答案是將數據以JSON格式進行保存。JSON是“JavaScript Object Notation”的縮寫,它本來是JavaScript語言中創建對象的一種字面量語法,現在已經被廣泛的應用於跨平臺跨語言的數據交換,原因很簡單,因爲JSON也是純文本,任何系統任何編程語言處理純文本都是沒有問題的。目前JSON基本上已經取代了XML作爲異構系統間交換數據的事實標準。關於JSON的知識,更多的可以參考JSON的官方網站,從這個網站也可以瞭解到每種語言處理JSON數據格式可以使用的工具或三方庫,下面是一個JSON的簡單例子。

{
    "name": "駱昊",
    "age": 38,
    "qq": 957658,
    "friends": ["王大錘", "白元芳"],
    "cars": [
        {"brand": "BYD", "max_speed": 180},
        {"brand": "Audi", "max_speed": 280},
        {"brand": "Benz", "max_speed": 320}
    ]
}

可能大家已經注意到了,上面的JSON跟Python中的字典其實是一樣一樣的,事實上JSON的數據類型和Python的數據類型是很容易找到對應關係的,如下面兩張表所示。

JSON					Python
object					dict
array					list
string					str
number (int / real)		int / float
true / false			True / False
null					None
Python									JSON
dict									object
list, tuple								array
str										string
int, float, int- & float-derived Enums	number
True / False							true / false
None									null

我們使用Python中的json模塊就可以將字典或列表以JSON格式保存到文件中,代碼如下所示。

import json


def main():
    mydict = {
        'name': '駱昊',
        'age': 38,
        'qq': 957658,
        'friends': ['王大錘', '白元芳'],
        'cars': [
            {'brand': 'BYD', 'max_speed': 180},
            {'brand': 'Audi', 'max_speed': 280},
            {'brand': 'Benz', 'max_speed': 320}
        ]
    }
    try:
        with open('data.json', 'w', encoding='utf-8') as fs:
            json.dump(mydict, fs)
    except IOError as e:
        print(e)
    print('保存數據完成!')


if __name__ == '__main__':
    main()

json模塊主要有四個比較重要的函數,分別是:

dump - 將Python對象按照JSON格式序列化到文件中
dumps - 將Python對象處理成JSON格式的字符串
load - 將文件中的JSON數據反序列化成對象
loads - 將字符串的內容反序列化成Python對象

這裏出現了兩個概念,一個叫序列化,一個叫反序列化。自由的百科全書維基百科上對這兩個概念是這樣解釋的:“序列化(serialization)在計算機科學的數據處理中,是指將數據結構或對象狀態轉換爲可以存儲或傳輸的形式,這樣在需要的時候能夠恢復到原先的狀態,而且通過序列化的數據重新獲取字節時,可以利用這些字節來產生原始對象的副本(拷貝)。與這個過程相反的動作,即從一系列字節中提取數據結構的操作,就是反序列化(deserialization)”。

目前絕大多數網絡數據服務(或稱之爲網絡API)都是基於HTTP協議提供JSON格式的數據,關於HTTP協議的相關知識,可以看看阮一峯老師的《HTTP協議入門》,如果想了解國內的網絡數據服務,可以看看聚合數據和阿凡達數據等網站,國外的可以看看{API}Search網站。下面的例子演示瞭如何使用requests模塊(封裝得足夠好的第三方網絡訪問模塊)訪問網絡API獲取國內新聞,如何通過json模塊解析JSON數據並顯示新聞標題,這個例子使用了天行數據提供的國內新聞數據接口,其中的APIKey需要自己到該網站申請。

import requests
import json


def main():
    resp = requests.get('http://api.tianapi.com/guonei/?key=APIKey&num=10')
    data_model = json.loads(resp.text)
    for news in data_model['newslist']:
        print(news['title'])


if __name__ == '__main__':
    main()

在Python中要實現序列化和反序列化除了使用json模塊之外,還可以使用pickle和shelve模塊,但是這兩個模塊是使用特有的序列化協議來序列化數據,因此序列化後的數據只能被Python識別。關於這兩個模塊的相關知識可以自己看看網絡上的資料。另外,如果要了解更多的關於Python異常機制的知識,可以看看segmentfault上面的文章《總結:Python中的異常處理》,這篇文章不僅介紹了Python中異常機制的使用,還總結了一系列的最佳實踐,很值得一讀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章