申明: 本站飛宇網 https://feiyetopro.blogspot.com/。自網路收集整理之書籍、文章、影音僅供預覽交流學習研究,其[書籍、文章、影音]情節內容, 評論屬其個人行為, 與本網站無關。版權歸原作者和出版社所有,請在下載 24 小時內刪除,不得用作商業用途;如果您喜歡其作品,請支持訂閱購買[正版]。謝謝!
第5章 模組與函數
本章將介紹Python中模組和函數的概念。結構化程式設計可以把複雜的問題分解為若干個元件,針對元件定義實現模組和函數。本章將詳細討論Python模組和函數的特性。最後還將介紹Python的函數化程式設計。
本章的知識點:
·模組的創建和使用
·內置模組
·常用模組
·函數的創建和使用
·lambda函數
·Generator函數
·函數化程式設計
5.1 Python程式的結構
Python的程式由包(package)、模組(module)和函數組成。模組是處理某一類問題的集合,模組由函數和類組成。包是由一系列模組組成的集合。圖5-1描述了包、模組、類和函數之間的關係。
圖5-1 包、模組、類和函數之間的關係
包就是一個完成特定任務的工具箱,Python提供了許多有用的工具包,如字串處理、圖形使用者介面、Web應用、圖形影像處理等。使用自帶的這些工具包,可以提高程式師的開發效率,減少程式設計的複雜度,達到代碼重用的效果。這些自帶的工具包和模組安裝在Python的安裝目錄下的Lib子目錄中。
例如,Lib目錄中的xml資料夾。xml資料夾就是一個包,這個包用於完成XML的應用開發。xml包中有幾個子包:dom、sax、etree和parsers。文件__init__.py是xml包的註冊檔,如果沒有該檔,Python將不能識別xml包。在系統字典表中定義了xml包。
注意 包必須至少含有一個__init__.py文件。__init__.py檔的內容可以為空,它用於標識當前資料夾是一個包。
5.2 模組
模組是Python中重要的概念,Python的程式是由一個個模組組成的。前面已經接觸到了模組,一個Python檔就是一個模組。下面將介紹模組的概念和特性。
5.2.1 模組的創建
模組把一組相關的函數或代碼組織到一個檔中。一個檔即是一個模組。模組由代碼、函數或類組成。創建一個名為myModule.py的檔,即定義了一個名為myModule的模組。在myModule模組中定義一個函數func()和一個類MyClass。MyClass類中定義一個方法myFunc()。
01
# 自訂模組
02
def func():
03 print ("MyModule.func()")
04
05
class MyClass:
06 def myFunc(self):
07 print ("MyModule.MyClass.myFunc()")
然後在myModule.py所在的目錄下創建一個call_myModule.py的文件。在該檔中調用myModule模組的函數和類。
01
#調用自訂模組的類和函數
02
import myModule # 導入moudle
03
04
myModule.func()
05
myClass = myModule.MyClass()
06
myClass.myFunc()
【代碼說明】
·第2行代碼導入模組myModule。
·第4行調用模組的函數。調用時需要加首碼myModule,否則Python不知道func()所在的命名空間。輸出結果:
myModule.func()
·第5行代碼創建類MyClass的實例myClass。這裡也需要使用首碼myModule調用類。
·第6行調用類的方法myFunc()。輸出結果:
myModule.MyClass.myFunc()
注意 myModule.py和call_myModule.py必須放在同一個目錄下,或放在sys.path所列出的目錄下;否則,Python解譯器找不到自訂的模組。
當Python導入一個模組時,Python首先查找當前路徑,然後查找lib目錄、site-packages目錄(Python\Lib\site-packages)和環境變數PYTHONPATH設置的目錄。如果導入的模組沒有找到,在以上路徑搜索一下是否含有這個模組。可以通過sys.path語句搜索模組的查找路徑。
5.2.2 模組的導入
在使用一個模組的函數或類之前,首先要導入該模組。前面已經多次使用模組的導入,模組的導入使用import語句。模組導入語句的格式如下所示。
import module_name
這條語句可以直接導入一個模組。調用模組的函數或類時,需要以模組名作為首碼,其格式如下所示。
module_name.func()
如果不想在程式中使用首碼符,可以使用from…import…語句導入。from…import…語句的格式如下所示。
from module_name import function_name
2.2.3小節比較了import語句和from…import…語句的不同。導入模組下所有的類和函數,可以使用如下格式的import語句。
from module_name import *
此外,同一個模組檔支援多條import語句。例如,定義一個名為myModule的模組。該模組定義一個全域變數count和一個函數func()。每次調用函數func(),使變數count的值加1。
01
count = 1
02
03
def func():
04 global count
05 count = count + 1
06 return count
多次導入myModule模組,查看變數count的結果。
01
import myModule
02
print("count =", myModule.func())
03
myModule.count = 10
04
print ("count =", myModule.count)
05
06
import myModule
07
print ("count =", myModule.func())
【代碼說明】
·第1行代碼導入模組myModule。
·第2行代碼調用模組中的函數func()。此時變數count的值等於2。輸出結果:count=2。
·第3行代碼給模組myModule中的變數count賦值,此時變數count的值等於10。
·第4行代碼獲取變數count的值。輸出結果:count=10。
·第6行代碼再次導入模組myModule,變數count的初始值為10。
·第7行代碼調用func(),變數count的值加1。輸出結果:count=11。
Python中的import語句比Java的import語句更靈活。Python的import語句可以置於程式中任意的位置,甚至可以放在條件陳述式中。在上面的程式碼片段後添加如下語句:
01
# import置於條件陳述式中
02
if myModule.count > 1:
03 myModule.count = 1
04
else:
05 import myModule
06
print ("count =", myModule.count)
【代碼說明】
·第2行代碼判斷myModule.count的值是否大於1。
·第3行代碼,如果count的值大於1,則把變數count的值置為1。由於前面程式碼片段中變數count的值為11,所以變數count的值被賦值為1。
·第5行代碼,如果count的值小於等於1,則導入import語句。
·第6行代碼輸出變數count的值。輸出結果:count=1
5.2.3 模組的屬性
模組有一些內置屬性,用於完成特定的任務,如__name__、__doc__。每個模組都有一個名稱,例如,__name__用於判斷當前模組是否是程式的入口,如果當前程式正在被使用,__name__的值為“__main__”。通常給每個模組都添加一個條件陳述式,用於單獨測試該模組的功能。例如,創建一個模組myModule。
01
if __name__ == '__main__':
02 print ('myModule作為主程式運行')
03
else:
04 print ('myModule被另一個模組調用')
【代碼說明】 第1行代碼判斷本模組是否作為主程式運行。單獨運行模組myModule,輸出結果如下所示。
myModule作為主程式運行
創建另一個模組call_myModule。這個模組很簡單,只要導入模組myModule即可。
01
import myModule
02
print (__doc__)
【代碼說明】 運行模組call_myModule,輸出結果:
myModule被另一個模組調用
第2行代碼調用了模組另一個屬性__doc__。由於該模組沒有定義文檔字串,所以輸出結果為None。輸出結果:None
5.2.4 模組的內置函數
Python提供了一個內聯模組buildin。內聯模組定義了一些開發中經常使用的函數,利用這些函數可以實現資料類型的轉換、資料的計算、序列的處理等功能。下面將介紹內聯模組中常用的函數。
1.apply()
Python3中移除了apply函數,所以不再可用了。調用可變參數清單的函數的功能只能使用在列表前添加*來實現。
2.filter()
filter()可以對某個序列做過濾處理,判斷自訂函數的參數返回的結果是否為真來過濾,並一次性返回處理結果。filter()的聲明如下所示。
class filter(object)
filter(function or None, iterable) --> filter object
下面這段代碼演示了filter()過濾序列的功能。從給定的清單中過濾出大於0的數字。
01
def func(x):
02 if x > 0:
03 return x
04
05
print (filter(func, range(-9, 10))) # 調用filter函數,返回的是filter物件
06
print(list(filter(func, range(-9, 10))) # 將filter對象轉換為清單
【代碼說明】 第5行代碼,使用range()生成待處理的列表,然後把該列表的值依次傳入func()。func返回結果給filter(),最後將結果yield成一個iterable物件返回,可以進行遍歷。輸出結果如下。
<filter object at 0x1022b2750>
直接列印出的是filter物件,無法看出其內容。第6行將其轉換為列表。
注意 filter()中的過濾函數func()的參數不能為空。否則,沒有可以存儲sequence元素的變數,func()也不能處理過濾。
3.reduce()
對序列中元素的連續操作可以通過迴圈來處理。例如,對某個序列中的元素累加操作。Python提供的reduce()也可以實現連續處理的功能。在Python2中reduce()存在於全域空間中,可以直接調用。而在Python3中將其移到了functools模組中,所以使用之前需要先引入。reduce()的聲明如下所示。
reduce(func, sequence[, initial]) ->
value
【代碼說明】
·參數func是自訂的函數,在函數func()中實現對參數sequence的連續操作。
·參數sequence待處理的序列。
·參數initial可以省略,如果initial不為空,則initial的值將首先傳入func()進行計算。如果sequence為空,則對initial的值進行處理。
·reduce()的返回值是func()計算後的結果。
下面這段代碼實現了對一個清單的數字進行累加的操作。
01
def sum(x, y):
02 return x + y
03
form functools import reduce # 引入reduce
04
print (reduce(sum, range(0, 10)))
05
print (reduce(sum, range(0, 10), 10))
06
print (reduce(sum, range(0, 0), 10))
【代碼說明】
·第1行代碼,定義了一個sum()函數,該函數提供兩個參數,執行累加操作。
·第4行代碼,對0+1+2+3+4+5+6+7+8+9執行累加計算。輸出結果為45。
·第5行代碼,對10+0+1+2+3+4+5+6+7+8+9執行累加計算。輸出結果為55。
·第6行代碼,由於range(0,0)返回空列表,所以返回結果就是10。輸出結果為10。
reduce()還可以對數位進行乘法、階乘等複雜的累計計算。
注意 如果用reduce()進行累計計算,必須在sum中定義兩個參數,分別對應加法運算子兩側的運算元。
4.map()
第4章使用了map()對tuple元組進行“解包”操作,調用時設置map()的第一個參數為None。map()的功能非常強大,可以對多個序列的每個元素都執行相同的操作,並返回一個map物件。map()的聲明如下所示。
class map(object)
map(func, *iterables) --> map object
【代碼說明】
·參數func是自訂的函數,實現對序列每個元素的操作。
·參數iterables是待處理的序列,參數iterables的個數可以是多個。
·map()的返回值是對序列元素處理後的清單。
下面這段代碼實現了清單中數位的冪運算。
01
def power(x): return x ** x
02
print (map(power, range(1, 5))) # 列印map對象
03
print(list(map(power,range(1,5)))) # 轉換為列表輸出
04
def power2(x, y): return x ** y
05
print (map(power2, range(1, 5), range(5, 1, -1))) # 列印map對象
06
print(list(map(power2, range(1, 5), range(5, 1, -1)))) # 轉換為列表輸出
【代碼說明】
·第1行代碼定義了一個power()函數,實現了數位的冪運算。
·第2行代碼把數字1、2、3、4依次傳入函數power中,將計算結果yield成一個iterable物件,輸出結果:
<map object at 0x1022b2750>
·第3行代碼將map物件轉換為清單然後列印出來,輸出結果:
[1, 4, 27, 256]
·第4行代碼,定義了一個power2()函數,計算x的y次冪。
·第5行代碼,提供了兩個列表參數。依次計算1^5、2^4、3^3、4^2,計算後的結果同樣yield成一個iterable物件。輸出結果:
<map object at 0x1022b2750>
·第6行代碼將map對象轉換成為清單輸出。輸出結果:
[1, 16, 27, 16]
注意 如果map()中提供多個序列,則每個序列中的元素一一對應進行計算。如果每個序列的長度不相同,則短的序列後補充None,再進行計算。
常用內置函數一覽表如表5-1所示。
表5-1 內置模組的函數

5.2.5 自訂包
包就是一個至少包含__init__.py文件的資料夾。Python包和Java包的作用是相同的,都是為了實現程式的重用,把實現一個常用功能的代碼組合到一個包中,調用包提供的服務從而實現重用。例如,定義一個包parent。在parent包中創建兩個子包pack和pack2。pack包中定義一個模組myModule,pack2包中定義一個模組myModule2。最後在包parent中定義一個模組main,調用子包pack和pack2,如圖5-2所示。

包pack的__init__.py程式如下所示。
01
if __name__ == '__main__':
02 print ('作為主程式運行')
03
else:
04 print ('pack初始化')
這段代碼初始化pack包,這裡直接輸出一段字串。當pack包被其他模組調用時,將輸出“pack初始化”。包pack的myModule模組如下所示。
01
def func():
02 print ("pack.myModule.func()")
03
04
if __name__ == '__main__':
05 print ('myModule作為主程式運行')
06
else:
07 print ('myModule被另一個模組調用')
當pack2包被其他模組調用時,將首先執行__init__.py文件。pack2包的__init__.py程式如下。
01
if __name__ == '__main__':
02 print ('作為主程式運行')
03
else:
04 print ('pack2初始化')
包pack2的myModule2模組如下。
01
def func2():
02 print
("pack2.myModule2.func()")
03
04
if __name__ == '__main__':
05 print ('myModule2作為主程式運行')
06
else:
07 print ('myModule2被另一個模組調用')
下面的main模組調用了pack、pack2包中的函數。
01
from pack import myModule
02
from pack2 import myModule2
03
04
myModule.func()
05
myModule2.func2()
【代碼說明】
·第1行代碼從pack包中導入myModule模組,myModule模組被main模組調用,因此輸出字串“myModule被另一個模組調用”。輸出結果如下。
pack初始化
myModule被另一個模組調用
·第2行代碼從pack2包中導入myModule2模組。輸出結果如下。
pack2初始化
myModule2被另一個模組調用
·第4行代碼調用myModule模組的函數func()。輸出結果如下。
pack.myModule.func()
·第5行代碼調用myModule2模組的函數func2()。輸出結果如下。
pack2.myModule2.func()
__init__.py也可以用於提供當前包的模組清單。例如,在pack包的__init__.py檔前面添加一行代碼。
__all__ = ["myModule"]
__all__用於記錄當前pack包所包含的模組。其中方括弧中的內容是模組名的清單,如果模組數量超過2兩個,使用逗號分開。同理,在pack2包也添加一行類似的代碼。
__all__ = ["myModule2"]
這樣就可以在main模組中一次導入pack、pack2包中所有的模組。修改後的main模組如下。
01
from pack import *
02
from pack2 import *
03
04
myModule.func()
05
myModule2.func2()
【代碼說明】
·第1行代碼,首先執行pack包的__init__.py檔,然後在__all__屬性中查找pack包含有的模組。如果pack包的__init__.py檔不使用__all__屬性記錄模組名,main模組調用時將不能識別myModule模組。Python將提示如下錯誤。
NameError: name 'myModule' is not
defined
·第2行代碼與第1行代碼的作用相同。
5.3 函數
函數就是一段可以重複多次調用的代碼,通過輸入的參數值,返回需要的結果。前面的例子已經多次使用了Python的內置函數,而且還自訂了一些函數。Python的函數有許多新的特性,下面將一一介紹。
5.3.1 函數的定義
函數的定義非常簡單,使用關鍵字def定義。函數在使用前必須定義,函數的類型即返回值的類型。Python函式定義的格式如下所示。
01
def 函數名(參數1,參數2…):
02 …
03
return 運算式
函數名可以是字母、數位或底線組成的字串,但是不能以數位開頭。函數的參數放在一對圓括號中,參數的個數可以有一個或多個,參數之間用逗號隔開,這種參數稱為形式參數。括弧後面以冒號結束,冒號下面就是函數的主體。
3.2.4小節使用了字典實現switch語句,現在把這段代碼封裝到函數中。它涉及3個參數:兩個運算元、一個運算子。修改後的代碼如下所示。
01
# 函數的定義
02
from __future__ import division
03
def arithmetic(x, y, operator):
04 result = {
05 "+" : x + y,
06 "-" : x - y,
07
"*" : x * y,
08 "/" : x / y
09 }
【代碼說明】
·第3行代碼定義函數arichmetic(),x、y是四則運算的兩個運算元,operator是運算子。這3個參數的值是從實際參數傳遞過來的。
·第4行到第9行代碼是函數的主體,實現了運算元的運算。
arichmetic()已經創建成功,剩下的就是函數的調用的問題了。函式呼叫的格式如下所示。
函數名(實參1,實參2…)
函數的調用採用函數名加一對圓括號的方式,圓括號內的參數是傳遞給函數的具體值。圖5-3說明了實際參數和形式參數的對應關係。

arichmetic()的調用如下所示。
01
# 函數的調用
02
print (arithmetic(1, 2, "+"))
【代碼說明】 arichmetic()寫在print語句的後面,直接輸出函數的返回值。輸出結果為“3”。
注意 實際參數必須與形式參數一一對應,否則將出現錯誤計算。具有預設值的參數例外。
5.3.2 函數的參數
在C、C++中,參數的傳遞有值傳遞和引用傳遞兩種方式。而Python中任何東西都是物件,所以參數只支援引用傳遞的方式。Python通過名稱綁定的機制,把實際參數的值和形式參數的名稱綁定在一起。即把形式參數傳遞到函數所在的局部命名空間中,形式參數和實際參數指向記憶體中同一個存儲空間。
函數的參數支援預設值。當某個參數沒有傳遞實際的值時,函數將使用預設參數計算。例如可以給arichmetic()的參數都提供一個預設值。
01
# 函數的預設參數
02
def arithmetic(x=1, y=1, operator="+"):
03 result = {
04 "+" : x + y,
05 "-" : x - y,
06
"*" : x * y,
07 "/" : x / y
08 }
09 return result.get(operator) # 返回計算結果
10
11
print (arithmetic(1, 2))
12
print (arithmetic(1, 2, "-"))
13
print (arithmetic(y=3, operator="-"))
14
print (arithmetic(x=4, operator="-"))
15
print (arithmetic(y=3, x=4, operator="-"))
【代碼說明】
·第2行代碼使用賦值運算式的方式定義參數的預設值。
·第11行代碼,參數x、y的值分別賦值為1、2,參數operator使用預設值“+”。輸出結果為“3”。
·第12行代碼,提供了3個實際參數,這3個值將分別覆蓋形式參數的預設值。輸出結果為“-1”。
·第13行代碼,指定參數y、operator的值。輸出結果為“-2”。這裡必須使用賦值運算式的方式傳遞參數;否則,Python解譯器將誤認為x=3、y="-"。因此下面的寫法是錯誤的。
print (arithmetic(3, "-"))
·第14行代碼,指定參數x、operator的值。輸出結果為“3”。
·第15行代碼,使用賦值運算式傳遞參數,可以顛倒參數清單的順序。輸出結果為“1”。
參數可以是變數,也可以是元組、清單等內置資料結構。
01
# 列表作為參數傳遞
02
def arithmetic(args=[], operator="+"):
03 x = args[0]
04 y = args[1]
05 result = {
06 "+" : x + y,
07 "-" : x - y,
08 "*" : x * y,
09 "/" : x / y
10 }
11
12
print (arithmetic([1, 2]))
【代碼說明】
·第2行代碼把參數x、y合併為一個參數,通過args列表傳遞x、y的值。
·第3、4行代碼,從列表中取出參數值分別賦值給變數x、y。
·第12行代碼,把列表[1,2]傳遞給arichmetic()。輸出結果為“3”。
由於參數實現了名稱綁定的機制,在使用默認參數時,可能會出現預期之外的結果。
01
def append(args=[]):
02 args.append(0)
03 print (args)
04
05
append()
06
append([1])
07
append()
【代碼說明】
·第1行代碼定義了一個append()函數,參數是一個預設的列表。
·第2行代碼在清單中追加一個元素0。
·第5行代碼調用append(),使用默認的列表。輸出結果為“[0]”。
·第6行代碼,傳遞了一個列表[1],append()中追加一個元素0。輸出結果為“[1,0]”。
·第7行代碼再次調用append(),此時使用的列表還是第一次調用的args,因此args在原有的基礎上將再次追加一個元素0。輸出結果為“[0,0]”。
為了避免這個問題,可以在append()中添加一個條件判斷語句。如果清單args中沒有任何元素,則先把args列表置空,然後再添加元素。
01
def append(args=[]):
02 if len(args) <= 0:
03 args = []
04 args.append(0)
05 print (args)
06
07
append()
08
append([1])
09
append()
【代碼說明】
·第2行代碼使用len()判斷列表args的長度是否大於0。如果小於等於0,則把args置為空清單,即取消了函數參數的綁定。
·第4行代碼在清單中追加一個元素0。
·第7行代碼調用append(),使用默認的列表。輸出結果為“[0]”。
·第8行代碼,傳遞了一個列表[1],append()中追加一個元素0。輸出結果為“[1,0]”。
·第9行代碼調用append(),通過len(args)的判斷,取消了參數的名字綁定。輸出結果為“[0]”。
在開發中,常常需要傳遞可變長度的參數。在函數的參數前使用識別字“*”可以實現這個要求。“*”可以引用元組,把多個參數組合到一個元組中。
01
# 傳遞可變參數
02
def func(*args):
03 print args
04
func(1, 2, 3)
【代碼說明】
·第2行代碼,在參數args前使用識別字“*”。
·第3行代碼輸出參數的值,由於參數使用了“*args”的形式,因此傳入的實際參數被“打包”到一個元組中,輸出結果為“(1,2,3)”。
·第4行代碼調用函數func()。其中的參數“1”、“2”、“3”成為args元組的元素。
Python還提供另一個識別字“**”。在形式參數前面添加“**”,可以引用一個字典,根據實際參數的賦值運算式生成字典。例如,下面這段代碼實現了在一個字典中匹配元組的元素。定義函數時,設計兩個參數:一個是待匹配的元組,表示為“*t”;另一個是字典,表示為“*d”。函式呼叫時,實際參數分成兩部分:一部分參數是若干個數位或字串,另一部分參數是賦值運算式,如圖5-4所示。

01
# 傳遞可變參數
02
def search(*t, **d):
03 keys = d.keys()
04 values = d.values()
05 print(keys)
06 print (values)
07 for arg in t:
08
for key in keys:
09 if arg == key:
10 print
("find:",d[key])
11
12
search("one", "three",
one="1",two="2",three="3")
【代碼說明】
·第2行代碼中的“*t”與第12行代碼中的“one”、“three”對應。“one”、“three”組成一個元組t。“**d”與“one="1",two="2",three="3"”對應,生成一個字典{one:"1",two:"2",three:"3"}。
·第5行代碼輸出結果:
['three', 'two', 'one']
·第6行代碼輸出結果:
['3', '2', '1']
·第7行到第10行代碼在字典d中查找元組t中的值。如果找到,則輸出。輸出結果如下所示。
find: 1
find: 3
注意 “*”必須寫在“**”的前面,這是語法規定。
5.3.3 函數的返回值
函數的返回使用return語句,return後面可以是變數或運算式。下面完善一下arithmetic(),添加return語句。代碼如下:
01
from __future__ import division
02
def arithmetic(x, y, operator):
03 result = {
04 "+" : x + y,
05 "-" : x - y,
06 "*" : x * y,
07 "/" : x / y
08 }
09 return result.get(operator) # 返回計算結果
【代碼說明】 第9行代碼調用字典的get(),獲得對應的運算式,並把計算後的結果返回。
對於C、Java,如果函數主體沒有使用return語句返回,而在設定陳述式中調用函數,程式編譯後會出現錯誤。Python沒有這個語法限制,即使函數沒有返回值,依然可以獲得返回值。例如:
01
# 沒有return語句的函數返回None
02
def func():
03 pass
04
05
print (func())
【代碼說明】
·第2行代碼定義了一個函數func(),函數的主體沒有任何實現代碼,pass關鍵字相當於一個預留位置。
·第5行代碼輸出func()的返回值,因為沒有return語句,所以返回值為None。輸出結果為“None”。
None是Python中的物件,不屬於數位也不屬於字串。當函數中的return語句不帶任何參數時,返回的結果也是None。
01
def func():
02 return
03
04
print (func())
如果需要返回多個值,可以把這些值“打包”到元組中。在調用時,對返回的元組“解包”即可。下面這段代碼實現了輸入變數的反轉。例如,輸入0、1、2,返回2、1、0。
01
# return返回多個值
02
def func(x, y, z):
03 l = [x, y, z]
04 l.reverse()
05 numbers = tuple(l)
06 return numbers
07
08
x, y, z = func(0, 1, 2)
09
print (x, y, z)
【代碼說明】
·第2行代碼定義了一個函數func(),該函數對傳入的3個參數反轉後,返回這3個值。
·第3行代碼把3個參數“打包”到一個列表中。
·第4行代碼反轉列表。
·第5行代碼把列表裝到一個元組中。
·第6行代碼返回元組,即返回了3個數字。
·第8行代碼調用func(),獲得返回的元組,並“解包”到3個變數中。
·第9行代碼輸出3個變數的值。
稍微改進一下代碼,還可以得到第二種解決辦法。
01
def func(x, y, z):
02 l = [x, y, z]
03 l.reverse()
04 a, b, c = tuple(l)
05 return a, b, c
06
07 x, y, z = func(0, 1, 2)
08
print (x, y, z)
【代碼說明】
·第4行代碼“解包”元組,把反轉後的值分別賦值給變數a、b、c。
·第5行代碼,return後可以跟逗號間隔的運算式,返回多個值。
·第7行代碼調用func(),把a、b、c分別賦值給x、y、z。
函數中可以使用多個return語句。例如,if…else…語句的各個分支中,返回不同的結果。
01
# 多個return語句
02
def func(x):
03 if x > 0:
04 return "x > 0"
05 elif x == 0:
06 return "x == 0"
07 else:
08 return "x < 0"
09
10
print (func(-2))
【代碼說明】 當傳入的參數大於0時,返回“x>0”;當傳入的參數等於0時,返回“x==0”;當傳入的參數小於0,返回“x<0”。
注意 多個return語句是不推薦的寫法,過多的return語句往往造成程式複雜化,這時就需要對代碼進行重構了。
如果程式中有多個return語句,可以通過增加一個變數的方法,減少return語句。
01
# 多個return語句的重構
02
def func(x):
03 if x > 0:
04
result = "x >
0"
05 elif x == 0:
06 result = "x == 0"
07 else:
08 result = "x < 0"
09 return result
10
11
print (func(-2))
【代碼說明】
·第4、6、8行代碼,增加了一個變數result,通過設定陳述式來記錄程式分支的狀況。
·第9行代碼返回result的值,這樣就使得每個分支的結果都可以調用同一個return語句返回。
5.3.4 函數的嵌套
函數的嵌套是指在函數的內部調用其他函數。C、C++只允許在函數體內部嵌套,而Python不僅支援函數體內嵌套,還支持函式定義的嵌套。例如,計算運算式(x+y)*(m-n)的值。可以把計算步驟分為3步,先計算運算式x+y,然後計算運算式m-n,最後計算前面兩步結果的乘積。因此,可以設計3個函數。第一個函數sum()計算x+y的值,第二個函數sub()計算m+n的值,第三個函數計算前面兩者的乘積,如圖5-5所示。
下面這段代碼演示了函數之間的調用操作。
01
# 嵌套函數
02
def sum(a, b):
03 return a + b
04
def sub(a, b):
05 return a - b
06
def func():
07 x = 1
08 y = 2
09 m = 3
10 n = 4
11 return sum(x, y) * sub(m, n)
12
13
print (func())
【代碼說明】
·第2行代碼定義了函數sum(),sum()帶有兩個參數a、b。參數a、b用於計算運算式x+y的值。
·第3行代碼計算a+b的值,即返回x+y的結果。
·第4行代碼定義了函數sub(),sub()帶有兩個參數a、b。
·第5行代碼計算a–b的值,即返回m–n的結果。
·第11行代碼,在return語句中調用sum()、sub(),並執行乘法運算。
·第13行代碼,調用函數func()。輸出結果如下所示。
-3
注意 函數嵌套的層數不宜過多。否則,容易造成代碼的可讀性差、不易維護等問題。一般函數的嵌套調用應控制在3層以內。
上面這段代碼也可以換一種形式實現,即把函數sum()、sub()放到func()的內部,如圖5-6所示。
下面這段代碼實現了在func()內部定義sum()、sub()。
01
# 嵌套函數
02
def func():
03 x = 1
04 y = 2
05
m= 3
06 n = 4
07 def sum(a, b): # 內建函式
08 return a + b
09 def sub(a, b): # 內建函式
10 return a - b
11 return sum(x, y) * sub(m, n)
12
13
print (func())
【代碼說明】
·第7行代碼,在func()內部定義了sum()。
·第9行代碼,在func()內部定義了sub()。
·第11行代碼,調用sum()和sub(),然後執行乘法運算。輸出結果為“-3”。
內建函式sum()、sub()也可以直接調用外部函數func()定義的變數,如圖5-7所示。



下面這段代碼實現了內建函式sum()、sub()引用外部函數func()的變數。
01
# 嵌套函數,直接使用外層函數的變數
02
def func():
03 x = 1
04 y = 2
05 m= 3
06 n = 4
07 def sum(): # 內建函式
08 return x + y
09 def sub(): # 內建函式
10 return m - n
11 return sum() * sub()
12
13
print (func())
【代碼說明】
·第7行代碼,函數sum()沒有任何參數。
·第8行代碼,在sum()內部調用外部變數x、y。
·第9行代碼,函數sub()也沒有任何參數。
·第10行代碼,在sub()內部調用外部變數m、n。
·第11行代碼,計算sum()*sub()的值。輸出結果為“-3”
注意 儘量不要在函數內部定義函數。這種方式不便於程式的維護,容易造成邏輯上的混亂。而且嵌套定義函數的層次越多,程式維護的代價就越大。
5.3.5 遞迴函數
遞迴函數可以在函數主體內直接或間接地調用自己,即函數的嵌套是函數本身。遞迴是一種程式設計方法,使用遞迴可以減少重複的代碼,使程式變得簡潔。遞迴的過程分為兩個階段——遞推和回歸。遞迴函數的原理如下。
第一階段,遞迴函數在內部調用自己。每一次函式呼叫又重新開始執行此函數的代碼,直到某一級遞迴程式結束。
第二階段,遞迴函數從後往前返回。遞迴函數從最後一級開始返回,一直返回到第一次調用的函數體內。即遞迴逐級調用完畢後,再按照相反的順序逐級返回。
注意 遞迴函數需要編寫遞迴結束的條件;否則,遞迴程式將無法結束。一般通過判斷語句來結束程式。
計算階乘是一個經典的遞迴實現。首先,回顧一下階乘的計算公式。

例如,計算5!的結果。在設計程式時,可以根據n是否等於1進行判斷。每次遞迴呼叫,傳入參數n-1。直到n=1時,返回1!等於1。再依次返回2!、3!、4!、5!的計算結果。圖5-8演示了階乘的計算過程。

下面這段代碼用遞迴實現了階乘的計算過程。
01
# 計算階乘
02
def refunc(n):
03 i = 1
04 if n > 1: # 遞迴的結束判斷
05 i = n
06 n = n * refunc(n-1) # 遞推
07 print ("%d! =" %i, n)
08 return n # 回歸
09
10
refunc(5)
【代碼說明】
·第2行代碼定義了遞迴函數,遞迴函數的定義和普通函數沒有什麼區別。
·第3行代碼定義了一個變數i,用於print語句的輸出。
·第4行代碼,對傳遞的參數n進行判斷。如果n大於1,則函數可以繼續遞推下去;否則,就返回當前計算的結果。
·第5行代碼把n的值賦值給i,用i來記錄當前遞推的數位。
·第6行代碼調用函數refunc()自身,傳遞參數n-1。
·第7行代碼輸出階乘的計算結果。
·第8行代碼返回每一級階乘的計算結果。
·第10行代碼調用遞迴函數refunc。每次遞迴的輸出結果如下所示。
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
注意 每次調用遞迴函數都會複製函數中所有的變數,再執行遞迴函數。程式需要較多的存儲空間,對程式的性能會有一定的影響。因此,對於沒有必要進行遞迴的程式,最好用其他的方法進行改進。
可以使用前面提到的reduce()快速實現階乘的運算。
01
# 使用reduce計算階乘
02
From functools import reduce
# Python3中reduce不再全域中,必須手動引入
03
print ("5! =", reduce(lambda x, y: x * y, range(1, 6)))
使用recude()只需要一行代碼就可以計算出5!。
5.3.6 lambda函數
lambda函數用於創建一個匿名函數,函數名未和識別字進行綁定。使用lambda函數可以返回一些簡單的運算結果。lambda函數的格式如下所示。
lambda 變數1,變數2… : 運算式
其中,變數清單用於運算式的計算。lambda屬於函數,因此變數清單後需要一個冒號。通常把lambda賦值給一個變數,變數就可作為函數使用。例如:
01
# 賦值
02
func = lambda變數1,變數2… : 運算式
03
# 調用
04
func()
這樣就把lambda和變數func綁定在一起了,變數func的名稱就是函數名。lambda函數可以消除內建函式。例如,可以把5.3.4小節中計算(x+y)*(m–n)的程式進行一番改造,把其中的函數sum()、sub()用lambda函數來替代。
01
# lambda
02
def func():
03 x = 1
04 y = 2
05 m= 3
06 n = 4
07 sum = lambda x, y : x + y
08 print (sum)
09 sub = lambda m, n : m - n
10 print (sub)
11 return sum(x, y) * sub(m, n)
12
13
print (func())
【代碼說明】
·第7行代碼定義了lambda函數,實現計算運算式x+y,並把lambda函數賦值給變數sum。
·第8行代碼輸出變數sum的值,sum保存了lambda函數的位址。輸出結果如下所示。
<function <lambda> at
0x00B4D3B0>
·第9、10行代碼的作用與第7、8行原理相同。
·第11行代碼計算sum()與sub()的乘積。輸出結果為“-3”。
注意 lambda也稱為運算式。lambda中只能使用運算式,不能使用判斷、迴圈等多重語句。
前面這個例子把lambda賦值給一個變數使用,也可以把lambda直接作為函數使用。
01
# lambda的函數用法
02
print ((lambda x: -x)(-2))
【代碼說明】 第2行代碼定義了匿名函數lambda
x:-x,用於返回數位的絕對值。函數的參數為-2,輸出結果為“2”。
5.3.7 Generator函數
生成器(Generator)的作用是一次產生一個資料項目,並把資料項目輸出。Generator函數可以用在for迴圈中遍歷。Generator函數每次返回一個資料項目的特性,使得反覆運算器的性能更佳。Generator函數的定義如下所示。
01
def 函數名(參數清單):
02 …
03 yield 運算式
Generator函數的定義和普通函數的定義沒有什麼區別,只要在函數體內使用yield生成資料項目即可。Generator函數可以被for迴圈遍歷,而且可以通過next()方法獲得yield生成的資料項目。下面這段代碼演示了Generator函數的使用。
01
# 定義Generator函數
02
def func(n):
03 for i in range(n):
04 yield i
05
# 在for迴圈中輸出
06
for i in func(3):
07 print (i)
08
# 使用next()輸出
09
r = func(3)
10
print (r.next())
11
print (r.next())
12
print (r.next())
13
print (r.next())
【代碼說明】
·第2行代碼定義了Generator函數,參數n表示一個數位,該函數依次生成0到n個數字。
·第4行代碼調用yield生成數字。
·第6行代碼遍歷函數func(),依次輸出yield生成的數位。輸出結果:
0
1
2
·第9行代碼把func()賦值給變數r。
·第10行到第12行代碼,輸出r.next()的值。輸出結果:
0
1
2
·第13行代碼再次調用r.next()。由於n的值等於3,生成3個數位後,已經沒有資料可生成。Python解譯器拋出異常StopIteration。
yield關鍵字與return關鍵字的返回值和執行原理都不相同。yield生成值並不會中止程式的執行,返回值後程式繼續往後執行。return返回值後,程式將中止執行。下面這段代碼演示了yield和return的區別。
01
# yield與return區別
02
def func(n):
03 for i in range(n):
04 return i
05
def func2(n):
06 for i in range(n):
07 yield i
08
09
print (func(3))
10
f = func2(3)
11
print (f)
12
print (f.next())
13
print (f.next())
【代碼說明】
·第4行代碼直接返回i的值,迴圈語句將被中止,整個程式到此結束。
·第7行代碼迴圈生成n個數字,迴圈語句不會被中止。
·第9行代碼的輸出結果為“0”。
·第11行代碼,yield並沒有返回任何值,而是返回了函數func2()的位址。輸出結果:
<generator object at 0x00B4C5D0>
·第12行代碼,調用f.next()才能獲得返回值。輸出結果為“0”。
·第13行代碼的輸出結果為“1”。
Generator函數可以返回元素的值,而序列也可以獲取元素的值。但是兩者還是存在很大的區別。Generator函數一次只返回一個資料項目,佔用更少的記憶體。每次生成資料都要記錄當前的狀態,便於下一次生成資料。資料的訪問是通過next()方法實現的。當訪問越界時,Generator函數會拋出異常StopIteration。序列一次返回所有的資料,元素的訪問是通過索引完成的。當訪問越界時,序列提示list
index out of range錯誤。
當程式需要較高的性能或一次只需要一個值進行處理時,使用Generator函數。當需要一次獲取一組元素的值時,使用序列。
5.4 小結
本章介紹了包、模組和函數之間的邏輯關係、Python程式的組成結構、模組和函數的使用。重點講解了模組的內置函數、常用模組的使用、函數的特性等知識。Python的參數傳遞使用引用傳遞的方式,並且支援返回多個值。遞迴函數是經常使用到的函式呼叫方法,Python中還可以使用Generator函數替代改造遞迴函數,提高遞迴的性能。lambda函數是一個非常實用的工具,它可以定義一些小函數,用於運算式或函數返回值中非常方便。
實際應用中,許多工都是由若干個函數組成的。這些函數把任務細化,實現明確的功能,最後把這些函數組合起來形成一個完整的模組。Python的標準庫提供了很多實用的模組和包,下一章將用到字串模組和規則運算式模組,講解Python中字串的特性、規則運算式的概念,以及這些模組的使用。
5.5 習題
1.import和from都可以用來引入模組,在什麼情況下沒法使用from?
2.使用from比使用import有哪些優點?又有哪些缺點?
3.下面的代碼片段的輸出結果是什麼?
01 s = lambda
x,y : x+y 02 print (s(‘aa’,’bb’))
4.寫一個根據日期計算是星期幾的模組,在程式中引入並使用這個模組。


0 留言:
發佈留言