1. 程式人生 > >Python自學記錄——函式引數和遞迴函式

Python自學記錄——函式引數和遞迴函式

大多數時候,我們呼叫函式時,需要傳入指定的引數,根據我們傳入的引數,函式將返回我們對應引數的結果。在Python定義引數比較簡單,靈活度特別大。除了正常定義的必選引數外,還有預設引數、可變引數、關鍵字引數,使函式定義的介面,不但能處理複雜的引數,還能簡化呼叫者的程式碼。

位置引數:

>>> def power(x):
...     return x*x

上述寫的是個簡單的求平方的示例,其中 x 就是位置引數。我們呼叫時只能傳入一個引數,會報錯;呼叫時傳入一個非數字型別的引數,也會報錯。下面會逐漸完善這個函式。

若我們想求一個數的3次方或4次方,寫多個函式會很麻煩,現有函式函式加一個引數即可解決這個問題,如下所示:

>>> def power(x,n):
...     sum = 1
...     while n>0:
...         sum = sum * x
...         n = n - 1
...     return sum

上述程式碼中 x 和 n 都是位置引數,呼叫函式時,需按照順序放值。

新的函式寫好後,測試沒有問題,但輸入一個引數的時候會報錯。為了解決這一問題,我們需要設定預設引數。

預設引數:

>>> def power(x,n=2):
...     sum = 1 
...     while n>0:
...         sum = sum * x
...         n = n - 1
...     return sum

上述程式碼引數中,加入了 n=2 ,呼叫時如果不輸入第二個引數時,函式預設輸入的是2,若輸入其他數字,則根據使用者輸入的為準。使用預設引數,有一點需要注意下:

變化大的函式放在前面,變化小的函式放在後面,變化小的函式可以寫為預設函式(使用時,預設函式儘量放在後面,而且不要放在首位。預設引數放在首位的話,還有其他需要輸入的引數,系統無法確認使用者第一個輸入的引數是修改預設引數還是預設引數後的其他引數,編譯器會報錯)。

下面舉一個比較複雜的例子。比如,你要錄入一批會員資訊,會員主要資訊包含:姓名、性別、年齡、城市等。由於大部分會員都是男性,且都在一個城市,則函式可以這樣寫:

>>> def hm(name,age,sex='boy',city='Harbin'):
...     return name+','+age+','+sex+','+city

上述函式,只有 name 和 age 引數時必填項,其他兩個是選填,若 輸入前兩個引數,示例如下:

>>> hm('Susan','20')

結果如下:

'Susan,20,boy,Harbin'

若性別是女孩,則寫法如下:

>>> hm('Susan','20','gril')

結果如下:

'Susan,20,gril,Harbin'

若性別不變,只改變城市,寫三個引數即可,寫法如下:

>>> hm('Susan','20',city='Beijing')

結果如下:

'Susan,20,boy,Beijing'

綜上所述,預設函式可以簡化很多步驟,降低了難度,需要複雜呼叫時,傳入更多引數即可,一件事只定義一個函式。

在預設函式這裡,有個很大的坑,程式碼如下:

>>> def k1(L=[]):
...     L.append('End')
...     return L

上述程式碼中,給L定義了一個空的list,L變成了一個儲存在記憶體中的變數,若正常傳參呼叫沒問題,但要是重複不傳參呼叫,則會不斷在list中新增指定字串,示例如下:

>>> k1([1,2,3])
 [1,2,3,'End']
>>> k1([1,2,3])
 [1,2,3,'End']
>>> k1()
 ['End']
>>> k1()
 ['End','End']
>>> k1()
 ['End','End','End']

若解決這個問題,需要使用不變變數 None 來解決,示例如下:

>>> def k1(L=None):
...     if L is None:
...         L=[]
...     L.append('End')
...     return L

不變物件一旦建立,物件內部的資料就不能修改,這樣就減少了由於修改資料導致的錯誤。此外,由於物件不變,多工環境下同時讀取物件不需要加鎖,同時讀一點問題都沒有。我們在編寫程式時,如果可以設計一個不變物件,那就儘量設計成不變物件。

可變引數:

可變引數,即傳入引數的數量是可變的。一般來說,首先想到的是,傳入 list 或 tuple,這樣是可以的,但有一個缺陷,傳入引數前需要先生成一個 list 或 tuple 再傳值,這樣比較麻煩,可變引數解決了這一問題,示例為數字求和,程式碼如下:

>>> def s1(*nums):
...     sum = 0
...     for n in nums:
...        sum = sum + n
...     return sum

若要將 list 或 tuple 中的個別值,傳入函式,寫法如下:

>>> ns = [1,2,3,4]
>>> s1(ns[0],ns[3])
 5
>>> s1()
 0

