薛欽亮的python教程(四)十分鐘搞明白python的函式
技術標籤:python從入門到入土列表pythonlambda程式語言
人生苦短,我愛python
接上篇薛欽亮的python教程(三)python的分支與迴圈居然這麼簡單
在搞明白python的基本語法、資料型別、迴圈和分支之後,今天來學習一下python的函式。
首先,為什麼要有函式呢?主要是因為如果某一段程式碼需要執行很多次,寫很多遍效率很低,喜歡悠閒的程式設計師為了提高編寫的效率,所以把這一段程式碼封裝成一個模組,這個就是函式。
一、定義函式
def function(param1, param2, param3, ……):
"""the body of the function"""
# do something
return something #if have something to return
python中用def(define)來定義函式,後面緊接著跟函式名,函式名後面的小括號中間用逗號隔開任意個變數,注意小括號後面還有一個冒號,以及函式體要整體縮排。
函式可以定義返回值,用return語句來返回。如果不寫return語句,預設返返回為None。
二、呼叫函式
函式定義之後是要拿來用的。用一個例子來具體說明。
def add(a,b):
print("a:",a,"b:",b)
c = a + b
return c
x = 1
y = 2
z = add(x,y)
print("z:",z)
輸出為:
a: 1 b: 2
z: 3
需要掌握的點有三個:
- 函式定義必須在呼叫之前,否則會出現函式未定義的錯誤,以及python中如果定義多個相同函式,呼叫時會呼叫此處往上最近的定義的函式。
- 函式呼叫時,傳遞的引數的值會按順序賦給定義的引數,在函式內部參與運算(這個過程其實是實參與形參的繫結)。
- 注意獲取返回值的方法,宣告一個新變數z等於函式的執行結果。
三、引數型別
1. 必備引數(位置引數)
上面的例子中所用的a和b就是必備引數,必須按順序傳入。
2. 預設引數
在定義函式時,可以給一些引數設定預設值。但是如果一個引數設定了預設值,其後面的引數都需要設定預設值,在呼叫時有預設值的引數可以不傳遞實際的值。
舉個栗子:
def SUM(a,b=0,c=0): #這是一個沒什麼意義的函式
return a+b+c
print(SUM(1,2))
可以看到,結果為3。對於引數a,按順序賦予了1,引數b預設為0,但按順序被賦予了值2,引數c沒有被賦予值,採用了預設值0,結果為3。
3. 關鍵字引數
python的函式引數值可以在呼叫時通過關鍵字指定,這樣就可以按照任意順序去傳遞引數值,而不必受位置引數的限制。
def Print(a,b):
print("a:",a)
print("b:",b)
Print(b='a',a='b')
結果為:
a: b
b: a
可以看到,我們先指定了b的值,為字元’a’,然後指定了a的值,為字元’b’。
4. 多值引數
這是所有引數中最靈活的一種,當函式需要處理的引數個數不確定,我們可以用這一辦法。
要講明白這種引數傳遞方式,首先講一下python中的特殊語法:拆包(*)。
- 如果一個變數為list、tuple、string型別,在變數名前加一個星號(*)可以把它拆成其所包含的多個元素。
t = (1,2,3)
print(*t)
結果為:
1 2 3
- 如果一個變數為dict型別,在變數名前加兩個星號(**)可以把它拆成其所包含的多個鍵值對。
一般習慣上,引數中前者用*args來命名,後者用**kwargs來命名。
分別來看兩個栗子:
def SUM(num,*args):
allsum = 0
for i in args:
print(i)
allsum += num*i
return allsum
print(SUM(5,1,2,3,4))
可以看到,第一個引數被繫結到了num上,之後的所有引數都被認為是args,於是args接收了4個引數。
def PrintName(**kwargs):
for k in kwargs:
print(k,"'s number is",kwargs[k])
PrintName(xiaoming=1,xiaofang=2,xiaolan=3)
結果為:
xiaoming 's number is 1
xiaofang 's number is 2
xiaolan 's number is 3
可以看到,我們指定了三個引數(定義的時候只定義了**kwargs),成功被接收。
所以,多值引數其實就是把拆包和用引數傳遞複雜資料型別二者結合而形成的語法,並沒有什麼神奇之處。
四、引數傳遞須注意的點
形參屬於函式中的變數,有其自身的作用域,因此一般來講,改變函式內變數的值不會影響函式外面的值。
a = 10
def changeA(a):
a = 5
print("in changeA:",a)
changeA(a)
print("out changeA:",a)
結果為:
in changeA: 5
out changeA: 10
但在部落格的第二篇7.1賦值運算中,我曾提到過List型別變數賦值的特殊性,就是List變數賦值其實只是生成了一個別名,兩個變數指向的地址仍然相同,修改其中一個另一個也受到影響。在函式傳參中亦然。
a = [1,2,3,4,5,6]
def changeA(a):
a[0] = 0
print("in changeA:",a)
changeA(a)
print("out changeA:",a)
結果為:
in changeA: [0, 2, 3, 4, 5, 6]
out changeA: [0, 2, 3, 4, 5, 6]
除了List,還有Set、dict也具有這種特性,他們都是可變的複雜集合型別,在賦值和傳參時相當於只傳遞了物件所在的地址,而沒有生成新的物件。
五、lambda匿名函式
這個語法主要是為了增強python函式的靈活性,對於一些功能較簡單的小函式,可以不用def這種方式,而用lambda匿名函式的語法。
那怎麼用呢?看下面的這個栗子:
sumprint = lambda x,y: print(x,"+",y,"=",x+y)
sumprint(2,5)
sumprint(1,-4)
結果為:
2 + 5 = 7
1 + -4 = -3
我們用一個表示式定義了一個簡單的函式,這是比較方便的。如果沒有看到匿名函式的威力,再看下面這個栗子:
首先補充兩個python的內建函式。
- filter(function, iterable)
給定一個標準(通過函式返回True or False),來過濾一個可迭代物件的元素。 - map(function, iterable)
給定一個標準(通過函式返回True or False),來對一個可迭代物件的元素做對映。
這裡由於需要傳入函式,但這種函式又是比較簡單且不會再複用的函式,所以lambda表示式可以幫助我們實現這個目的,直接傳遞一個表示式進去,而不用額外定義函式。
看一下真正的栗子:
personlist = [{'age':10,'name':'daxiong'},{'age':5,'name':'jingxiang'},{'age':19,'name':'panghu'}]
for i in filter(lambda x: x['age'] < 18, personlist):
print(i)
結果為:
{'age': 10, 'name': 'daxiong'}
{'age': 5, 'name': 'jingxiang'}
這個栗子是篩選一個列表中年齡小於18歲的人員,如果我們要用普通的函式去定義也是可以的,但遠沒有lambda表示式簡單美觀。
num = [1,2,3,4,5,6,7,8,9,10]
list(map(lambda x: 2**x,num))
這個程式碼的功能是返回一個列表,列表中元素為2的1次方到2的10次方。至於為什麼不用for迴圈呢?因為map屬於python內建函式,執行速度更快,結合lambda函式就可以用python很方便地寫出高質量的程式碼。
六、函式名作為變數
既然lambda整個函式可以作為表示式賦給另一個變數,那我們普通的函式是否可以呢?答案是肯定的。
這個地方沒有什麼不好理解的,舉個例子即可。
def myfun(a,b):
print("myfun:",a+b)
yourfun = myfun
yourfun(3,5)
輸出是:myfun: 8,函式名myfun被當作一個變數賦給了yourfun,於是我們就可以用yourfun以同樣的方式來呼叫myfun這個函數了。
七、函式遞迴
函式套娃 遞迴這麼有趣的東西,python裡面怎麼可能沒有呢?這裡簡單舉一個遞迴求斐波那契數列的栗子吧(只是說明遞迴的用法,請不要在意這個函式的執行效率)。
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-2)+fibonacci(n-1)
本來打算是函式和類一起講解的,但發現函式的內容比我預想的要多,於是就分為兩篇吧。下一篇,python中的面向物件思想和類的使用,敬請期待!
大家的支援是我寫作的動力,希望各位看到可以多多鼓勵!