2020年10月9日星期五

005 零基礎學的Python 第一篇 Python語言基礎 第5章 模組與函數

 申明本站飛宇網 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包中有幾個子包:domsaxetreeparsers。文件__init__.pyxml包的註冊檔,如果沒有該檔,Python將不能識別xml包。在系統字典表中定義了xml包。

注意  包必須至少含有一個__init__.py文件。__init__.py檔的內容可以為空,它用於標識當前資料夾是一個包。

 

5.2 模組

模組是Python中重要的概念,Python的程式是由一個個模組組成的。前面已經接觸到了模組,一個Python檔就是一個模組。下面將介紹模組的概念和特性。

5.2.1 模組的創建

模組把一組相關的函數或代碼組織到一個檔中。一個檔即是一個模組。模組由代碼、函數或類組成。創建一個名為myModule.py的檔,即定義了一個名為myModule的模組。在myModule模組中定義一個函數func()和一個類MyClassMyClass類中定義一個方法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.pycall_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()


如果不想在程式中使用首碼符,可以使用fromimport語句導入。fromimport語句的格式如下所示。


from module_name import function_name


2.2.3小節比較了import語句和fromimport語句的不同。導入模組下所有的類和函數,可以使用如下格式的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語句比Javaimport語句更靈活。Pythonimport語句可以置於程式中任意的位置,甚至可以放在條件陳述式中。在上面的程式碼片段後添加如下語句:


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()也可以實現連續處理的功能。在Python2reduce()存在於全域空間中,可以直接調用。而在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()的第一個參數為Nonemap()的功能非常強大,可以對多個序列的每個元素都執行相同的操作,並返回一個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行代碼把數字1234依次傳入函數power中,將計算結果yield成一個iterable物件,輸出結果:


<map object at 0x1022b2750>


·第3行代碼將map物件轉換為清單然後列印出來,輸出結果:


[1, 4, 27, 256]


·第4行代碼,定義了一個power2()函數,計算xy次冪。

·第5行代碼,提供了兩個列表參數。依次計算1^52^43^34^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包中創建兩個子包packpack2pack包中定義一個模組myModulepack2包中定義一個模組myModule2。最後在包parent中定義一個模組main,調用子包packpack2,如圖5-2所示。


5-2 包與模組的樹形關係圖

pack__init__.py程式如下所示。


01      if __name__ == '__main__':

02          print ('作為主程式運行')

03      else:

04          print ('pack初始化')


這段代碼初始化pack包,這裡直接輸出一段字串。當pack包被其他模組調用時,將輸出pack初始化。包packmyModule模組如下所示。


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初始化')


pack2myModule2模組如下。


01      def func2():

02          print ("pack2.myModule2.func()")

03     

04      if __name__ == '__main__':

05          print ('myModule2作為主程式運行')

06      else:

07          print ('myModule2被另一個模組調用')


下面的main模組調用了packpack2包中的函數。


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模組中一次導入packpack2包中所有的模組。修改後的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()xy是四則運算的兩個運算元,operator是運算子。這3個參數的值是從實際參數傳遞過來的。

·第4行到第9行代碼是函數的主體,實現了運算元的運算。

arichmetic()已經創建成功,剩下的就是函數的調用的問題了。函式呼叫的格式如下所示。


函數名(實參1,實參2…)


函數的調用採用函數名加一對圓括號的方式,圓括號內的參數是傳遞給函數的具體值。圖5-3說明了實際參數和形式參數的對應關係。


5-3 實際參數和形式參數的對應關係

arichmetic()的調用如下所示。


01    # 函數的調用

02      print (arithmetic(1, 2, "+"))


【代碼說明】 arichmetic()寫在print語句的後面,直接輸出函數的返回值。輸出結果為3

注意  實際參數必須與形式參數一一對應,否則將出現錯誤計算。具有預設值的參數例外。

5.3.2 函數的參數

CC++中,參數的傳遞有值傳遞和引用傳遞兩種方式。而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行代碼,參數xy的值分別賦值為12,參數operator使用預設值“+”。輸出結果為“3”。

·第12行代碼,提供了3個實際參數,這3個值將分別覆蓋形式參數的預設值。輸出結果為“-1”。

·第13行代碼,指定參數yoperator的值。輸出結果為“-2”。這裡必須使用賦值運算式的方式傳遞參數;否則,Python解譯器將誤認為x=3y="-"。因此下面的寫法是錯誤的。


