1. 程式人生 > 實用技巧 >第34講:豐富的else語句及簡潔的with語句

第34講:豐富的else語句及簡潔的with語句

一 else語句搭配不同的語句使用

1、跟if語句搭配:組成要麼怎樣,要麼不怎樣

  • 特點:根據判斷條件決定是否執行else語句的內容
  • 舉例:
    1 def if_else():
    2     if 1 > 2:
    3         print('這是錯誤的')
    4     else:
    5         print('這才是正確的')

2、跟while和for語句搭配,else只在迴圈完成後執行:組成幹完了能怎樣,幹不完就別想怎樣

  • 特點:
    • 如果迴圈內容全部執行完,則一定會執行else語句的內容
    • 如果迴圈內容執行過程中,用break語句跳出了迴圈,則一定不會執行else語句的內容
  • 舉例:
     1 def showMaxFactor(num):
     2     count = num // 2
     3     while count > 1:
     4         if num % count == 0:
     5             print('%d最大的約數是%d'%(num,count))
     6             break
     7         count -= 1
     8     else:
     9         print('%d是素數@!'%num)
    10 num = int(input('請輸入一個整數:'))
    11 showMaxFactor(num)

3、跟異常處理語句:組成沒有問題,那就幹吧

  • 特點:沒有異常的時候,才會執行else語句的內容
  • 舉例:
    • 下面的程式碼有異常,所以會執行出錯啦的語句
    • 1 try:
      2     int('abc')
      3 except ValueError as reason:
      4     print('出錯啦:'+ str(reason))
      5 else:
      6     print('沒有任何異常!')
    • 下面的程式碼沒有異常,會執行沒有任何異常的語句
    • 1 try:
      2     int('123')
      3 except ValueError as reason:
      4     print('出錯啦:
      '+ str(reason)) 5 else: 6 print('沒有任何異常!')

參考內容:https://www.cnblogs.com/qinguodong/p/10893010.html

二 with語句

1 適用場景:

  • with 語句是從 Python 2.5 開始引入的一種與異常處理相關的功能。
  • with語句的目的在於從流程圖中把 try,except 和finally 關鍵字和資源分配釋放相關程式碼統統去掉,簡化try….except….finlally的處理流程。
  • with 語句適用於對資源進行訪問的場合,確保不管使用過程中是否發生異常都會執行必要的”清理”操作,釋放資源,比如檔案使用後自動關閉、執行緒中鎖的自動獲取和釋放等。

2 上下文管理器

  • 上下文管理協議(Context Management Protocol):包含方法enter() 和exit(),支援該協議的物件要實現這兩個方法。
  • 上下文管理器(Context Manager):支援上下文管理協議的物件,這種物件實現了enter() 和exit() 方法。上下文管理器定義執行 with 語句時要建立的執行時上下文,負責執行 with 語句塊上下文中的進入與退出操作。通常使用 with 語句呼叫上下文管理器,也可以通過直接呼叫其方法來使用。
  • 執行時上下文(runtime context):由上下文管理器建立,通過上下文管理器的enter() 和exit() 方法實現,enter() 方法在語句體執行之前進入執行時上下文,exit() 在語句體執行完後從執行時上下文退出。with 語句支援執行時上下文這一概念。
  • 上下文表達式(Context Expression):with 語句中跟在關鍵字 with 之後的表示式,該表示式要返回一個上下文管理器物件。
  • 語句體(with-body):with 語句包裹起來的程式碼塊,在執行語句體之前會呼叫上下文管理器的enter() 方法,執行完語句體之後會執行exit() 方法。

3 基本語法和工作原理

  • 語法格式
    • 1 with context_expression [as target(s)]:
      2         with-body
    • 這裡 contextexpression 要返回一個上下文管理器物件,該物件並不賦值給 as 子句中的 target(s) ,如果指定了 as 子句的話,會將上下文管理器的_enter() 方法的返回值賦值給 target(s)。target(s) 可以是單個變數,或者由”()”括起來的元組(不能是僅僅由”,”分隔的變數列表,必須加”()”)。
    • Python 對一些內建物件進行改進,加入了對上下文管理器的支援,可以用於 with 語句中,比如可以自動關閉檔案、執行緒鎖的自動獲取和釋放等。
  • 使用with語句操作檔案物件
    • 特點:
      • 不管在處理檔案過程中是否發生異常,都能保證 with 語句執行完畢後已經關閉了開啟的檔案控制代碼
      • 使用 with 語句可以減少編碼量。已經加入對上下文管理協議支援的還有模組 threading、decimal 等。
    • 舉例:
      • with語句實現
      • 1 try:
        2     with open('data.txt','w') as f:
        3         for each_line in f:
        4             print(each_line)     
        5 except OSError as reason:
        6     print("出錯啦"+str(reason))
        7     
      • else語句實現
      • 1 try:
        2     f=open('data.txt','w')
        3     for each_line in f:
        4         print(each_line)
        5 except OSError as reason:
        6     print("出錯啦"+str(reason))
        7 else:
        8     f.close()
  • with語句執行過程
    • 舉例:
    •  1 context_manager = context_expression
       2     exit = type(context_manager).__exit__
       3     value = type(context_manager).__enter__(context_manager)
       4     exc = True   # True 表示正常執行,即便有異常也忽略;False 表示重新丟擲異常,需要對異常進行處理
       5     try:
       6         try:
       7             target = value  # 如果使用了 as 子句
       8             with-body     # 執行 with-body
       9         except:
      10             # 執行過程中有異常發生
      11             exc = False
      12             # 如果 __exit__ 返回 True,則異常被忽略;如果返回 False,則重新丟擲異常
      13             # 由外層程式碼對異常進行處理
      14             if not exit(context_manager, *sys.exc_info()):
      15                 raise
      16     finally:
      17         # 正常退出,或者通過 statement-body 中的 break/continue/return 語句退出
      18         # 或者忽略異常退出
      19         if exc:
      20             exit(context_manager, None, None, None)
      21         # 預設返回 None,None 在布林上下文中看做是 False
    • 執行過程:
      • 執行 context_expression,生成上下文管理器 context_manager
      • 呼叫上下文管理器的enter() 方法;如果使用了 as 子句,則將enter() 方法的返回值賦值給 as 子句中的 target(s)
      • 執行語句體 with-body
      • 不管是否執行過程中是否發生了異常,執行上下文管理器的exit() 方法,exit() 方法負責執行”清理”工作,如釋放資源等。如果執行過程中沒有出現異常,或者語句體中執行了語句 break/continue/return,則以 None 作為引數呼叫exit(None, None, None) ;如果執行過程中出現異常,則使用 sys.excinfo 得到的異常資訊為引數呼叫_exit(exc_type, exc_value, exc_traceback)
      • 出現異常時,如果exit(type, value, traceback) 返回 False,則會重新丟擲異常,讓with 之外的語句邏輯來處理異常,這也是通用做法;如果返回 True,則忽略異常,不再對異常進行處理

參考內容:https://developer.ibm.com/zh/articles/os-cn-pythonwith/

三 課後習題

測試題

0. 在 Python 中,else 語句能跟哪些語句進行搭配?
答:在 Python 中,else 語句不僅能跟 if 語句搭,構成“要麼怎樣,要麼不怎樣”的語境;Ta 還能跟迴圈語句(for 語句或者 while 語句),構成“幹完了能怎樣,幹不完就別想怎樣”的語境;其實 else 語句還能夠跟我們剛剛講的異常處理進行搭配,構成“沒有問題,那就幹吧”的語境。

1. 請問以下例子中,迴圈中的 break 語句會跳過 else 語句嗎?

 1 def showMaxFactor(num):
 2     count = num // 2
 3     while count > 1:
 4         if num % count == 0:
 5             print('%d最大的約數是%d' % (num, count))
 6             break
 7         count -= 1
 8     else:
 9         print('%d是素數!' % num)
10 num = int(input('請輸入一個數:'))
11 showMaxFactor(num)
第一題

答:會,因為如果將 else 語句與迴圈語句(while 和 for 語句)進行搭配,那麼只有在迴圈正常執行完成後才會執行 else 語句塊的內容。

2. 請目測以下程式碼會列印什麼內容?

1 try:
2         print('ABC')
3 except:
4         print('DEF')
5 else:
6         print('GHI')
7 finally:
8         print('JKL')
第二題

答:只有 except 語句中的內容不被列印,因為 try 語句塊中並沒有異常,則 else 語句塊也會被執行。

ABC
GHI
JKL

3. 使用什麼語句可以使你不比再擔心檔案開啟後卻忘了關閉的尷尬?
答:使用 with 語句。

1 try:
2     with open('data.txt', 'w') as f:
3         for each_line in f:
4             print(each_line)
5 except OSError as reason:
6     print('出錯啦:' + str(reason))
第三題

4. 使用 with 語句固然方便,但如果出現異常的話,檔案還會自動正常關閉嗎?
答:with 語句會自動處理檔案的開啟和關閉,如果中途出現異常,會執行清理程式碼,然後確保檔案自動關閉。

5. 你可以換一種形式寫出下邊的虛擬碼嗎?

1 with A() as a:
2     with B() as b:
3         suite

答:with 語句處理多個專案的時候,可以用逗號隔開寫成一條語句)

