Python學習總結(三)——Function(函式)
函式定義
def 函式名():
函式體
return 返回值1, 返回值2
在Python中,函式有五大要點,分別是def、函式名、函式體、引數、返回值,以及兩個英文版符號,分別是括號(括號內為引數)和冒號(:)。
def:函式的關鍵字,沒它可不行。
函式名:函式的名稱,根據函式名呼叫函式。
函式體:函式中進行一系列的具體操作。
引數:為函式體提供資料。
返回值:當函式執行完畢後,可以給呼叫者返回資料。
上述函式的要點中,最重要的是引數和返回值。
1.返回值
函式是一個功能塊,該功能到底執行成功與否,需要通過返回值來告知呼叫者。函式體內部的語句在執行時,一旦執行到return時,函式就執行完畢。如果沒有return語句,函式執行完畢後也會返回結果,只是結果為None。
2.引數
定義函式時,引數是一定需要考慮的。Python的函式定義非常簡單,但靈活度卻非常大。
對於函式的呼叫者來說,只需要知道如何傳遞正確的引數,以及函式將返回什麼樣的值就夠了,函式內部的複雜邏輯被封裝起來,呼叫者無需瞭解。
Python中,引數型別有:必選引數、預設引數、可變引數、關鍵字引數和命名關鍵字引數。函式中,引數定義的順序必須是:必選引數、預設引數、可變引數、命名關鍵字引數和關鍵字引數。
3.空函式
空函式:什麼事也不做,可以用pass語句。既然“一事不做”,那空函式還有什麼用處?實際上pass可以用來作為佔位符,比如現在還沒想好怎麼寫函式的程式碼,就可以先放一個pass,讓程式碼能執行起來。如此,執行程式碼程式就不會出現錯誤了。
# 空函式
def fun():
pass
函式引數
Python中,引數是非常靈活的。掌握引數就能領悟函式的真諦了。這是真的!引數是比較難理解的,特別是引數組合。
1.位置引數
# 位置引數(必選引數)
def involution(x):
return x * x
print(involution(6))
如程式碼所示,引數x就是一個位置引數。
2.預設引數
# 預設引數 def involution(x, n=2): s = 1 while n > 0: n = n - 1 s = s * x return s print(involution(6)) print(involution(5, 3))
Python函式支援預設引數,即可以給函式的引數指定預設值。當該引數沒有傳入相應的值時,該引數就使用預設值。
如程式碼所示,當我們呼叫involution(6),就相當於呼叫involution(6,2)。
注:1.設定預設引數時,必選引數在前,預設引數在後,否則Python的直譯器會報錯;
2.定義預設引數要牢記:預設引數必須指向不可變物件!
def add_end(L=[]):
L.append('END')
return L
print(add_end())
print(add_end())
print(add_end())
上述程式碼展示的是預設引數不為不可變物件的現象。因此,預設引數必須指向不可變物件【字串、None……】。
3.可變引數
在Python函式中,還可以定義可變引數。顧名思義,可變引數就是傳入的引數個數是可變的,可以是1個、2個到任意個,還可以是0個。
我們以數學題為例子,給定一組數字a,b,c……,請計算a2 + b2 + c2 + ……。
要定義出這個函式,我們必須確定輸入的引數。由於引數個數不確定,我們首先想到可以把a,b,c……作為一個list或tuple傳進來,這樣,函式可以定義如下:
1 #一般性函式
2 def calc(numbers):
3 sum = 0
4 for n in numbers:
5 sum = sum + n * n
6 return sum
如何呼叫calc()函式呢?需要呼叫時,需要為引數引入list或者tuple。
1 #函式呼叫
2 >>> calc([1, 2, 3])
3 14
4 >>> calc((1, 3, 5, 7))
5 84
然而,如果我們使用可變引數,我們可以進行簡化,方法如下:
1 #可變引數
2 def calc(*numbers):
3 sum = 0
4 for n in numbers:
5 sum = sum + n * n
6 return sum
咋呼叫呢?這個可簡單啦,再也不用list或者tuple了。引數呼叫只需如下所示:
1 #可變引數的魅力
2 >>> calc(1, 2, 3)
3 14
4 >>> calc(1, 3, 5, 7)
5 84
6
7 #引數呼叫不用calc([1,2,3]),括號內還用寫中括號,好麻煩~~~
定義可變引數和定義一個list或tuple引數相比,僅僅在引數前面加了一個*
號。在函式內部,引數numbers
接收到的是一個tuple,因此,函式程式碼完全不變。但是,呼叫該函式時,可以傳入任意個引數,包括0個引數:
1 >>> calc(1, 2)
2 5
3 >>> calc()
4 0
如果已經有一個list或者tuple,要呼叫一個可變引數怎麼辦?可以這樣做:
1 >>> nums = [1, 2, 3]
2 >>> calc(nums[0], nums[1], nums[2])
3 14
這種寫法當然是可行的,問題是太繁瑣,所以Python允許你在list或tuple前面加一個*號,把list或tuple的元素變成可變引數傳進去:
1 >>> nums = [1, 2, 3]
2 >>> calc(*nums)
3 14
4.關鍵字引數
可變引數允許你傳入0個或任意個引數,這些可變引數在函式呼叫時自動組裝為一個tuple。而關鍵字引數允許你傳入0個或任意個含引數名的引數,這些關鍵字引數在函式內部自動組裝為一個dict。dict就是字典,它是鍵值對組合,益處多多~~~
1 #引入關鍵字引數,預設為**kw
2 def person(name, age, **kw):
3 print('name:', name, 'age:', age, 'other:', kw)
函式person除了必選引數name和age外,還接受關鍵字引數kw。在呼叫該函式時,可以只傳入必選引數(必選引數必須全部傳入,否則會出錯),也可以傳入關鍵字引數。注:關鍵字引數可是任意個的。
1 #呼叫關鍵字引數
2 >>>def person(name,age,**kw):
3 ... print('name:',name,'age:',age,'other:',kw)
4 ...
5 >>>person('Jack')
6 Traceback (most recent call last):
7 File "<stdin>", line 1, in <module>
8 TypeError: person() missing 1 required positional argument: 'age'
9 >>>person('Jack',36)
10 name:Jack age:36 other:{}
11 >>>person('Jack',36,city='Hangzhou')
12 name:Jack age:36 other:{'city':'Hangzhou'}
13 >>>person('Jack',36,city='Hangzhou',job='Engineer')
14 name:Jack age:36 other:{'city':'Hangzhou','job':'Engineer'}
關鍵字引數有什麼用呢?其實,既然存在就有它的強大之處。就像自然界中的萬物,物競天擇,適者生存。如果它能夠在自然界中生存下來,那麼它就有獨特的生存本領。因此,關鍵字引數還是有用武之地的。
它可以擴充套件函式的功能。比如,在person函式裡,我們保證能接收到name和age這兩個引數,但是,如果呼叫者願意提供更多的引數,我們也能收到。試想你正在做一個使用者註冊的功能,除了使用者名稱和年齡是必填項外,其他都是可選項,利用關鍵字引數來定義這個函式就能滿足註冊的需求。
如何操作呢?我們可以先組裝出一個dict,然後,把該dict轉換為關鍵字引數傳進去:
1 >>> extra = {'city': 'Hangzhou', 'job': 'Engineer'}
2 >>> person('Jack', 36, city=extra['city'], job=extra['job'])
3 name: Jack age: 36 other: {'city': 'Hangzhou', 'job': 'Engineer'}
當然了,上面程式碼呼叫方式有點煩,通過dict鍵來查詢值。我們可以通過關鍵字簡化一下:
1 >>> extra = {'city': 'Hangzhou', 'job': 'Engineer'}
2 >>> person('Jack', 36, **extra)
3 name: Jack age: 36 other: {'city': 'Hangzhou', 'job': 'Engineer'}
**extra表示把extra這個dict的所有key-value用關鍵字引數傳入到函式的**kw引數,kw將獲得一個dict。
5.命名關鍵字引數
對於關鍵字引數,函式的呼叫者可以傳入任意不受限制的關鍵字引數。至於到底傳入了哪些,就需要在函式內部通過kw
檢查。
仍以person()函式為例,我們希望檢查是否有city和job引數:
1 def person(name, age, **kw):
2 if 'city' in kw:
3 # 有city引數
4 pass
5 if 'job' in kw:
6 # 有job引數
7 pass
8 print('name:', name, 'age:', age, 'other:', kw)
如果要限制關鍵字引數的名字,就可以用命名關鍵字引數,例如,只接收city和job作為關鍵字引數。這種方式定義的函式如下:
1 def person(name, age, *, city, job):
2 print(name, age, city, job)
和關鍵字引數*kw不同,命名關鍵字引數需要一個特殊分隔符,*後面的引數被視為命名關鍵字引數。 呼叫命名關鍵字引數方式如下:
1 #呼叫命名關鍵字引數
2 >>> person('Jack', 36, city='Hangzhou', job='Engineer')
3 Jack 36 Hangzhou Engineer
那如果引數中有可變引數,那該怎麼辦呢?
若可變引數後面跟著命名關鍵字引數,後面跟著的命名關鍵字引數就不再需要一個特殊分隔符*了。
1 def person(name, age, *args, city, job):
2 print(name, age, args, city, job)
命名關鍵字引數必須傳入引數名,這和位置引數不同。如果沒有傳入引數名,呼叫將報錯。而命名關鍵字引數可以有預設值,從而簡化呼叫:
1 def person(name, age, *, city='Hangzhou', job):
2 print(name, age, city, job)
由於命名關鍵字引數city具有預設值,呼叫時,可不傳入city引數:
1 >>> person('Jack', 36, job='Engineer')
2 Jack 36 Hangzhou Engineer
6.引數組合
目前,函式中共有5種常用的引數型別。若只傳入一種型別的引數,這太簡單了。難點在哪?難點就在引數組合使用,那是相當噁心。不過,平時最好不要混合使用引數,不然容易搞得“烏煙瘴氣”。
OK!言歸正傳,不然跑題啦。
Python中,定義一個函式,我們可以用必選引數、預設引數、可變引數、關鍵字引數和命名關鍵字引數,這5種引數都可以組合使用。但是請注意,引數定義的順序必須是:必選引數、預設引數、可變引數、命名關鍵字引數和關鍵字引數。
下面來定義一個函式,該函式引數包含一種或幾種引數。
1 def f1(a, b, c=0, *args, **kw):
2 print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
3
4 def f2(a, b, c=0, *, d, **kw):
5 print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
在函式呼叫的時候,Python直譯器自動按照引數位置和引數名把對應的引數傳進去。
1 >>> f1(1, 2)
2 a = 1 b = 2 c = 0 args = () kw = {}
3 >>> f1(1, 2, c=3)
4 a = 1 b = 2 c = 3 args = () kw = {}
5 >>> f1(1, 2, 3, 'a', 'b')
6 a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
7 >>> f1(1, 2, 3, 'a', 'b', x=99)
8 a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
9 >>> f2(1, 2, d=99, ext=None)
10 a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
最神奇的是通過一個tuple和dict,你也可以呼叫上述函式:
1 >>> args = (1, 2, 3, 4)
2 >>> kw = {'d': 99, 'x': '#'}
3 >>> f1(*args, **kw)
4 a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
5 >>> args = (1, 2, 3)
6 >>> kw = {'d': 88, 'x': '#'}
7 >>> f2(*args, **kw)
8 a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
所以,對於任意函式,都可以通過類似func(*args, **kw)
的形式呼叫它,無論它的引數是如何定義的。
然而,雖然函式引數型別多達5種,但不要同時使用太多的組合,否則函式介面的可理解性很差。哎,簡簡單單才是真啊。
7.函式引數小結
引數,作為函式傳入值的媒介,這裡有必要做一個總結。
一、Python的函式具有非常靈活的引數形態,既可以實現簡單的呼叫,又可以傳入非常複雜的引數;
二、預設引數一定要用不可變物件,如果是可變物件,程式執行時會有邏輯錯誤;
三、*args
是可變引數,args接收的是一個tuple;
四、**kw
是關鍵字引數,kw接收的是一個dict;
五、可變引數既可以直接傳入:func(1, 2, 3)
,又可以先組裝list或tuple,再通過*args
傳入:func(*(1, 2, 3))
;
六、關鍵字引數既可以直接傳入:func(a=1, b=2)
,又可以先組裝dict,再通過**kw
傳入:func(**{'a': 1, 'b': 2});
七、使用*args
和**kw
是Python的習慣寫法,當然也可以用其他引數名,但最好使用習慣用法;
八、命名的關鍵字引數是為了限制呼叫者可以傳入的引數名,同時可以提供預設值;
九、定義命名的關鍵字引數在沒有可變引數的情況下不要忘了寫分隔符*
,否則定義的將是位置引數。
故而,為了學好Python中的函式部分,引數不容忽視。
函式呼叫
在學習了函式的定義之後,我們應該需要呼叫函式,獲取我們想要的資料。
如何呼叫函式呢?語法:函式名(引數)
Python中,大佬們內建了許多有點用的函式,而我們只需拿來就行(這讓我想起了魯迅的“拿來主義”)。
若要呼叫Python中的內建函式,我們首先要知道函式名和引數。哈哈,又是引數~~~
比如我想要求某數的絕對值。如果你不知道Python有相關的內建函式,就只能這麼做:
1 #求取絕對值
2 >>>def abs(num):
3 ... if num >= 0:
4 ... return num
5 ... else:
6 ... return (-num)
7 ...
8 >>>abs(9)
9 9
10 >>>abs(0)
11 0
12 >>>abs(-8)
13 8
上述程式碼雖然可以實現求絕對值的功能,但是太繁瑣,需要敲幾行程式碼才能實現該功能。然而,Python中有這個函式可以直接呼叫並輸出結果。
1 #Python內建函式:abs()
2 >>>abs(-9)
3 9
4 >>>abs(9)
5 9
6 #獲取幫助文件
7 >>>help(abs)
8 Help on built-in function abs in module builtins:
9
10 abs(x, /)
11 Return the absolute value of the argument.
對於函式引數,通常會遇到以下兩個問題:
1.如果函式傳入引數的數量錯誤,會如何呢?簡單,直接Error唄。比如abs():
1 #函式傳入引數的數量錯誤
2 >>> abs(-9,89)
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 TypeError: abs() takes exactly one argument (2 given)
2.如果傳入的引數數量是對的,但引數型別不能被函式所接受,也會報TypeError
的錯誤,並且給出錯誤資訊:str
是錯誤的引數型別:
1 #傳入的引數型別錯誤
2 >>> abs('a')
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 TypeError: bad operand type for abs(): 'str'
常見內建函式(Built-in Functions)
1.資料結構相關:list()、tuple()、dict()、str()……
2.數字相關:abs()、min()、max()、len()……
3.其他:int()、float()……
好,不一一例舉了,直接上圖吧~~~
資料型別轉換
Python內建的常用函式還包括資料型別轉換函式,比如int()
函式可以把其他資料型別轉換為整數:
1 #Python之資料型別轉換(int、float、str……)
2 >>> int('123')
3 123
4 >>> int(12.34)
5 12
6 >>> float('12.34')
7 12.34
8 >>> str(1.23)
9 '1.23'
10 >>> str(100)
11 '100'
12 >>> bool(1)
13 True
14 >>> bool('')
15 False
函式別名
瞭解Linux的讀者可能知道別名(alias,unalias)這個指令。Python中也有“別名”之說,比如把函式名賦給變數:
1 #函式“別名”
2 >>>abs(-8)
3 8
4 >>>a = abs
5 >>>a(-9)
6 9
7 >>>a(0)
8 0
9 >>>a(9)
10 9
遞迴函式
講述遞迴函式之前,我想起一個東西:階乘(n!)。舉個例子,我們來計算階乘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)用遞迴的方式寫出來就是:
1 #遞迴函式
2 >>>def fact(n):
3 ... if n == 1:
4 ... return 1
5 ... else:
6 ... return fact(n - 1) * n
7 ...
8 >>>fact(1)
9 1
10 >>>fact(5)
11 120
12 #遞迴函式之棧溢位
13 >>>fact(1000)
14 Traceback (most recent call last):
15 File "<stdin>", line 1, in <module>
16 File "<stdin>", line 5, in x
17 File "<stdin>", line 5, in x
18 File "<stdin>", line 5, in x
19 [Previous line repeated 994 more times]
20 File "<stdin>", line 2, in x
21 RecursionError: maximum recursion depth exceeded in comparison
如程式碼所示,使用遞迴函式的優點是邏輯簡單清晰,缺點是過深的呼叫會導致棧溢位。
針對尾遞迴優化的語言可以通過尾遞迴防止棧溢位。尾遞迴事實上和迴圈是等價的,沒有迴圈語句的程式語言只能通過尾遞迴實現迴圈。
Python標準的直譯器沒有針對尾遞迴做優化,任何遞迴函式都存在棧溢位的問題。
什麼是尾遞迴?這個請讀者自行查詢唄,這裡就不介紹啦,嘿嘿~~~
下面來個斐波拉契數列:
1 #斐波拉契數列
2 >>>def fibo(arg1,arg2):
3 ... if arg1 == 0:
4 ... print(arg1,arg2)
5 ... arg3 = arg1 + arg2
6 ... print(arg3)
7 ... fibo(arg2, arg3)
8 ...
9 >>>fibo(0,1)
上述程式碼展示的斐波拉契數列會一直計算,直至棧溢位:
1 #斐波拉契數列導致棧溢位
2 488272859468887457959087733119242564077850743657661180827326798539177758919828135114407499369796465649524266755391104990099120377
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 File "<stdin>", line 6, in fibo
6 File "<stdin>", line 6, in fibo
7 File "<stdin>", line 6, in fibo
8 [Previous line repeated 992 more times]
9 File "<stdin>", line 5, in fibo
10 RecursionError: maximum recursion depth exceeded while calling a Python object
11 16602747662452097049541800472897701834948051198384828062358553091918573717701170201065510185595898605104094736918879278462233015981029522997836311232618760539199036765399799926731433239718860373345088375054249
如何才能避免棧溢位呢?自己想唄,要不大腦會生鏽的。
對於漢羅塔問題,利用遞迴來解決該問題也是相當的簡單,且程式碼清晰:
1 #遞迴解決漢羅塔問題
2 >>>def hanrota(n,a,b,c):
3 ... if n == 1:
4 ... print(a,'-->',c)
5 ... else:
6 ... hanrota(n - 1,a,c,b)
7 ... hanrota(1,a,b,c)
8 ... hanrota(n - 1,b,a,c)
9 ...
10 >>>hanrota(3,'A','B','C')
11 A --> C
12 A --> B
13 C --> B
14 A --> C
15 B --> A
16 B --> C
17 A --> C
漢羅塔問題
匿名函式
定義函式真得不可多得,但有時候不需要顯示地定義函式。因此,函式也需要靈活地運用。使用匿名函式可以更加方便。
匿名函式語法:
lambda x: x * x(關鍵字lambda
表示匿名函式,冒號前面的x
表示函式引數)
1 def f(x):
2 return x * x
匿名函式的好處:因為函式沒有名字,不必擔心函式名衝突。
1 def is_odd(n):
2 return n % 2==1
3
4 L = list(filter(lambda n: n%2==1,range(1,20)))
5 print(L)
1.匿名函式也是一個函式物件,也可以把匿名函式賦值給一個變數,再利用變數來呼叫該函式。
2.Python中,匿名函式可以作為返回值返回並輸出結果。