《Python從小白到大牛》已經上市!
程序中反覆執行的代碼能夠封裝到一個代碼塊中,這個代碼塊模仿了數學中的函數,具備函數名、參數和返回值,這就是程序中的函數。html
Python中的函數很靈活,它能夠在模塊中,但類以外定義,即函數,做用域是當前模塊;也能夠在別的函數中定義,即嵌套函數;還能夠在類中定義,即方法。python
在前面的學習過程當中也用到了一些函數,若是len()、min()和max(),這些函數都由Python官方提供的,稱爲內置函數(Built-in
Functions, 縮寫BIF)。程序員
注意
Python做爲解釋性語言函數必須先定義後調用,也就是定義函數必須在調用函數以前,不然會有錯誤發生。算法
本節介紹自定義函數,自定義函數的語法格式以下:shell
def 函數名(參數列表) : 函數體 return 返回值
在Python中定義函數時,關鍵字是def,函數名須要符合標識符命名規範;多個參數列表之間能夠用逗號(,)分隔,固然函數也能夠沒有參數。若是函數有返回數據,就須要在函數體最後使用return語句將數據返回;若是沒有返回數據,則函數體中可使用return
None或省略return語句。編程
函數定義示例代碼以下:安全
# coding=utf-8 # 代碼文件:chapter10/ch10.1.py def rectangle_area(width, height): ① area = width * height return area ② r_area = rectangle_area(320.0, 480.0) ③ print("320x480的長方形的面積:{0:.2f}".format(r_area))
上述代碼第①行是定義計算長方形面積的函數rectangle_area,它有兩個參數,分別是長方形的寬和高,width和height是參數名。代碼第②行代碼是經過return返回函數計算結果。代碼第③行是調用rectangle_area函數。數據結構
Python中的函數參數很靈活,具體體如今傳遞參數有多種形式上。本節介紹幾種不一樣形式參數和調用方式。app
爲了提升函數調用的可讀性,在函數調用時可使用關鍵字參數調用。採用關鍵字參數調用函數,在函數定義時不須要作額外的工做。編程語言
示例代碼以下:
# coding=utf-8 # 代碼文件:chapter10/ch10.2.1.py def print_area(width, height): area = width * height print("{0} x {1} 長方形的面積:{2}".format(width, height, area)) print_area(320.0, 480.0) # 沒有采用關鍵字參數函數調用 ① print_area(width=320.0, height=480.0) # 採用關鍵字參數函數調用② print_area(320.0, height=480.0) # 採用關鍵字參數函數調用 ③ # print_area(width=320.0, height) # 發生錯誤 ④ print_area(height=480.0, width=320.0) # 採用關鍵字參數函數調用 ⑤
print_area函數有兩個參數,在調用時沒有采用關鍵字參數函數調用,見代碼第①行,也可使用關鍵字參數調用函數,見代碼第②行、第③行和第⑤行,其中width和height是參數名。從上述代碼比較可見採用關鍵字參數調用函數,調用者可以清晰地看出傳遞參數的含義,關鍵字參數對於有多參數函數調用很是有用。另外,採用關鍵字參數函數調用時,參數順序能夠與函數定義時參數順序不一樣。
注意
在調用函數時,一旦其中一個參數採用了關鍵字參數形式傳遞,那麼其後的全部參數都必須採用關鍵字參數形式傳遞。代碼第④行的函數調用中第一個參數width採用了關鍵字參數形式,而它後面的參數沒有采用關鍵字參數形式,所以會有錯誤發生。
在定義函數的時候能夠爲參數設置一個默認值,當調用函數的時候能夠忽略該參數。來看下面的一個示例:
# coding=utf-8 # 代碼文件:chapter9/ch10.2.2.py def make_coffee(name="卡布奇諾"): return "製做一杯{0}咖啡。".format(name)
上述代碼定義了makeCoffee函數,能夠幫助我作一杯香濃的咖啡。因爲我喜歡喝卡布奇諾,就把它設置爲默認值。在參數列表中,默認值能夠跟在參數類型的後面,經過等號提供給參數。
在調用的時候,若是調用者沒有傳遞參數,則使用默認值。調用代碼以下:
coffee1 = make_coffee("拿鐵") ① coffee2 = make_coffee() ② print(coffee1) # 製做一杯拿鐵咖啡。 print(coffee2) # 製做一杯卡布奇諾咖啡。
其中第①行代碼是傳遞"拿鐵"參數,沒有使用默認值。第②行代碼沒有傳遞參數,所以使用默認值。
提示
在其餘語言中make_coffee函數能夠採用重載實現多個版本。Python不支持函數重載,而是使用參數默認值的方式提供相似函數重載的功能。由於參數默認值只須要定義一個函數就能夠了,而重載則須要定義多個函數,這會增長代碼量。
Python中函數的參數個數能夠變化,它能夠接受不肯定數量的參數,這種參數稱爲可變參數。Python中可變參數有兩種,在參數前加*或**形式,*可變參數在函數中被組裝成爲一個元組,**可變參數在函數中被組裝成爲一個字典。
下面看一個示例:
def sum(*numbers, multiple=1): total = 0.0 for number in numbers: total += number return total * multiple
上述代碼定義了一個sum()函數,用來計算傳遞給它的全部參數之和。*numbers是可變參數。在函數體中參數numbers被組裝成爲一個元組,可使用for循環遍歷numbers元組,計算他們的總和,而後返回給調用者。
下面是三次調用sum()函數的代碼:
print(sum(100.0, 20.0, 30.0)) # 輸出150.0 print(sum(30.0, 80.0)) # 輸出110.0 print(sum(30.0, 80.0, multiple=2)) # 輸出220.0 ① double_tuple = (50.0, 60.0, 0.0) # 元組或列表 ② print(sum(30.0, 80.0, *double_tuple)) # 輸出220.0 ③
能夠看到,每次所傳遞參數的個數是不一樣的,前兩次調用時都省略了multiple參數,第三次調用時傳遞了multiple參數,此時multiple應該使用關鍵字參數傳遞,不然有錯誤發生。
若是已經有一個元組變量(見代碼第②行),可否傳遞給可變參數呢?這須要使用對元組進行拆包,見代碼第③行在元組double_tuple前面加上單星號(*),單星號在這裏表示將double_tuple拆包爲50.0,
60.0, 0.0形式。另外,double_tuple也能夠是列表對象。
注意
*可變參數不是最後一個參數時,後面的參數須要採用關鍵字參數形式傳遞。代碼第①行30.0,
80.0是可變參數,後面multiple參數須要關鍵字參數形式傳遞。
下面看一個示例:
def show_info(sep=':', **info): print('-----info------') for key, value in info.items(): print('{0} {2} {1}'.format(key, value, sep))
上述代碼定義了一個show_info()函數,用來輸出一些信息,其中參數sep信息分隔符號,默認值是冒號(:)。**info是可變參數,在函數體中參數info被組裝成爲一個字典。
注意 **可變參數必須在正規參數以後,若是本例函數定義改成show_info(**info,sep=':')形式,會發生錯誤。
下面是三次調用show_info()函數的代碼:
show_info('->', name='Tony', age=18, sex=True) ① show_info(sutdent_name='Tony', sutdent_no='1000', sep='-') ② stu_dict = {'name': 'Tony', 'age': 18} # 建立字典對象 show_info(**stu_dict, sex=True, sep='=') # 傳遞字典stu_dict ③
上述代碼第①行是調用函數show_info(),第一個參數'-\>'是傳遞給sep,其後的參數name='Tony',
age=18,
sex=True是傳遞給info,這種參數形式事實上就是關鍵字參數,注意鍵不要用引號包裹起來。
代碼第②行是調用函數show_info(),sep也採用關鍵字參數傳遞,這種方式sep參數能夠放置在參數列表的任何位置,其中的關鍵字參數被收集到info字典中。
代碼第③行是調用函數show_info(),其中字典對象stu_dict,傳遞時stu_dict前面加上雙星號(**),雙星號在這裏表示將stu_dict拆包爲key=value對的形式。
Python函數的返回值也是比較靈活的,主要有三種形式:無返回值、單一返回值和多返回值。前面使用的函數基本都單一返回值,本節重點介紹無返回值和多返回值兩種形式。
有的函數只是爲了處理某個過程,此時能夠將函數設計爲無返回值的。所謂無返回值,事實上是返回None,None表示沒有實際意義的數據。
無返回值函數示例代碼以下:
# coding=utf-8 # 代碼文件:chapter9/ch10.3.1.py def show_info(sep=':', **info): ① """定義**可變參數函數""" print('-----info------') for key, value in info.items(): print('{0} {2} {1}'.format(key, value, sep)) return # return None 或省略 ② result = show_info('->', name='Tony', age=18, sex=True) print(result) # 輸出 None def sum(*numbers, multiple=1): ③ """定義*可變參數函數""" if len(numbers) == 0: return # return None 或省略 ④ total = 0.0 for number in numbers: total += number return total * multiple print(sum(30.0, 80.0)) # 輸出110.0 print(sum(multiple=2)) # 輸出 None ⑥
上述代碼定義了兩個函數,這個兩個函數事實上是在10.3.2節示例基礎上的重構。其中代碼第①行的show_info()只是輸出一些信息,不須要返回數據,所以能夠省略return語句。若是必定要使用return語句,見代碼第②行在函數結束前使用return或return
None。
對於本例中的show_info()函數強加return語句顯然是畫蛇添足,可是有時使用return或return
None是必要的。代碼第③行定義了sum()函數,若是numbers中數據是空的,後面的求和計算也就沒有意義了,能夠在函數的開始判斷numbers中算法有數據,若是沒有數據則使用return或return
None跳出函數,見代碼第④行。
有時須要函數返回多個值,實現返回多個值的實現方式有不少,簡單實現是使用元組返回多個值,由於元組做爲數據結構能夠容納多個數據,另外元組是不可變的,使用起來比較安全。
下面來看一個示例:
# coding=utf-8 # 代碼文件:chapter9/ch10.3.2.py def position(dt, speed): ① posx = speed[0] * dt ② posy = speed[1] * dt ③ return (posx, posy) ④ move = position(60.0, (10, -5)) ⑤ print("物體位移:({0}, {1})".format(move[0], move[1])) ⑥
這個示例是計算物體在指定時間和速度時的位移。第①行代碼是定義position函數,其中dt參數是時間,speed參數是元組類型,speed第一個元素X軸上的速度,speed第二個元素Y軸上的速度。position函數的返回值也是元組類型。
函數體中的第②行代碼是計算X方向的位移,第③行代碼是計算Y方向的位移。最後,第④行代碼將計算後的數據返回,(posx,
posy)是元組類型實例。
第⑤行代碼調用函數,傳遞的時間是60.0秒,速度是(10,
-5)。第⑥行代碼打印輸出結果,結果以下:
物體位移:(600.0, -300.0)
變量能夠在模塊中建立,做用域是整個模塊,稱爲全局變量。變量也能夠在函數中建立,默認狀況下做用域是整個函數,稱爲局部變量。
示例代碼以下:
# coding=utf-8 # 代碼文件:chapter9/ch10.4.py # 建立全局變量x x = 20 ① def print_value(): print("函數中x = {0}".format(x)) ② print_value() print("全局變量x = {0}".format(x))
輸出結果:
函數中x = 20 全局變量x = 20
上述代碼第①行是建立全局變量x,全局變量做用域是整個模塊,因此在print_value()函數中也能夠訪問變量x,見代碼第②行。
修改上述示例代碼以下:
# 建立全局變量x x = 20 def print_value(): # 建立局部變量x x = 10 ① print("函數中x = {0}".format(x)) print_value() print("全局變量x = {0}".format(x))
輸出結果:
函數中x = 10 全局變量x = 20
上述代碼的是print_value()函數中添加x =
10語句,見代碼第①行,這會函數中建立x變量,函數中的x變量與全局變量x命名相同,在函數做用域內會屏蔽全局x變量。
函數中建立的變量默認做用域是當前函數,這能夠防止程序員少犯錯誤,由於函數中建立的變量,若是做用域是整個模塊,那麼在其餘函數中也能夠訪問,Python沒法從語言層面定義常量,因此在其餘函數中因爲誤操做修改了變量,這樣一來很容易致使程序出現錯誤。即使筆者不贊同,但Python提供這種可能,經過在函數中將變量聲明爲global的,能夠把變量的做用域變成全局的。
修改上述示例代碼以下:
# 建立全局變量x x = 20 def print_value(): global x ① x = 10 ② print("函數中x = {0}".format(x)) print_value() print("全局變量x = {0}".format(x))
輸出結果:
函數中x = 10 全局變量x = 10
代碼第①行是在函數中聲明x變量做用域爲全局變量,因此代碼第②行修改x值,就是修改全局變量x的數值。
在一個函數中使用return關鍵字返回數據,可是有時候會使用yield 關鍵字返回數據。yield 關鍵字的函數返回的是一個生成器(generator)對象,生成器對象是一種可迭代對象。
若是想計算平方數列,一般的實現代碼以下:
def square(num): ① n_list = [] for i in range(1, num + 1): n_list.append(i * i) ② return n_list ③ for i in square(5): ④ print(i, end=' ')
返回結果是:
1 4 9 16 25
首先定義一個函數,見代碼第①行。代碼第②行經過循環計算一個數的平方,並將結果保存到一個列表對象n_list中。最後返回列表對象,見代碼第③行。代碼第④行是遍歷返回的列表對象。
在Python中還能夠有更加好解決方案,實現代碼以下:
def square(num): for i in range(1, num + 1): yield i * i ① for i in square(5): ② print(i, end=' ')
返回結果是:
1 4 9 16 25
代碼第①行使用了yield關鍵字返回平方數,再也不須要return關鍵字了。代碼第②行調用函數square()返回的是生成器對象。生成器對象是一種可迭代對象,可迭代對象經過next()方法得到元素,代碼第②行的for循環可以遍歷可迭代對象,就是隱式地調用生成器的next()方法得到元素的。
顯式地調用生成器的next()方法,在Python Shell中運行示例代碼以下:
>>> def square(num): for i in range(1, num + 1): yield i * i >>> n_seq = square(5) <generator object square at 0x000001C8F123CE60> >>> n_seq.__next__() ① 1 >>> n_seq.__next__() 4 >>> n_seq.__next__() 9 >>> n_seq.__next__() 16 >>> n_seq.__next__() 25 >>> n_seq.__next__() ② Traceback (most recent call last): File "<pyshell#33>", line 1, in <module> n_seq.__next__() StopIteration >>>
上述代碼第①行\~第②行調用了6次next()方法,但第6次調用則會拋出StopIteration異常,這是由於已經沒有元素可迭代了。
生成器函數是經過yield返回數據,與return不一樣的是:return語句一次返回全部數據,函數調用結束;而yield語句只返回一個元素數據,函數調用不會結束,只是暫停,直到next()方法被調用,程序繼續執行yield語句以後的語句代碼。這個過程如圖10-1所示。
注意
生成器特別適合用於遍歷一些大序列對象,它無須將對象的全部元素都載入內存後纔開始進行操做。而是僅在迭代至某個元素時纔會將該元素載入內存。
在本節以前定義的函數都是全局函數,並將他們定義在全局做用域中。函數還可定義在另外的函數體中,稱做「嵌套函數」。
示例代碼:
# coding=utf-8 # 代碼文件:chapter10/ch10.6.py def calculate(n1, n2, opr): multiple = 2 # 定義相加函數 def add(a, b): ① return (a + b) * multiple # 定義相減函數 def sub(a, b): ② return (a - b) * multiple if opr == '+': return add(n1, n2) else: return sub(n1, n2) print(calculate(10, 5, '+')) # 輸出結果是30 # add(10, 5) 發生錯誤 ③ # sub(10, 5) 發生錯誤 ④
上述代碼中定義了兩個嵌套函數:add()和sub(),見代碼第①行和第②行。嵌套函數能夠訪問所在外部函數calculate()中的變量multiple,而外部函數不能訪問嵌套函數局部變量。另外,嵌套函數的做用域在外部函數體內,所以在外部函數體以外直接訪問嵌套函數會發生錯誤,見代碼第③行和第④行。
函數式編程(functional
programming)與面向對象編程同樣都一種編程範式,函數式編程,也稱爲面向函數的編程。
Python並非完全的函數式編程語言,可是仍是提供了一些函數式編程必備的技術,主要有:函數類型和Lambda表達式,他們是實現函數式編程的基礎。
Python提供了一種函數類型function,任何一個函數都有函數類型,可是函數調用時,就建立了函數類型實例,即函數對象。函數類型實例與其餘類型實例同樣,在使用場景上沒有區別:它能夠賦值給一個變量;也能夠做爲參數傳遞給一個函數;還能夠做爲函數返回值使用。
爲了理解函數類型,先重構10.6節中嵌套函數的示例,示例代碼以下:
# coding=utf-8 # 代碼文件:chapter10/ch10.7.1.py def calculate_fun(opr): ① # 定義相加函數 def add(a, b): return a + b # 定義相減函數 def sub(a, b): return a - b if opr == '+': return add ② else: return sub ③ f1 = calculate_fun('+') ④ f2 = calculate_fun('-') ⑤ print(type(f1)) print("10 + 5 = {0}".format(f1(10, 5))) ⑥ print("10 - 5 = {0}".format(f2(10, 5))) ⑦
輸出結果:
<class 'function'> 10 + 5 = 30 10 - 5 = 10
'+'爲True時返回add函數名,不然返回sub函數名,見代碼第③行。這裏的函數名本質上函數對象。calculate_fun()函數與10.5節示例calculate()函數不一樣之處在於,calculate_fun()函數返回的是函數對象,calculate()函數返回的是整數(相加或相減計算以後的結果)。因此代碼第④行的f1是add()函數對象,代碼第⑤行的f2是sub()函數對象。
函數對象是能夠與函數同樣進行調用的。事實上在第⑥行以前沒有真正調用add()函數進行相加計算,f1(10,
5)表達式才真的調用了add()函數。第⑦行的f2(10, 5)表達式是調用sub()函數。
理解了函數類型和函數對象,學習Lambda表達式就簡單了。Lambda表達式本質上一種匿名函數,匿名函數也是函數有函數類型,也能夠建立函數對象。
定義Lambda表達式語法以下:
lambda 參數列表 : Lambda體
lambda是關鍵字聲明這是一個Lambda表達式,「參數列表」與函數的參數列表是同樣的,但不須要小括號包裹起來,冒號後面是「Lambda體」,Lambda表達式主要的代碼在此處編寫,相似於函數體。
注意
Lambda體部分不能是一個代碼塊,不能包含多條語句,只有一條語句,語句會計算一個結果返回給Lambda表達式,可是與函數不一樣是,不須要使用return語句返回。與其餘語言中的Lambda表達式相比,Python中提供Lambda表達式只能處理一些簡單的計算。
重構10.7.1節示例,代碼以下:
# coding=utf-8 # 代碼文件:chapter10/ch10.7.2.py def calculate_fun(opr): if opr == '+': return lambda a, b: (a + b) ① else: return lambda a, b: (a - b) ② f1 = calculate_fun('+') f2 = calculate_fun('-') print(type(f1)) print("10 + 5 = {0}".format(f1(10, 5))) print("10 - 5 = {0}".format(f2(10, 5)))
輸出結果:
<class 'function'> 10 + 5 = 30 10 - 5 = 10
上述代碼第①行替代了add()函數,第②行替代了sub()函數,代碼變的很是的簡單。
函數式編程本質是經過函數處理數據,過濾、映射和聚合是處理數據的三大基本操做。針對但其中三大基本操做Python提供了三個基礎的函數:filter()、map()和reduce()。
過濾操做使用filter()函數,它能夠對可迭代對象的元素進行過濾,filter()函數語法以下:
filter(function, iterable)
其中參數function是一個函數,參數iterable是可迭代對象。filter()函數調用時iterable會被遍歷,它的元素逐一傳入function函數,function函數返回布爾值,在function函數中編寫過濾條件,若是爲True的元素被保留,若是爲False的元素被過濾掉。
下面經過一個示例介紹一下filter()函數使用,示例代碼以下:
users = ['Tony', 'Tom', 'Ben', 'Alex'] users_filter = filter(lambda u: u.startswith('T'), users) ① print(list(users_filter))
輸出結果:
['Tony', 'Tom']
代碼第①行調用filter()函數過濾users列表,過濾條件是T開通的元素,lambda u:
u.startswith('T')是一個Lambda表達式,它提供了過濾條件。filter()函數還不是一個列表,須要使用list()函數轉換過濾以後的數據爲列表。
再看一個示例:
number_list = range(1, 11) nmber_filter = filter(lambda it: it % 2 == 0, number_list) print(list(nmber_filter))
該示例實現了獲取1\~10數字中的偶數,輸出結果以下:
[2, 4, 6, 8, 10]
映射操做使用map()函數,它能夠對可迭代對象的元素進行變換,map()函數語法以下:
map(function, iterable)
其中參數function是一個函數,參數iterable是可迭代對象。map()函數調用時iterable會被遍歷,它的元素逐一傳入function函數,在function函數中對元素進行變換。
下面經過一個示例介紹一下map()函數使用,示例代碼以下:
users = ['Tony', 'Tom', 'Ben', 'Alex'] users_map = map(lambda u: u.lower(), users) ① print(list(users_map))
輸出結果:
['tony', 'tom', 'ben', 'alex']
上述代碼第①行調用map()函數將users列表元素轉換爲小寫字母,變換使用Lambda表達式lambda
u:
u.lower()。map()函數返回的還不是一個列表,須要使用list()函數將過濾以後的數據轉換爲列表。
函數式編程時數據能夠從一個函數「流」入另一個函數,可是遺憾的是Python並不支持「鏈式」API。例如想獲取users列表中「T」開通的名字,再將其轉換爲小寫字母,這樣的需求須要使用filter()函數進行過濾,再使用map()函數進行映射變換。實現代碼以下:
users = ['Tony', 'Tom', 'Ben', 'Alex'] users_filter = filter(lambda u: u.startswith('T'), users) # users_map = map(lambda u: u.lower(), users_filter) ① users_map = map(lambda u: u.lower(), filter(lambda u: u.startswith('T'), users)) ② print(list(users_map))
上述代碼第①行和第②行實現相同功能。
聚合操做會將多個數據聚合起來輸出單個數據,聚合操做中最基礎的是概括函數reduce(),reduce()函數會將多個數據按照指定的算法積累疊加起來,最後輸出一個數據。
reduce()函數語法以下:
reduce(function, iterable[, initializer])
參數function是聚合操做函數,該函數有兩個參數,參數iterable是可迭代對象;參數initializer初始值。
下面經過一個示例介紹一下reduce()函數使用。下面示例實現了對一個數列求和運算,代碼以下:
from functools import reduce ① a = (1, 2, 3, 4) a_reduce = reduce(lambda acc, i: acc + i, a) # 10 ② # a_reduce = reduce(lambda acc, i: acc + i, a, 2) # 12 ③ print(a_reduce)
reduce()函數是在functools模塊中定義的,因此要使用reduce()函數須要導入functools模塊,見代碼第①行。代碼第②行是調用reduce()函數,其中lambda
acc, i: acc +
i是進行聚合操做的Lambda表達式,該Lambda表達式有兩個參數,其中acc參數是上次累積計算結果,i當前元素,acc
+
i表達式是進行累加。reduce()函數最後的計算結果是一個數值,直接可使用經過reduce()函數返回。代碼第行是傳入了初始值2,則計算的結果是12。
經過對本章內容的學習,讀者能夠熟悉在Python中如何定義函數、函數參數和函數返回值,瞭解函數變量做用域和嵌套函數。最後還介紹了Python中函數式編程基礎。
http://www.zhijieketang.com/group/8
做者微博:@tony_關東昇br/>郵箱:eorient@sina.com智捷課堂×××公共號:zhijieketangPython讀者服務QQ羣:628808216