1 with A() as a, B() as b:
2     suite

動動手部分:

0. 使用 with 語句改寫以下程式碼,讓 Python 去關心檔案的開啟與關閉吧。

 1 def file_compare(file1, file2):
 2     f1 = open(file1)
 3     f2 = open(file2)
 4     count = 0 # 統計行數
 5     differ = [] # 統計不一樣的數量
 6 
 7     for line1 in f1:
 8         line2 = f2.readline()
 9         count += 1
10         if line1 != line2:
11             differ.append(count)
12 
13     f1.close()
14     f2.close()
15     return differ
16 
17 file1 = input('請輸入需要比較的頭一個檔名:')
18 file2 = input('請輸入需要比較的另一個檔名:')
19 
20 differ = file_compare(file1, file2)
21 
22 if len(differ) == 0:
23     print('兩個檔案完全一樣!')
24 else:
25     print('兩個檔案共有【%d】處不同:' % len(differ))
26     for each in differ:
27         print('第 %d 行不一樣' % each)

with改寫後的程式碼:

 1 def file_compare(file1,file2):
 2     count = 0  # 統計行數
 3     differ = [] # 統計不一樣的數量
 4     
 5     with open(file1) as f1,open(file2) as f2:
 6         for line1 in f1:
 7             line2 = f2.readline()
 8             count += 1
 9             if line1 != line2:
