2.4 Python 異常處理與函數
本節要點
- 瞭解 Python 中異常處理的概念
- 重點掌握 Python 中函數的概念、用法
課前準備
檢查pip命令
在 cmd 輸入 pip 或終端中輸入 pip 或 pip3 ,會輸出 pip 的使用方法
課程內容
異常處理
1. 程式常見的錯誤
什麼是異常?
- 異常即是一個事件,該事件會在程式執行過程中發生,影響了程式的正常執行。
- 一般情況下,在 Python 無法正常處理程式時就會發生一個異常。
- 異常是 Python 對象,表示一個錯誤。
- 當 Python 腳本發生異常時我們需要捕獲處理它,否則程式會終止執行。
異常名稱 | 描述 |
BaseException | 所有異常的基類 |
SystemExit | 解釋器請求退出 |
KeyboardInterrupt | 使用者中斷執行(通常是輸入^C) |
Exception | 常規錯誤的基類 |
StopIteration | 迭代器沒有更多的值 |
GeneratorExit | 生成器(generator)發生異常來通知退出 |
StandardError | 所有的內建標準異常的基類 |
ArithmeticError | 所有數值計算錯誤的基類 |
FloatingPointError | 浮點計算錯誤 |
OverflowError | 數值運算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有資料類型) |
AssertionError | 斷言語句失敗 |
AttributeError | 對象沒有這個屬性 |
EOFError | 沒有內建輸入,到達 EOF 標記 |
EnvironmentError | 操作系統錯誤的基類 |
IOError | 輸入/輸出操作失敗 |
OSError | 操作系統錯誤 |
WindowsError | 系統調用失敗 |
ImportError | 匯入模塊/對象失敗 |
LookupError | 無效資料查詢的基類 |
IndexError | 序列中沒有此索引(index) |
KeyError | 映射中沒有這個鍵 |
MemoryError | 內存溢出錯誤(對於Python 解釋器不是致命的) |
NameError | 未聲明/初始化對象 (沒有屬性) |
UnboundLocalError | 造訪未初始化的本地變數 |
ReferenceError | 弱引用(Weak reference)試圖造訪已經垃圾回收了的對象 |
RuntimeError | 一般的運行時錯誤 |
NotImplementedError | 尚未實現的方法 |
SyntaxError | Python 語法錯誤 |
IndentationError | 縮進錯誤 |
TabError | Tab 和空格混用 |
SystemError | 一般的解釋器系統錯誤 |
TypeError | 對類型無效的操作 |
ValueError | 傳入無效的參數 |
UnicodeError | Unicode 相關的錯誤 |
UnicodeDecodeError | Unicode 解碼時的錯誤 |
UnicodeEncodeError | Unicode 編碼時錯誤 |
UnicodeTranslateError | Unicode 轉換時錯誤 |
Warning | 警告的基類 |
DeprecationWarning | 關於被棄用的特徵的警告 |
FutureWarning | 關於構造將來語義會有改變的警告 |
OverflowWarning | 舊的關於自動提升為長整型(long)的警告 |
PendingDeprecationWarning | 關於特性將會被廢棄的警告 |
RuntimeWarning | 可疑的運行時行為(runtime behavior)的警告 |
SyntaxWarning | 可疑的語法的警告 |
UserWarning | 使用者程式碼生成的警告 |
2. 如何判斷和捕捉異常
捕捉異常可以使用 try/except 語句。
try/except 語句用來檢測 try 語句塊中的錯誤,從而讓 except 語句捕獲異常資訊並處理。 如果你不想在異常發生時結束你的程式,只需在 try 裏捕獲它。
以下為簡單的 try…except…else 的語法:
try:
<語句> #運行別的程式碼
except <名字>:
<語句> #如果在try部份引發了'name'異常
except <名字>,<資料>:
<語句> #如果引發了'name'異常,獲得附加的資料
else:
<語句> #如果沒有異常發生
- try 的工作原理是,當開始一個 try 語句後,python 就在當前程式的上下文中作標記,這樣當異常出現時就可以回到這裡,try 子句先執行,接下來會發生什麼依賴於執行時是否出現異常。
- 如果當 try 後的語句執行時發生異常,python 就跳回到 try 並執行第一個匹配該異常的 except 子句,異常處理完畢,控制流就透過整個 try 語句(除非在處理異常時又引發新的異常)。
- 如果在 try 後的語句裏發生了異常,卻沒有匹配的 except 子句,異常將被遞交到上層的 try,或者到程式的最上層(這樣將結束程式,並列印預設的出錯資訊)。
- 如果在 try 子句執行時沒有發生異常,python 將執行 else 語句後的語句(如果有 else 的話),然後控制流透過整個 try 語句。
下面是簡單的例子,它打開一個文件,在該文件中的內容寫入內容,且並未發生異常:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
try:
fh = open("testfile", "w")
fh.write("這是一個測試文件,用於測試異常!!")
except IOError:
print ("Error: 沒有找到文件或讀取文件失敗")
else:
print ("內容寫入文件成功")
fh.close()
下面是簡單的例子,它打開一個文件,在該文件中的內容寫入內容,但文件沒有寫入權限,發生了異常:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
try:
fh = open("testfile", "w")
fh.write("這是一個測試文件,用於測試異常!!")
except IOError:
print ("Error: 沒有找到文件或讀取文件失敗")
else:
print ("內容寫入文件成功")
fh.close()
函數
1. 函數的概念
- 你可以定義一個由自己想要功能的函數,以下是簡單的規則:
- 函數程式碼塊以 def 關鍵詞開頭,後接函數標識符名稱和圓括號 ();
- 任何傳入參數和自變數必須放在圓括號中間。圓括號之間可以用於定義參數;
- 函數的第一行語句可以選擇性地使用文件字串—用於存放函數說明;
- 函數內容以冒號起始,並且縮進;
- return [表達式]結束函數,選擇性地返回一個值給調用方。不帶表達式的 return 相當於返回 None。
建立一個函數
在 Python 中,定義一個函數要使用 def 語句:
def my_abs(x):
if x >= 0:
return x
else:
return -x
print(my_abs(-99))
函數可以返回多個值嗎?
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
x, y = move(100, 100, 60, math.pi / 6)
print(x, y)
形參與實參
username 就是一個形參,小明是我在調用函數時傳入的一個實參,它的值被儲存在形參 username 中:
局部變數與全局變數
# -*- coding: UTF-8 -*-
total = 0 # 這是一個全局變數
# 可寫函數說明
def sum( arg1, arg2 ):
#返回2個參數的和."
total = arg1 + arg2 # total在這裡是局部變數.
print ("函數內是局部變數 : ", total)
return total
#調用sum函數
sum( 10, 20 )
print("函數外是全局變數 : ", total)
在函數中聲明變數為全局變數:
#在函數裏定義全局變數
a=0
def plus_a():
global a
a +=1
關鍵詞參數、預設參數、收集參數
1. 位置參數
我們先寫一個計算 x^2 的函數:
def power(x):
return x * x
對於 power(x) 函數,參數x就是一個位置參數。 當我們調用 power 函數時,必須傳入有且僅有的一個參數x:
>>> power(5)
25
>>> power(15)
225
現在,如果我們要計算 x3 怎麼辦?可以再定義一個 power3 函數,但是如果要計算 x4、x5…… 怎麼辦?我們不可能定義無限多個函數。 你也許想到了,可以把 power(x) 修改為 power(x, n),用來計算 xn:
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
對於這個修改後的 power(x, n) 函數,可以計算任意 n 次方:
>>> power(5, 2)
25
>>> power(5, 3)
125
修改後的 power(x, n) 函數有兩個參數:x 和 n,這兩個參數都是位置參數,調用函數時,傳入的兩個值按照位置順序依次賦給參數 x 和 n。
2. 預設參數
新的 power(x, n) 函數定義沒有問題,但是,舊的調用程式碼失敗了,原因是我們增加了一個參數,導致舊的程式碼因為缺少一個參數而無法正常調用:
>>> power(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'n'
Python 的錯誤資訊很明確:調用函數 power() 缺少了一個位置參數 n。 這個時候,預設參數就排上用場了。由於我們經常計算 x2,所以,完全可以把第二個參數 n 的預設值設定為 2:
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
這樣,當我們調用 power(5) 時,相當於調用 power(5, 2):
>>> power(5)
25
>>> power(5, 2)
25
而對於 n > 2 的其他情況,就必須明確地傳入 n,比如 power(5, 3)。
從上面的例子可以看出,預設參數可以簡化函數的調用。設定預設參數時,有幾點要注意:
- 一是必選參數在前,預設參數在後,否則 Python 的解釋器會報
- 二是如何設定預設參數。
當函數有多個參數時,把變化大的參數放前面,變化小的參數放後面。變化小的參數就可以作為預設參數。
使用預設參數有什麼好處?最大的好處是能降低調用函數的難度。
3. 可變參數
在 Python 函數中,還可以定義可變參數。顧名思義,可變參數就是傳入的參數個數是可變的,可以是 1 個、2 個到任意個,還可以是 0 個。
我們以數學題為例子,給定一組數字 a,b,c……,請計算 a2 + b2 + c2 + ……。
要定義出這個函數,我們必須確定輸入的參數。由於參數個數不確定,我們首先想到可以把 a,b,c…… 作為一個 list 或 tuple 傳進來,這樣,函數可以定義如下:
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
但是調用的時候,需要先組裝出一個 list 或 tuple:
>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84
所以,我們把函數的參數改為可變參數:
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
定義可變參數和定義一個 list 或 tuple 參數相比,僅僅在參數前面加了一個 * 號。在函數內部,參數numbers接收到的是一個tuple,因此,函數程式碼完全不變。但是,調用該函數時,可以傳入任意個參數,包括 0 個參數:
>>> calc(1, 2)
5
>>> calc()
0
如果已經有一個 list 或者 tuple,要調用一個可變參數怎麼辦?可以這樣做:
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14
這種寫法當然是可行的,問題是太繁瑣,所以Python允許你在list或tuple前面加一個*號,把list或tuple的元素變成可變參數傳進去:
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
附註:
nums 表示把 nums 這個 list 的所有元素作為可變參數傳進去。這種寫法相當有用,而且很常見。
4. 關鍵字參數
可變參數允許你傳入 0 個或任意個參數,這些可變參數在函數調用時自動組裝為一個 tuple。而關鍵字參數允許你傳入 0 個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個dict。請看範例:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
函數 person 除了必選參數 name 和 age 外,還接受關鍵字參數 kw。在調用該函數時,可以只傳入必選參數:
>>> person('Michael', 30)
name: Michael age: 30 other: {}
也可以传入任意个数的关键字参数:
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
關鍵字參數有什麼用?它可以擴展函數的功能。比如,在 person 函數裏,我們保證能接收到 name 和 age 這兩個參數,但是,如果調用者願意提供更多的參數,我們也能收到。試想你正在做一個使用者註冊的功能,除了使用者名和年齡是必填項外,其他都是可選項,利用關鍵字參數來定義這個函數就能滿足註冊的需求。
和可變參數類似,也可以先組裝出一個 dict,然後,把該 dict 轉換為關鍵字參數傳進去:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
當然,上面複雜的調用可以用簡化的寫法:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
extra 表示把該變數的所有 key-value 用關鍵字參數傳入到函數的 kw 參數,kw 將獲得一個 dict,注意 kw 獲得的 dict 是extra 的一份拷貝,對 kw 的改動不會影響到函數外的 extra。