1. 程式人生 > >Python指南:控制結構與函式

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

作為該分支的suite。

用條件分支實現三目運算子:

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

語句,就會跳轉到迴圈起始處,並對boolean_expression的取值進行重新評估。

存在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異常系統部分截圖:

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