10                 differ.append(count)
11     
12     return differ
13 
14 file1 = input("請輸入需要比較的頭一個檔名:")
15 file2 = input("請輸入需要比較的另一個檔名:")
16 
17 differ = file_compare(file1,file2)
18 
19 if len(differ) == 0:
20     print("兩個檔案完全一樣!")
21 else:
22     print(f"兩個檔案共有{len(differ)}處不同:")
23     for each in differ:
24         print(f"第{each}行不一樣")

1. 你可以利用異常的原理,修改下面的程式碼使得更高效率的實現嗎?

 1 print('|--- 歡迎進入通訊錄程式 ---|')
 2 print('|--- 1:查詢聯絡人資料  ---|')
 3 print('|--- 2:插入新的聯絡人  ---|')
 4 print('|--- 3:刪除已有聯絡人  ---|')
 5 print('|--- 4:退出通訊錄程式  ---|')
 6 
 7 contacts = dict()
 8 
 9 while 1:
10     instr = int(input('\n請輸入相關的指令程式碼:'))
11     
12     if instr == 1:
13         name = input('請輸入聯絡人姓名:')
14         if name in contacts:
15             print(name + ' : ' + contacts[name])
16         else:
17             print('您輸入的姓名不再通訊錄中!')
18 
19     if instr == 2:
20         name = input('請輸入聯絡人姓名:')
21         if name in contacts:
22             print('您輸入的姓名在通訊錄中已存在 -->> ', end='')
23             print(name + ' : ' + contacts[name])
24             if input('是否修改使用者資料(YES/NO):') == 'YES':
25                 contacts[name] = input('請輸入使用者聯絡電話:')
26         else:
27             contacts[name] = input('請輸入使用者聯絡電話:')
28 
29     if instr == 3:
30         name = input('請輸入聯絡人姓名:')
31         if name in contacts:
32             del(contacts[name])         # 也可以使用dict.pop()
33         else:
34             print('您輸入的聯絡人不存在。')
35             
36     if instr == 4:
37         break
38 
39 print('|--- 感謝使用通訊錄程式 ---|')
第1題

答:使用條件語句的程式碼非常直觀明瞭,但是效率不高。因為程式會兩次訪問字典的鍵,一次判斷是否存在(例如 if name in contacts),一次獲得值(例如 print(name + ' : ' + contacts[name]))。

如果利用異常解決方案,我們可以簡單避開每次需要使用 in 判斷是否鍵存在字典中的操作。因為只要當鍵不存在字典中時,會觸發 KeyError 異常,利用此特性我們可以修改程式碼:

 1 print('|--- 歡迎進入通訊錄程式 ---|')
 2 print('|--- 1:查詢聯絡人資料  ---|')
 3 print('|--- 2:插入新的聯絡人  ---|')
 4 print('|--- 3:刪除已有聯絡人  ---|')
 5 print('|--- 4:退出通訊錄程式  ---|')
 6 
 7 contacts = dict()
 8 
 9 while 1:
10     instr = int(input('\n請輸入相關的指令程式碼:'))
11     
12     if instr == 1:
13         name = input('請輸入聯絡人姓名:')
14         try:
15             print(name + ':' + contacts[name])
16         except KeyError:
17             print('您輸入的姓名不在通訊錄中!')
18     
19     if instr == 2:
20         name = input('請輸入聯絡人姓名:')
21         try:
22             contacts[name]
23             print('您輸入的姓名在通訊錄中已經存在 -->> ',end='')
24             print(name + ':' + contacts[name])
25             if input('是否修改使用者資料(YES/NO):') == 'YES':
26                 contacts[name] = input('請輸入使用者聯絡電話:')
27         except:
28             contacts[name] = input('請輸入使用者聯絡電話:')
29     
30     if instr == 3:
31         name = input('請輸入聯絡人姓名:')
32         try:
33             del(contacts[name])   # 也可以使用dict.pop()
34         except:
35             print('您輸入的聯絡人不存在。')
36     
37     if instr == 4:
38         break
39 
40 print('|--- 感謝使用通訊錄程式 ---|')
使用異常處理的程式碼