Python指南:控制結構與函式
1、控制結構
Python通過if語句實現了分支,通過while語句與for…in語句實現了迴圈,還有一種通過if實現的條件表示式(類似於C語言的三目運算子)。
1.1 條件分支
Python條件分支語句的最通常語法如下:
if boolean_expression1:
suite1
elif boolean_expression2:
suite2
...
elif boolean_expressionN:
suiteN
else:
else_suit
可以有0個或多個elif語句,最後一個else語句是可選的。如果在某個分支什麼都不想做,可以使用pass
用條件分支實現三目運算子:
expression1 if boolean_expression else expression2
如果boolean_expression為True,條件表示式為expression1,否則為expression2。舉個例子:
x = (1 if True else 0)
print(x)
[out]
1
注意圓括號的使用,如果不使用圓括號,我們可能掉入一些陷阱,看下面兩個程式碼的區別:
x = 10 + 5 if False else 0
print('first:', x)
x = 10 + (5 if False else 0)
print('secont:', x)
[out]
first: 0
secont: 10
從結果可以看出,如果不使用圓括號,Python將”10+5”看做條件表示式的expression1部分。
1.2 迴圈
Python提供了兩種迴圈方式:while和for…in。
1.2.1 while迴圈
語法格式:
while boolean_expression:
while_suit
else:
else_suit
else分支是可選的。如果boolean_expression為True,while_suite就會執行,否則迴圈終止。如果在while_suite內部執行了continue
存在else分支的話,如果迴圈是正常終止的,else_suite就會執行。如果由於break語句、返回語句或由於發生異常導致跳出迴圈,else_suite不會執行。
讓我們看一下else分支的實際使用。str.index()與list.index()返回給定字串或資料想得索引位置,如果找不到則產生ValueError異常。現在我們改變一下策略:如果找不到資料項,返回-1。
# while...else...例項
def list_index(lst, target):
index = 0
while index < len(lst):
if lst[index] == target:
break
else:
index += 1
else:
index = -1
return index
# 測試
lst = [1, 2, 'cat', 'apple']
print('index of "apple":', list_index(lst, 'apple'))
print('index of 9:', list_index(lst, 9))
[out]
index of "apple": 3
index of 9: -1
由輸出結果得知,我們想要的效果已經實現了。
1.2.2 for迴圈
語法格式:
for expression in iterable:
for_suit
else:
else_suit
else分支是可選的。如果在for_suite內執行了continue語句,控制流立即跳轉到迴圈起始處,並開始下一次迭代。
下面用for…in迴圈實現上述list_index():
# for...in版本
def list_index2(lst, target):
for index, value in enumerate(lst):
if value == target:
break
else:
index = -1
return index
# 測試
lst = [1, 2, 'cat', 'apple']
print('index of "apple":', list_index(lst, 'apple'))
print('index of 9:', list_index(lst, 9))
[out]
index of "apple": 3
index of 9: -1
2、異常處理
Python通過產生異常來指明發生錯誤或異常條件。
2.1 捕獲異常
異常的捕獲是使用try…except塊實現的,其語法格式如下:
try:
try_suite
except exception_group1 as variable1:
except_suite1
...
except exception_groupN as variableB:
except_suiteN
else:
else_suite
finally:
finally_suite
至少要包含一個except塊,else和finally塊都是可選的。在try_suite正常執行完畢是,會執行else_suite——如果發生異常,就不會執行。如果存在一個finally塊,則最後總會執行。
每個except分支的as variable
是可選的,如果使用,該變數就會包含發生的異常,並可以在異常塊的suite中進行存取。
要與異常組進行匹配,異常必須與組中列出的異常型別(或其中某一個)一致,或者與組中列出的異常型別(或其中某一個)的子類,下列列出Python異常系統部分截圖:
我們使用異常來實現前面的list_index()函式:
# 異常版本
def list_index3(lst, target):
try:
index = lst.index(target)
except ValueError:
index = -1
return index
# 測試
lst = [1, 2, 'cat', 'apple']
print('index of "apple":', list_index(lst, 'apple'))
print('index of 9:', list_index(lst, 9))
[out]
index of "apple": 3
index of 9: -1
try…except…finally塊的一種常見應用是處理檔案錯誤,我們開啟檔案產生異常、處理過程產生異常或者正常處理完成後,無論如何我們都需要關閉檔案,finally能幫我們始終關閉檔案。
2.2 產生異常
我們可以建立自己的異常,以產生我們所需要的異常並對其進行處理。產生異常的語法如下:
raise exception(args)
raise exception(args) from original_exception
raise
使用第一種語法是,指定的異常應該是內建的異常或者繼承自Exception的自定義異常。如果給定一些文字作為該異常的引數,那麼在捕捉到該異常並列印時,這些文字應該為輸出資訊。
使用第二種語法,也就是沒有指定異常時,raise將重新產生當前活躍的異常,如果當前沒有,就會產生一個TypeError。
2.3 自定義異常
自定義異常時自定義的資料型別(類)。建立自定義異常的語法如下:
class exceptionName(baseException):pass
其基類應該為Exception類或繼承自Exception的類。
自定義異常的一個用途是跳出深層巢狀迴圈。
下面舉一個簡單的自定義異常例子:
# 自定義異常
class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
try:
raise MyError(10)
except MyError as e:
print('There is a MyError, value:', e.value)
[out]
There is a MyError, value: 10
3、自定義函式
函式可用於將相關功能打包並引數化。在Python中,可以建立4中函式:全域性函式、區域性函式、lambda函式、方法。
- 全域性函式可以由建立該函式的同一模組(同一.py檔案)中的任意程式碼存取。
- 區域性函式(也稱為巢狀函式)定義在其他函式之內,只對對其進行定義的函式時可見的。
- Lambda函式是表示式,因此可以在需要使用的地方建立。
- 方法是與特定資料型別關聯的函式,並且只能與資料型別關聯在一起使用。
函式的引數可以指定預設值,比如def add(a, b=1)
。需要注意的是不允許在沒有預設值的引數後面跟隨預設值,比如def bad(a, b=1, c)
。
3.1 名稱與Docstrings
對於函式或變數的名稱,有一些可以考慮的經驗如下:
- 對常量使用UPPERCASE,對類(包括異常)使用TitleCase,對GUI函式與方法使用camel-Case,對其他物件使用lowercase或lowercase_with_underscores。
- 對所有名稱,避免使用縮略。
- 函式名與方法名應該可以表明其行為或返回值。
我們可以為任何函式新增文件資訊,docstring可以是簡單地新增在def行之後、函式程式碼開始之前的字串。以下舉requests庫中一個函式為例:
def get(url, params=None, **kwargs):
r"""Sends a GET request.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('get', url, params=params, **kwargs)
對函式文件而言,如果比函式本身還長,也並非不同尋常,常規的做法是,docstring的第一行知識一個簡短的描述,之後是一個空白行,再之後跟隨的是完整的描述資訊,如果是互動式輸入再執行的程式,還會給出一些例項。
3.2 引數與引數拆分
前面章節中講過,我們可以使用序列拆分操作符(*)來提供位置引數。我們也可以在函式引數列表中使用序列拆分操作符,在建立使用可變數量的位置引數的函式時,這種方法是有效的。
# 引數拆分
def product(*args):
print(type(args))
print(args)
product(1, 'love', 2)
[out]
<class 'tuple'>
(1, 'love', 2)
由輸出可以看出,在函式內部引數args的型別為元組,其項數隨著給定的位置引數個數的變化而變化。
我們可以將關鍵字引數跟隨在位置引數後面,例如:
# 關鍵字引數跟在位置引數後面
def sum_of_powers(*args, power=1):
result = 0
for arg in args:
result += arg ** power
return result
print(sum_of_powers(1, 3, 5))
print(sum_of_powers(1, 3, 5, power=2))
[out]
9
35
將*本身作為引數也是可以的,用於表明在*後不應該在出現位置引數,但關鍵字引數是允許的。
# *單獨傳入
def heron(a, b, c, *, units='meters'):
s = (a + b + c)/2
return '{} {}'.format(s, units)
print(heron(23, 24, 15))
print(heron(23, 24, 15, units='inches'))
print(heron(23, 24, 15, 'inches'))
[out]
31.0 meters
31.0 inches
Traceback (most recent call last):
File "D:\Programming\Python\第四章 控制結構與函式.py", line 110, in <module>
print(heron(23, 24, 15, 'inches'))
TypeError: heron() takes 3 positional arguments but 4 were given
就像我們可以對序列進行拆分來產生函式的位置引數一樣,我們也可以使用對映拆分操作符(**)來對對映進行拆分。
# 引數序列拆分
def print_dict(key='defkey', value='defvalue'):
string = 'key = {}, value = {}'.format(key, value)
return string
options = dict(key='hello', value='world')
print(print_dict(**options))
[out]
key = hello, value = world
3.3 存取全域性範圍的變數
我們經常在程式中設定一些全域性的常量,這是合理的,有時候也會定義一些全域性的變數,雖然這種做法不好。在函式中要對全域性變數進行讀取或修改,需要在前面新增 global 關鍵字,舉個例子:
# 全域性變數的存取
Price = 8.9
def raise_price():
global Price
print('The original price is: ${}. '.format(Price))
Price += 1
print('The lastest price is: ${}. '.format(Price))
raise_price()
[out]
The original price is: $8.9.
The lastest price is: $9.9.
global 的作用是高職Python,Price 變數作用範圍是全域性的,對變數的賦值應該應用於全域性變數,而不是建立一個同名的本地變數。如果不使用global語句,程式也可以執行,但是Python會在區域性(函式)範圍內查詢,由於找不到就建立一個新的名為Price的區域性變數,而不改變全域性的Price變數。
3.4 Lambda函式
Lambda函式的語法格式:
lambda parameters: expression
parameters 是可選的,如果提供,通常是逗號分隔的變數名形式,也就是位置引數。expression不能包含分支或迴圈,也不能包含return(或yeild)語句,lambda表示式的結果是一個匿名函式。所謂匿名,就是不再使用def語句這樣的標準形式定義一個函式。
我們已知三角形的底邊長為b,高為h,之前我們要寫求面積的函式會像這樣寫:
def area(b, h):
return 0.5 * b * h
那麼用匿名函式如何寫呢?
area = lambda b,h: 0.5 * b * h
匿名函式不需要return來返回值,表示式本身的結果就是返回值。
匿名函式優點:
- 使用Python寫一些指令碼時,使用lambda可以省去定義函式的過程,讓程式碼更加精簡。
- 對於一些抽象的,不會被別的地方再重複使用的函式,有時候函式起個名字也是個難題,使用lambda不需要考慮命名的問題
- 使用lambda在某些時候然後程式碼更容易理解
3.5 斷言
為了避免無效資料對程式的影響,我們可以宣告前提和後果,使用 assert 語句可以來實現該功能,其語法格式為:
assert boolean_expression, optional_expression
如果 boolean_expression 評價為 False 就產生一個 AssertionError 異常。如果給定了可選的 optional_expression ,就將其用作AssertionError異常的引數。
注意:斷言是為開發者設計的,而不是面向終端使用者的。
有一個product函式,要求所有的引數為非0值,並將使用引數0進行的呼叫視為編碼錯誤,下面給出兩種等價版本:
# assert語句,版本1
def product1(*args):
assert all(args), '0 argument'
result = 1
for arg in args:
result *= arg
return result
# 版本2
def product2(*args):
result = 1
for arg in args:
result *= arg
assert result, '0 argument'
return result
版本1對每個呼叫檢查所有的引數,版本2只對結果進行檢查。如果某個引數為0,就會產生一個AssertionError並向錯誤流(通常為控制檯)寫入錯誤資訊:
x = product1(0, 1, 2, 3, 4)
[out]
Traceback (most recent call last):
File "D:\xxx\第四章 控制結構與函式.py", line 160, in <module>
x = product1(0, 1, 2, 3, 4)
File "D:\xxx\第四章 控制結構與函式.py", line 144, in product1
assert all(args), '0 argument'
AssertionError: 0 argument
在程式準備就緒將要釋出時,手動去除assert語句是低效的,我們可以告訴Python不執行assert語句:執行程式時,在命令列中指定 -O 選項。另一種方法是將環境變數 PYTHONOPTIMIZE 設定為 O 。