5 函數

其實我們已經開始使用函數了,例如我們請 Python 回傳資料類型的 type() 或者產生數列 list 的 range() 都是函數。函數的使用方法是在小括號中放輸入(input),然後呼叫函數得到輸出(output)。

5.1 查詢函數的用法

如果我們不知道某個函數的用法,可以使用 help() 查詢,了解一個函數的輸入與它可以設定的參,例如查詢 range() 函數就知道可以指定產出數列的起始值(start 預設為 0)、終止值(stop)與公差(step 預設為 1):

help(range)

圖 5-1 查詢函數

5.2 自訂函數

Python 自訂函數的架構:

def function_name(輸入, 參數 1, 參數 2, ...):
    '''
    Docstrings
    '''
    # 主要的程式
    return 結果

首先給函數取個名字(function_name),接著在小括號中放進輸入(inputs)與參數(parameters),然後附上一段說明(Docstrings),在縮排內撰寫我們主要的程式,最後把輸出結果放在 return 後面。

5.2.1 函數說明文件 Docstrings

養成在自訂函數中加入 Docstrings 說明的好習慣,這有助於分享我們的自訂函數給其他人使用。Docstrings 可以區分為單行與多行,單行的 Docstrings 用一個單引號(')包起來:

def function_name(輸入, 參數 1, 參數 2, ...):
    '單行的 Docstrings'
    # 主要的程式
    return 結果

多行的 Docstrings 用三個單引號(''')包起來:

def function_name(輸入, 參數 1, 參數 2, ...):
    '''
    多行的 Docstrings
    更多的說明內容
    '''
    # 主要的程式
    return 結果

我們撰寫的 Docstrings 會在使用 help() 查詢函數用法的時候出現,例如我們的第一個自訂函數,它的作用是將輸入的數字平方之後回傳,我們就叫它 my_squared() 函數。

def my_squared(x):
    '將輸入的數字平方之後回傳'
    return x ** 2

help(my_squared) # 查詢 my_squared() 函數

圖 5-2 查詢函數(2)

5.2.2 計算圓面積的函數

熟能生巧,我們來練習自訂一個輸入半徑長度可以計算出圓面積的函數叫做 circle_area()

import math # 要使用圓周率 pi 得引入套件 math

def circle_area(r):
    '輸入半徑長度可以計算出圓面積'
    return math.pi * r ** 2

print circle_area(3)

圖 5-3 計算圓面積的函數

5.2.3 計算圓周長的函數

我們接著來練習自訂一個輸入半徑長度可以計算出圓周長的函數叫做 circle_circum()

import math # 要使用圓周率 pi 得引入套件 math

def circle_circum(r):
    '輸入半徑長度可以計算出圓周長'
    return 2 * math.pi * r

print circle_circum(3)

圖 5-4 計算圓周長的函數

5.2.4 加入參數

我們把前面兩個自訂函數的功能整合起來到一個函數裡面,這個時候我們會正式納入參數(parameters)的觀念到這個自訂函數中,使用一個布林參數 is_area 來讓使用者在呼叫函數時決定要計算圓面積或者圓周長。

import math # 要使用圓周率 pi 得引入套件 math

def circle_calculator(r, is_area = True):
    '''
    輸入半徑長度可以計算出圓面積與圓周長
    利用參數 is_area 決定要回傳圓面積或圓周長
    預設回傳圓面積
    '''
    if is_area == True:
        return math.pi * r ** 2
    else:
        return 2 * math.pi * r

print circle_calculator(3)
print circle_calculator(3, is_area = False)

圖 5-5 計算圓面積與圓周長的函數

這裡我們設計 is_area 參數是有一個預設值 True,假如自訂函數時沒有指定 is_area 參數的預設值並且呼叫函數時也沒有指定,就會出現錯誤訊息:

import math # 要使用圓周率 pi 得引入套件 math

def circle_calculator(r, is_area): # 沒有指定預設值
    '''
    輸入半徑長度可以計算出圓面積與圓周長
    利用參數 is_area 決定要回傳圓面積或圓周長
    預設回傳圓面積
    '''
    if is_area == True:
        return math.pi * r ** 2
    else:
        return 2 * math.pi * r

print circle_calculator(3) # 沒有指定 is_area 參數的值

圖 5-6 計算圓面積與圓周長的函數(2)

5.2.5 有多個輸出的函數

我們再修正一下 circle_calculator() 這個自訂函數,讓它可以輸入半徑就一併輸出圓面積與圓周長。這時我們只要在 return 敘述後面將兩個輸出用逗號 , 隔開就會儲存在一個 tuple 中回傳。

import math # 要使用圓周率 pi 得引入套件 math

def circle_calculator(r):
    '''
    輸入半徑長度可以計算出圓面積與圓周長
    將圓面積與圓周長一起回傳
    '''
    area = math.pi * r ** 2
    circum = 2 * math.pi * r
    return area, circum

print circle_calculator(3)

圖 5-7 有多個輸出的函數

5.3 更多自訂函數

5.3.1 自訂 my_len() 函數

我們希望 my_len() 函數可以計算輸入 list 中有幾個元素。

def my_len(x):
    '計算輸入 list 中有幾個元素'
    cnt = 0
    for i in x:
        cnt += 1
    return cnt

num_list = range(1, 11)
my_len(num_list) # 計算 num_list 中有幾個數字

圖 5-8 my_len() 函數

5.3.2 自訂 my_sum() 函數

我們希望 my_sum() 函數可以計算輸入 list 中的數字加總。

def my_sum(x):
    '計算輸入 list 中的數字加總'
    summation = 0
    for i in x:
        summation += i
    return summation

num_list = range(1, 11)
my_sum(num_list) # 計算 num_list 中數字的加總

圖 5-9 my_sum() 函數

5.3.3 自訂 my_mean() 函數

我們希望 my_mean() 函數可以計算輸入 list 中的數字平均數,平均數是數字的加總除以個數,這個函數恰好綜合了前面兩個例子。

def my_mean(x):
    '計算輸入 list 中的數字平均數'
    cnt = 0
    summation = 0.0
    for i in x:
        cnt += 1
        summation += i
    return (summation / cnt)

num_list = range(1, 11)
my_mean(num_list) # 計算 num_list 中數字的平均數

圖 5-10 my_mean() 函數

5.4 變數範圍(Variable Scope)

在自訂函數時必須意識到變數範圍這個議題,程式中的變數開始被我們區分為區域變數(local variables)全域變數(global variables),在函數內我們能夠使用兩種類型的變數,但是在函數外,僅能使用全域變數。用講的很抽象,我們還是動手定義自訂函數來釐清這個概念,用第一個自訂函數 my_squared() 來演釋:

def my_squared(x):
    ans = x ** 2 # ans 是一個區域變數
    return ans

print ans # 在函數外沒有 ans

圖 5-11 變數範圍

假如我們將 ans 在函數外也進行指派:

ans = 0 # ans 是一個全域變數
def my_squared(x):
    ans = x ** 2 # ans 是一個區域變數
    return ans

print my_squared(3) # 區域變數 ans 的值是 9
print ans # 全域變數 ans 的值是 0

圖 5-12 變數範圍

5.5 匿名函數(lambda 函數)

除了正常的函數自訂結構,我們也可以使用一個稱作 lambda 函數的寫法,這樣的寫法不需要 return 敘述,我們來試著將 my_squared() 函數改寫成 lambda 函數:

my_squared = lambda x : x ** 2
print my_squared(3)

圖 5-13 lambda 函數

lambda 函數常與 map()filter()reduce() 一起使用,像是利用 filter() 搭配 lambda 函數將 1 到 10 的偶數挑出來:

my_list = range(1, 11)
print filter(lambda x : x % 2 == 0, my_list)

圖 5-14 lambda 函數(2)

或者是利用 map() 搭配 lambda 函數將 1 到 10 的每一個數字都平方:

my_list = range(1, 11)
print map(lambda x : x ** 2, my_list)

圖 5-15 lambda 函數(3)

或者是利用 reduce() 搭配 lambda 函數將 1 到 10 的每一個數字加總:

my_list = range(1, 11)
print reduce(lambda x, y : x + y, my_list)

圖 5-16 lambda 函數(4)

5.6 巢狀函數

我們可以在函數裡面嵌入函數,舉例來說,我們可以將前面用來計算平均數的函數 my_mean() 包含兩個函數,一個是計算總和的函數 my_sum(),一個是計算個數的函數 my_len()

def my_mean(x):
    '計算平均數'
    def my_sum(x):
        '計算輸入 list 中的數字加總'
        summation = 0.0
        for i in x:
            summation += i
        return summation
    def my_len(x):
        '計算輸入 list 中有幾個元素'
        cnt = 0
        for i in x:
            cnt += 1
        return cnt
    return my_sum(x) / my_len(x)

# 呼叫自訂函數
my_list = range(1, 11)
print my_mean(my_list)

圖 5-17 巢狀函數

5.7 錯誤處理

我們在使用內建函數時候常有各種原因會導致錯誤或者警示,這時收到的回傳訊息可以幫助我們修改程式,假如我們執行 print int("True") 會得到 ValueError: invalid literal for int() with base 10: 'True' 的回傳訊息。

圖 5-18 錯誤處理

自訂函數時如果能夠掌握某些特定錯誤,撰寫客製的錯誤訊息,可以讓使用自訂函數的使用者更快完成偵錯,我們可以使用 try - except 的語法結構進行錯誤處理。

我們修改原本計算平方數的 my_squared() 當使用者輸入文字時會回傳客製錯誤訊息:「請輸入數值。」

def my_squared(x):
    '計算平方數且具有錯誤處理的函數'
    try:
        return x ** 2
    except:
        print("請輸入數值。")

my_squared("3")

圖 5-19 錯誤處理

除了我們在這個例子中遭遇的 TypeError 以外,也可以針對不同的錯誤類型設計客製錯誤訊息,接著我們再寫一個除法的函數 divide() 來示範,如果使用者輸入的資料類型錯誤(TypeError)就回傳「請輸入數值。」假如使用者在除數指定了 0(ZeroDivisionError)就回傳「除數不可以為零。」

def divide(x, y):
    try:
        return x / y
    except TypeError:
        print("請輸入數值。")
    except ZeroDivisionError:
        print("除數不可以為零。")
    except:
        print("其他的錯誤")

print divide("9", 3)
print divide(9, 0)

圖 5-20 錯誤處理(2)

5.8 彈性參數

Python 可以使用 *args**kwargs(Keyword arguments)來分別處理不帶鍵值與帶有鍵值的彈性參數,利用這個特性,我們不一定要將輸入用容器把變數包裝起來。

5.8.1 使用 *args

我們可以將美國影集六人行(Friends)的六個主要演員直接當作參數輸入函數,略過存放在 list 的步驟:

def output_stars(*args):
    '將輸入的演員一一輸出'
    for star in args:
        print star

output_stars("Jennifer Aniston", "Courteney Cox", "Lisa Kudrow", "Matt LeBlanc", "Matthew Perry", "David Schwimmer")

圖 5-21 使用 *args

5.8.2 使用 **kwargs

我們可以將美國影集六人行(Friends)的影集資訊直接當作參數輸入函數,略過存放在 dict 的步驟:

def output_info(**kwargs):
    '將影集資訊一一輸出'
    for info in kwargs:
        print info + ": " + str(kwargs[info])

output_info(genre = 'Sitcom', seasons = 10, episodes = 236)

圖 5-22 使用 **kwargs

results matching ""

    No results matching ""