使用 else、finally

May 2, 2022

tryexcept 的語法,還可以搭配 elsefinally 來使用,當 else 區塊出現時,若 try 區塊沒有發生例外,else 就會執行,如果 finally 區塊出現時,無論 try 區塊中有沒有發生例外,finally 區塊都一定會執行。

try/except/else

else 可與 tryexcept 搭配,是其他語言中不常見的,else 可與 tryexcept 搭配的原因在於,讓 try 的程式碼,盡量與可能引發例外的來源相關。例如:

numbers = input('輸入數字(空白區隔):').split(' ')
try:
    ints = [int(number) for number in numbers]
except ValueError as err:
    print(err)
else:
    print('平均', sum(ints) / len(ints)) 

在這個範例中,try 區塊集中在嘗試執行 int,緊接著的 except 用以比對 ValueError,這樣程式碼上就可以清楚地看出,intValueError 的關係,若沒有引有例外,就會執行 else 區塊以顯示結果。

在 Python 官方文件〈Errors and Exceptions〉也有個範例如下:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError: 
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

在上面的範例中,open 呼叫時若沒有因檔案開啟失敗而引發例外,就會執行 else 區塊的內容,這會比撰寫以下的程式來得好:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()
    except OSError: 
        print('cannot open', arg)

在上面的程式中,如果真的引發了例外,那到底是 open 引發的例外,還是 readlines 引發的例外呢?如果是 readlines 引發的例外,那麼 except'cannot open' 的訊息顯示,可能就誤導了除錯的方向。

因為 try 區塊沒有發生例外,else 就會執行,在測試的場合,也會看到 try/except/else 的搭配,例如:

try:
    do_some()     # 預期會拋出例外
except SomeError:
    pass
else:
    fail('測試失敗的相關訊息')

try/finally

在 Python 官方文件〈Errors and Exceptions〉的範例中,實際上 readlines 也是有可能引發例外,如果檔案順利開啟,然而 readlines 引發了例外,那麼最後的 f.close 就不會被執行,如果想確保 f.close 一定會執行,可以修改如下:

import sys

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except FileNotFoundError: 
        print('找不到檔案', arg)
    else:
        try:
            print(arg, ' 有 ', len(f.readlines()), ' 行 ')
        finally:
            f.close() 

由於 finally 區塊一定會被執行,這個範例要關閉檔案的動作,一定得是在檔案開啟成功,而 f 被指定了檔案物件之後,如果這麼撰寫:

import sys

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except FileNotFoundError: 
        print('找不到檔案', arg)
    else:
        print(arg, ' 有 ', len(f.readlines()), ' 行 ')
    finally:
        f.close()

那麼若檔案開啟失敗,就不會建立 f 變數,最後執行 finallyf.close 時,就會引發 NameError並且指出f名稱未定義。

如果程式撰寫的流程中先 return 了,而且也有寫 finally 區塊,那 finally 區塊會先執行完後,再將值傳回。例如,下面這個範例會先顯示「finally」再顯示「1」:

def test(flag: bool):
    try:
        if flag:
            return 1
    finally:
        print('finally')
    return 0

print(test(True))

分享到 LinkedIn 分享到 Facebook 分享到 Twitter