1. 程式人生 > >極簡Python入門——函式

極簡Python入門——函式

函式

什麼是函式?

函式是組織好的,可重複使用的,用來實現單一,或相關聯功能的程式碼段。

我們知道圓的面積計算公式為:

S = πr2

當我們知道半徑r的值時,就可以根據公式計算出面積。假設我們需要計算3個不同大小的圓的面積:

r1 = 12.34
r2 = 9.08
r3 = 73.1
s1 = 3.14 * r1 * r1
s2 = 3.14 * r2 * r2
s3 = 3.14 * r3 * r3

當代碼出現有規律的重複的時候,你就需要當心了,每次寫3.14 * x * x不僅很麻煩,而且,如果要把3.14改成3.14159265359的時候,得全部替換。

有了函式,我們就不再每次寫s = 3.14 * x * x,而是寫成更有意義的函式呼叫s = area_of_circle(x),而函式area_of_circle本身只需要寫一次,就可以多次呼叫。

呼叫函式

Python內建了很多有用的函式,我們可以直接呼叫。要呼叫一個函式,需要知道函式的名稱和引數,比如求絕對值的函式abs,只有一個引數。

呼叫abs函式如下:

>>> abs(100)
100
>>> abs(-20)
20
>>> abs(12.34)
12.34

定義函式

在Python中,定義一個函式要使用def語句,依次寫出函式名、括號、括號中的引數和冒號:,然後,在縮排塊中編寫函式體,函式的返回值用return語句返回。

以自定義一個求絕對值的my_abs函式為例:

def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x

空函式

如果想定義一個什麼事也不做的空函式,可以用pass語句:

def nop():
    pass

pass語句什麼都不做,那有什麼用?實際上pass可以用來作為佔位符,比如現在還沒想好怎麼寫函式的程式碼,就可以先放一個pass,讓程式碼能執行起來。缺少了pass,程式碼執行就會有語法錯誤。

引數檢查

呼叫函式時,如果引數個數不對,Python直譯器會自動檢查出來,並丟擲TypeError:

>>> my_abs(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: my_abs() takes 1 positional argument but 2 were given

但是如果引數型別不對,Python直譯器就無法幫我們檢查。試試my_abs和內建函式abs的差別:

>>> my_abs('A')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in my_abs
TypeError: unorderable types: str() >= int()

返回多個值

函式可以返回多個值嗎?答案是肯定的。

比如在遊戲中經常需要從一個點移動到另一個點,給出座標、位移和角度,就可以計算出新的新的座標:

import math
def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny

import math語句表示匯入math包,並允許後續程式碼引用math包裡的sin、cos等函式。

遞迴函式

在函式內部,可以呼叫其他函式。如果一個函式在內部呼叫自身本身,這個函式就是遞迴函式。

舉個例子,我們來計算階乘n! = 1 x 2 x 3 x … x n,用函式fact(n)表示,可以看出:
fact(n) = n! = 1 x 2 x 3 x … x (n-1) x n = (n-1)! x n = fact(n-1) x n
所以,fact(n)可以表示為n x fact(n-1),只有n=1時需要特殊處理。
於是,fact(n)用遞迴的方式寫出來就是:

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

上面就是一個遞迴函式。可以試試:

>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

如果我們計算fact(5),可以根據函式定義看到計算過程如下:
===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

遞迴函式的優點是定義簡單,邏輯清晰。理論上,所有的遞迴函式都可以寫成迴圈的方式,但迴圈的邏輯不如遞迴清晰。

函式的引數

定義函式的時候,我們把引數的名字和位置確定下來,函式的介面定義就完成了。對於函式的呼叫者來說,只需要知道如何傳遞正確的引數,以及函式將返回什麼樣的值就夠了,函式內部的複雜邏輯被封裝起來,呼叫者無需瞭解。

Python的函式定義非常簡單,但靈活度卻非常大。除了正常定義的必選引數外,還可以使用預設引數、可變引數和關鍵字引數,使得函式定義出來的介面,不但能處理複雜的引數,還可以簡化呼叫者的程式碼。

位置引數

先寫一個計算x2的函式:

def power(x):
    return x * x

對於power(x)函式,引數x就是一個位置引數。

當我們呼叫power函式時,必須傳入有且僅有的一個引數x:

>>> power(5)
25
>>> power(15)
225

現在,如果我們要計算x3怎麼辦?可以再定義一個power函式,但是如果要計算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。

預設引數

新的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的直譯器會報錯。
二是如何設定預設引數。

當函式有多個引數時,把變化大的引數放前面,變化小的引數放後面。變化小的引數就可以作為預設引數。
使用預設引數有什麼好處?最大的好處是能降低呼叫函式的難度。

寫個一年級小學生註冊的函式,需要傳入name和gender兩個引數:

def enroll(name, gender):
    print('name:', name)
    print('gender:', gender)

呼叫enroll()函式只需要傳入兩個引數:

>>> enroll('Sarah', 'F')
name: Sarah
gender: F

如果要繼續傳入年齡、城市等資訊怎麼辦?這樣會使得呼叫函式的複雜度大大增加。
我們可以把年齡和城市設為預設引數:

def enroll(name, gender, age=6, city='Beijing'):
    print('name:', name)
    print('gender:', gender)
    print('age:', age)
    print('city:', city)

這樣,大多數學生註冊時不需要提供年齡和城市,只提供必須的兩個引數:

>>> enroll('Sarah', 'F')
name: Sarah
gender: F
age: 6
city: Beijing

只有與預設引數不符的學生才需要提供額外的資訊:
enroll(‘Bob’, ‘M’, 7)
enroll(‘Adam’, ‘M’, city=‘Tianjin’)

有多個預設引數時,呼叫的時候,既可以按順序提供預設引數,比如呼叫enroll(‘Bob’, ‘M’, 7),意思是,除了name,gender這兩個引數外,最後1個引數應用在引數age上,city引數由於沒有提供,仍然使用預設值。

也可以不按順序提供部分預設引數。當不按順序提供部分預設引數時,需要把引數名寫上。比如呼叫enroll(‘Adam’, ‘M’, city=‘Tianjin’),意思是,city引數用傳進去的值,其他預設引數繼續使用預設值。

可變引數

在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

如果利用可變引數,呼叫函式的方式可以簡化成這樣:

>>> 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

關鍵字引數

可變引數允許你傳入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表示把extra這個dict的所有key-value用關鍵字引數傳入到函式的kw引數,kw將獲得一個dict,注意kw獲得的dict是extra的一份拷貝,對kw的改動不會影響到函式外的extra。

引數組合

在Python中定義函式,可以用必選引數、預設引數、可變引數、關鍵字引數和命名關鍵字引數,這5種引數都可以組合使用。但是請注意,引數定義的順序必須是:必選引數、預設引數、可變引數、命名關鍵字引數和關鍵字引數。

比如定義一個函式,包含上述若干種引數:

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

在函式呼叫的時候,Python直譯器自動按照引數位置和引數名把對應的引數傳進去。

>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}

最神奇的是通過一個tuple和dict,你也可以呼叫上述函式:

>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}

本文章參考以下連結https://www.liaoxuefeng.com