第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('出錯啦:
參考內容: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('|--- 感謝使用通訊錄程式 ---|')使用異常處理的程式碼