print (arithmetic(3, "-"))


·第14行代碼,指定參數xoperator的值。輸出結果為“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行代碼把參數xy合併為一個參數,通過args列表傳遞xy的值。

·第34行代碼,從列表中取出參數值分別賦值給變數xy

·第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所示。


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(),獲得對應的運算式,並把計算後的結果返回。

對於CJava,如果函數主體沒有使用return語句返回,而在設定陳述式中調用函數,程式編譯後會出現錯誤。Python沒有這個語法限制,即使函數沒有返回值,依然可以獲得返回值。例如:


01      # 沒有return語句的函數返回None

02      def func():

03          pass

04     

05      print (func())


【代碼說明】

·第2行代碼定義了一個函數func(),函數的主體沒有任何實現代碼,pass關鍵字相當於一個預留位置。

·第5行代碼輸出func()的返回值,因為沒有return語句,所以返回值為None。輸出結果為“None”。

NonePython中的物件,不屬於數位也不屬於字串。當函數中的return語句不帶任何參數時,返回的結果也是None


01      def func():

02          return

03     

04      print (func())


如果需要返回多個值,可以把這些值打包到元組中。在調用時,對返回的元組解包即可。下面這段代碼實現了輸入變數的反轉。例如,輸入012,返回210


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行代碼“解包”元組,把反轉後的值分別賦值給變數abc

·第5行代碼,return後可以跟逗號間隔的運算式,返回多個值。

·第7行代碼調用func(),把abc分別賦值給xyz

函數中可以使用多個return語句。例如,ifelse語句的各個分支中,返回不同的結果。


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))


【代碼說明】

·第468行代碼,增加了一個變數result,通過設定陳述式來記錄程式分支的狀況。

·第9行代碼返回result的值,這樣就使得每個分支的結果都可以調用同一個return語句返回。

5.3.4 函數的嵌套

函數的嵌套是指在函數的內部調用其他函數。CC++只允許在函數體內部嵌套,而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()帶有兩個參數ab。參數ab用於計算運算式x+y的值。

·第3行代碼計算a+b的值,即返回x+y的結果。

·第4行代碼定義了函數sub()sub()帶有兩個參數ab

·第5行代碼計算ab的值,即返回mn的結果。

·第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所示。


5-5 函數的嵌套


5-6 在函數內部定義函數


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()內部調用外部變數xy

·第9行代碼,函數sub()也沒有任何參數。

·第10行代碼,在sub()內部調用外部變數mn

·第11行代碼,計算sum()*sub()的值。輸出結果為“-3

注意  儘量不要在函數內部定義函數。這種方式不便於程式的維護,容易造成邏輯上的混亂。而且嵌套定義函數的層次越多,程式維護的代價就越大。

5.3.5 遞迴函數

遞迴函數可以在函數主體內直接或間接地調用自己,即函數的嵌套是函數本身。遞迴是一種程式設計方法,使用遞迴可以減少重複的代碼,使程式變得簡潔。遞迴的過程分為兩個階段——遞推和回歸。遞迴函數的原理如下。

第一階段,遞迴函數在內部調用自己。每一次函式呼叫又重新開始執行此函數的代碼,直到某一級遞迴程式結束。

第二階段,遞迴函數從後往前返回。遞迴函數從最後一級開始返回,一直返回到第一次調用的函數體內。即遞迴逐級調用完畢後,再按照相反的順序逐級返回。

注意  遞迴函數需要編寫遞迴結束的條件;否則,遞迴程式將無法結束。一般通過判斷語句來結束程式。

計算階乘是一個經典的遞迴實現。首先,回顧一下階乘的計算公式。


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


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         # Python3reduce不再全域中,必須手動引入

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)*(mn)的程式進行一番改造,把其中的函數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>


·第910行代碼的作用與第78行原理相同。

·第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表示一個數位,該函數依次生成0n個數字。

·第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返回值後,程式將中止執行。下面這段代碼演示了yieldreturn的區別。


01      # yieldreturn區別

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.importfrom都可以用來引入模組,在什麼情況下沒法使用from

2.使用from比使用import有哪些優點?又有哪些缺點?

3.下面的代碼片段的輸出結果是什麼?

01 s = lambda x,y : x+y 02 print (s(‘aa’,’bb’))

4.寫一個根據日期計算是星期幾的模組,在程式中引入並使用這個模組。

 

0 留言:

發佈留言