上述程式碼中,若不傳引數,則返回函式中定義的 sum 的值,0 。

若要將整個的 list 或 tuple 中的值傳進去,則寫法如下:

>>> nts = (1,2,3) 
>>> s1(*nts)
 6

*nts 表示將 nts 中所有的值傳進去,在Python中,這種寫法很常見。

關鍵字引數:

關鍵字引數是將你傳入的值單獨裝在一個tuple,示例如下:

>>> def g1(name,age,**mes):
...    print('name:', name, 'age:', age, 'message:', mes)

測試結果如下:

>>> g1('Susan',20,sex='boy',city='Harbin')
 name: Susan age: 20 message: {'sex': 'boy', 'city': 'Harbin'}

關鍵字函式拓展了函式的功能。比如我們錄製一個人的資訊,處了幾項必填選項外,選填選項如果使用者填寫了,就儲存在dict中,有多少存多少,滿足需求。

關鍵字引數的傳入方法和可變引數類似,從dict中取幾個或全取,寫法如下:

>>> gs = {'sex':'boy','city':'Harbin','tel':'1234567'}
>>> g1('Susan',20,city=gs['city'],sex=gs['sex'])
 name: Susan age: 20 message: {'city':'Harbin','sex':'boy'}
>>>
>>> g1('Susan',20,**gs)
 name:Susan age:20 message:{'city':Harbin,'sex':'boy','tel':'1234567'}

有一點需要注意下,傳入函式的 dict ,屬於拷貝資料,對其的修改不會影響到原先的 dict 。

命名關鍵字引數:

通過指定的引數變數名來呼叫函式,傳值時輸入指定變數名+變數值呼叫函式,程式碼如下:

>>> def m1(name,age,*,city,sex):
...      print('name:',name,'age:',age,'city:',city,'sex:',sex)

命名關鍵字函式有一個特殊的分割符 * ,* 後面的為命名關鍵字引數。傳參時,須寫引數名,否則報錯,示例如下:

>>> m1('Susan',20,city='Harbin',sex='boy')
 name: Susan age: 20 city: Harbin sex: sex

錯誤示例如下:

>>> m1('Susan',20,'Harbin','boy')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: m1() takes 2 positional arguments but 4 were given

若函式的引數中有可變函式,則就可以不用寫 命名關鍵字函式的分隔符 *,示例如下:

>>> def m2(name,*age,city,sex):
...      print('name:',name,'age:',age,'city:',city,'sex:',sex)

結果如下:

>>> m2('Susan',[20,21],city='Harbin',sex='boy')
 name: Susan age: ([20,21],) city: Harbin sex: boy

命名關鍵字函式的引數可以設定預設值。使用命名關鍵字函式時,若沒有可變引數,在命名關鍵字引數前必須加分隔符 * ,否則視為 位置函式。

用 Python2. 7 版本 使用此方法報錯。

引數組合:

在Python中,上述引數可以混合使用,但要注意的是,混合使用的引數順序必須是:必選引數,預設引數,可變引數,命名關鍵字引數,關鍵字引數。示例如下:

>>> def h1(a,b=0,*c,d,**f):
...     print('a:',a,'b:',b,'c:',c,'d:',d,'f:',f)

測試如下:

>>> h1(1,2,3,d=4,f=5)
a: 1 b: 2 c: (3,) d: 4 f: {'f': 5}
>>> h1(1,2,d=4)
a: 1 b: 2 c: () d: 4 f: {}

最神奇的一點,通過 tuple 和 dict 也可以呼叫上述函式,示例如下:

>>> c1 =(1,2,3,4)
>>> d1 = {'d':100,'f':'fff'}
>>> h1(*c1,**d1)
a: 1 b: 2 c: (3, 4) d: 100 f: {'f': 'fff'}

遞迴函式:

遞迴函式就是函式呼叫自身函式,示例如下:

>>> def sumNum(x):
...     if x == 1:
...         return 1
...     return x + sumNum(x-1)

上述程式碼是求數字 1 到整數 x 之間的和,程式碼的末尾呼叫了自身函式。

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

使用遞迴函式時需要防止棧的溢位,棧的大小不是無限的,所以,當遞迴的次數太多,會導致棧溢位,編譯器報錯,示例如下:

>>> sumNum(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in fact
  ...
  File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded in comparison

正常來講,解決的方法是 尾遞迴 優化,簡單來說,呼叫自身函式時,語句中沒有表示式,示例如下:

>>> def sumNum(x,zj):
...     if x == 1:
...         return zj
...     return sumNum(x-1,x+zj)
但是,大多數程式語言都沒有針對 尾遞迴 做優化,Python也不例外。所以,使用遞迴函式時要注意,不要過深的呼叫。

函式引數很重要,基礎知識要打牢,本篇就到這裡,繼續學習~~