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也不例外。所以,使用遞歸函數時要注意,不要過深的調用。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章