过程或函数的副作用是_Python函数和函数式编程(两万字长文警告!一文彻底搞定函数,建议收藏!)...
Python函數和函數式編程
函數是可重用的程序代碼段,在Python中有常用的內置函數,例如len()、sum()等。 在Pyhon模塊和程序中也可以自定義函數。使用函數可以提高編程效率。
1、函數概述
1.1函數的基本概念
函數用于在程序中分離不同的任務。在程序設計過程中,如果可以分離任務,則建議使用函數分別實現分離后的子任務。
函數為代碼復用提供了一個通用的機制,定義和使用函數是Python程序設計的重要組成部分。
函數允許程序的控制在調用代碼和麗數代碼之間切換,也可以把控制轉換到自身的函數,即函數自己調用本身,此過程稱為遞歸(recursion)調用。
1.2函數的功能
函數是模塊化程序設計的基本構成單位,使用函數具有如下優點。
(1)實現結構化程序設計:通過把程序分割為不同的功能模塊可以實現自頂向下的結構化設計。
(2)減少程序的復雜度:簡化程序的結構,提高程序的可閱讀性。
(3)實現代碼的復用:一次定義多次調用,實現代碼的可重用性。
(4)提高代碼的質量:實現分割后子任務的代碼相對簡單,易于開發、調試、修改和維護。
(5)協作開發:在將大型項目分割成不同的子任務后,團隊多人可以分工合作,同時進行協作開發。
(6)實現特殊功能:遞歸函數可以實現許多復雜的算法。
1.3Python中函數的分類
在Python語言中函數可以分為以下4類。
(1)內置函數: Python語言內置了若干常用的函數,例如abs()、Jen()等,在程序中可以直接使用。
(2)標準庫函數:Python語言安裝程序同時會安裝若干標準庫,例如math.random等,
通過import語句可以導人標準庫,然后使用其中定義的函數。
(3)第三方庫函數: Python社區提供了許多其他高質量的庫,例如Python圖像庫等。在下載、安裝這些庫后,通過import語句可以導人庫,然后使用其中定義的函數。
(4)用戶自定義麗數:本章將詳細討論的數的定義和調用方法。
2、函數的聲明和調用
2.1函 數對象的創建
在Python語言中函數也是對象,使用def語句創建,其語法格式如下。
def函數名([形參列表]) :函數體
說明:
(1)函數使用關鍵字def聲明,函數名為有效的標識符(命名規則為全小寫字母,可以使用下畫線增加可閱讀性,例如my func),形參列表(用圓括號括起來,并用逗號隔開,可能為空)為函數的參數。函數定義的第一行稱為函數簽名(signature),函數簽名指定函數名稱以及函數的每個形式參數變量名稱。
(2)在聲明函數時可以聲明函數的參數,即形式參數,簡稱形參;形參在函數定義的圓括號對內指定,用逗號分隔。在調用函數時需要提供函數所需參數的值,即實際參數,簡稱實參。
(3) def是復合語句,故函數體需采用縮進書寫規則。
(4)函數可以使用return返回值。如果函數體中包含return語句,則返回值;否則不返回,即返回值為空(None)。無返回值的函數相當于其他編程語言中的過程。
(5) def是執行語句,Python解釋執行def語句時會創建一個函數對象,并綁定到函數名變量。
[例1 定義返回兩個數的平均值的函數。
def my average(a, b):return(a+ b)/2[例2] 定義打印n個星號的無返回值的函數。
def print star(n):print(("."* n). center(50)) #打印n個星號,兩邊填充空格總寬度為50[例3] 定義計算并返回第n階調和數(1+ 1/2+1/3+…+ 1/n)的函數。
def harmonic(n): #計算n階調和數(1 + 1/2 + 1/3+.+ 1/n)total = 0.00for i in range(1, n+ 1):total += 1.00/ ireturn total2.2函數的調用
在進行雨數調用時,根據需要可以指定實際傳人的參數值。函數調用的語法格式如下,
函數名([實參列表]);
說明:
(1)函數名是當前作用域中可用的函數對象,即調用函教之前程序必須先執行def語句,創建函數對象(內置函數對象會自動創建import導入模塊時會執行模塊中的def語句,模塊中定義的函數)。函數的定義位置必須位于調用該函數的全局代碼之前,故典型的Python程序結構順序通常為①import語句>②函數定義>③全局代碼。
(2)實參列表必須與函數定義的形參列表一一對應
(3)函數調用是表達式。如果函數有返回值,可以在表達式中直接使用;如果函數沒有返回值,則可以單獨作為表達式語句使用。
[例4]先定義一個打印n個星號的無返回值的函數print_star(n),然后從命令行第一個參數中獲取所需打印的三角形的行數lines,并循環調用print_star() 函數輸出由星號構成的等腰三角形,每行打印1,3,5,.,2* lines-1個星號。
import sys def print_star(n): print(("*"*n).center(50)) #打印n個星號,兩邊填充空格,總寬度50 lines = int(sys.argv[1]) #三角形行數 for i in range(1, 2*lines,2): #每行打印1、3、5、...個星號print_star(i)
[例5]函數的調用示例 2Charmonic py):定義計算并返回第n階調和數(1 + 1/2+1/3十…十1/n)的函數,輸出前n個調和數。
2.3函數的副作用
大多數函數授收 一個或多個參數,通過計算返回一個值,這種類型的兩牧稱為純函數(pure function),即給定同樣的實際參數,其返回值唯一, 且不會產生其他的可觀察到的副作用,例如讀取鍵盤輸人、產生輸出、改變系統的狀態等。
相對于純函數,產生副作用的函數也有一定的應用。在一般情況 下,產生副作用的函數相當于其他程序設計語言中的過程。在這些函數中可以省略return語句:當Python執行完函數的最后一條語句后,將控制權返回給調用者。
例如,函數print star(n)的副作用是向標準輸出寫人若干星號。編寫同時產生副作用和返回值的函數通常被認為是不良編程風格,但有一個例外,即讀取函數。例如.input()函數既返回一個值,同時又產生副作用(從標準輸人中讀取并消耗一個字符串)。
3、參數的傳遞
3.1形式參數和實際參數
函數的聲明可以包含一個[形參列表],而函數調用則通過傳遞[實參列表],以允許函數體中的代碼引用這些參數變量。
聲明函數時所聲明的參數即為形式參數,簡稱形參;調用函數時提供函數所需要的參數的值即為實際參數,簡稱實參。
實際參數值默認按位置順序依次傳遞給形式參數。如果參數個數不對,將會產生錯誤。
[例6]形式參 數和實際參數示例
3.2形式參數變量和對象引用傳遞
聲明函數時聲明的形式參數等同于函數體中的局部變量在函數體中的任何位置都可以使用。
局部變量和形式參數變量的區別在于局部變量在函數體中綁定到某個對象,而形式參數變量則綁定到函數調用代碼傳遞的對應實際參數對象。
Python參數傳遞方法是傳遞對象引用,而不是傳遞對象的值。
3.3傳遞不可變對象的引用
在調用函數時,若傳遞的是不可變對象(例如int lont.sr 和bol對象)的引用,則如果函數體中修改對象的值,其結果實際上是創建了一個新的對象。
[例7]錯誤的遞增函數。
在本例中,i的初值為100,當調用函數inc(i,10)后,在函數體內執行了“1+=10”語句,函數體內的i變成了110但是,當函數調用完畢返回主程序時i的值仍然為100,因為整數i是不可變對象,而在Python語言中一個函數不能改變 一個不可變對象(例如整數、浮點數、布爾值或字符串)的值(即函數無法產生副作用)。
[例8]正確的遞增函數。
i=100 def inc(j,n):j += nreturn j i =inc(i,10) print(i)在本例中,i的初值為100,當使用表達式“i=inc(i,10)”調用函數ine(i,10)后,在函數體內執行了“i+= 10”語句,函數體內的i變成了110,并 且函數返回了110。當函數調用完畢返回主程序時i被賦值為110.
3.4傳遞可變對象的引用
在調用函數時,如果傳遞的是可變對象(例如list對象)的引用,則在函數體中可以直接修改對象的值。
[例9] 定義一個可以交換給定列表中兩個指定下標的元素值的函數。
def exchange(a,i,j):temp=a[i]a[i]=a[j]a[j]=temp[例10] 隨機混排給定列表的元素值。
def shuffle(a):n=len(a) #獲取列表a的長度nfor i in range(n) : #從0到n-1進行循環迭代r = random.randrange(i, n) #取[i,n)的隨機整數exchange(a, i, r) # 交換列表a中下標分別為i和r的元素的值3.5可選參數
在聲明函數時,如果希望函數的一些參數是可選的,可以在聲明函數時為這些參數指定默認值。在調用該函數時,如果沒有傳人對應的實參值,則的數使用聲明時指定的默認參數值。
注意:
必須先聲明沒有默認值的形參,然后再聲明有默認值的形參。這是因為在函數調用時默認是按位置傳遞實際參數值的。
[例11]基于期中成績和期末成績,按照指定的權重計算總評成績。
def my_sum1(mid_score, end_score, mid_rate = 0.4): #期中成績、期末成績、期中成績權重#基于期中成績、期末成績和權重計算總評成績score = mid_score * mid_rate + end_score * (1 - mid_rate) print(format(score, '.2f')) #輸出總評成績,保留2位小數 my_sum1(88, 79) #期中成績權重為默認的40% my_sum1(88, 79, 0.5) #期中成績權重設置為50%3.6位置參數和命名參數
在函數調用時,實參默認按位置順序傳遞形參。按位置傳遞的參數稱為位置參數。
在函數調用時,也可以通過名稱(關鍵字)指定傳人的參數,例如my_ max1(a=1, b=2)或者my_ max1(b=2, a=1)。
按名稱指定傳人的參數稱為命名參數,也稱為關鍵字參數。使用關鍵字參數具有3個優點參數按名稱意義明確;傳遞的參數與順序無關:如果有多個可選參數,則可以選擇指定某個參數值。
在帶星號的參數后面聲明的參數強制為命名參數,如果這些參數沒有默認值,且調用時必須使用命名參數賦值,則會引發錯誤。
如果不需要帶星號的參數,只需要強制命名參數,則可以簡單地使用一個星號,例加
def total(initial=5, *,vegetables)[例12]基于期中成績和期末成績,按照指定的權重計算總評成績。本例中所使用的3種調用方式等價。
def my_sum2(mid_score, end_score, mid_rate = 0.4): #期中成績、期末成績、期中成績權重#基于期中成績、期末成績和權重計算總評成績score = mid_score * mid_rate + end_score * (1 - mid_rate)print(format(score, '.2f')) #輸出總評成績,保留2位小數 #期中88,期末79,并且期中成績權重為默認的40%。三種調用方式等價 my_sum2(88, 79) my_sum2(mid_score = 88, end_score = 79) my_sum2(end_score = 79, mid_score = 88)3.7可變參數
在聲明函數時,可以通過帶星的參數(例如* paraml)向函數傳遞可變數量的實參。在調用函數時,從那一點后所有的參數被收集為一個元組。
在聲明函數時,也可以通過帶雙星的參數(例如**param2)向函數傳遞可變數量的實參。
在調用函數時,從那一點后所有的參數被收集為一個字典。
帶星或帶雙星的參數必須位于形參列表的最后位置。
[例13] 利用帶星的參數計算各數字的累加和。
def my_sum3(a, b, *c): #各數字累加和total = a + bfor n in c:total = total + nreturn total print(my_sum3(1, 2)) #計算1+2 print(my_sum3(1, 2, 3, 4, 5)) #計算1+2+3+4+5 print(my_sum3(1, 2, 3, 4, 5, 6, 7)) #計算1+2+3+4+5+6+7
[例14] 利用帶星和帶雙星的參數計算各數字的累加和。
3.8 強制命名參數
在帶星號的參數后面聲明參數會導致強制命名參數(Keyword- only).在調用時必須顯式使用命名參數傳遞值,因為按位置傳遞的參數默認收集為一個元組,傳遞給前面帶星號的可變參數。
如果不需要帶星號的可變參數,只想使用強制命名參數,可以簡單地使用一個星號, 例如
def my_ func(* ,a,b, c)[例15] 基于期中成績和期末成績,按照指定的權重計算總評成績。
def my_sum(*, mid_score, end_score, mid_rate = 0.4): #期中成績、期末成績、期中成績權重#基于期中成績、期末成績和權重計算總評成績score = mid_score * mid_rate + end_score * (1 - mid_rate)print(format(score, '.2f')) #輸出總評成績,保留2位小數 my_sum(mid_score = 88, end_score = 79) #期中88,期末79,期中權重為默認的40% my_sum(end_score = 79, mid_score = 88) #期末79,期中88,期中權重為默認的40% my_sum(88, 79) #報錯,必須顯式使用命名參數傳遞值3.9參數類型檢查
通常,函數在定義時既要指定定義域也要指定值城,即指定形式參數和返回值的類型。
基于Python語言的設計理念,在定義函數時不用限定其參數和返回值的類型。這種靈活性可以實現多態性,即允許函數適用于不同類型的對象,例如my _average(a,b)函數,既可以返回兩個int對象的平均值,也可以返回兩個float 對象的平均值。
當使用不 支持的類型參數調用函數時會產生錯誤。例如,my_avreg(a,b) 函數傳遞的多數為str對象,Python在運行時將拋出錯誤TypeError.
原則上可以增加代碼檢測這種類型錯誤,但Python程序設計遵循一種慣例即用戶調用所數時必須理解并保證傳人正確類型的參數值。
4、函數的返回值
4.1return語句和函數返回值
在函數體中使用return語句可以實現從函數中返回一個值并跳出函數的功能.
[例16] 編寫函數,利用return語句返回函數值
求若干數中的最大值。
求若干數中最大值的方法一般如下。
(1)將最大值的初值設為一個比較小的數,或者取第-一個數為最大值的初值。
(2)利用循環將每個數與最大值比較,若此數大于最大值,則將此數設置為最大值。
程序運行結果如下。
4.2多條return語句
reurn語向可以放置在函數中的任何位置,當執行到第一個retum語句時程序返回到調用程序。
[例17]判斷素數示例(prime py):先編寫判斷一個數是否為素數的函數,然后編寫測試代碼,判斷并輸出1~99中的素數。
所謂素數(或稱質數),是指除了1和該數本身,不能被任何整數整除的正整數。判斷一個正整數,是否為素數,只要判斷n可否被2~n中的任何個整數整除, 如果。不能被此范圍中的任何一個整數整除,n即為素數,否則n為合數。
4.3 返回多個值
在函數體中使用return語句可實現從函數返回一個值并跳出函數。如果需要返回多個值,則可以返回一個元組。
[例18]編寫函數 ,返回一個隨機列表。先編寫一個函數,生成由n個隨機整數構成的列表,然后編寫測試代碼,生成并輸出由5個隨機整數構成的列表的各元素值。
import random def randomarray(n): #生成由n個隨機數構成的列表a = []for i in range(n):a.append(random.random())return a #測試代碼 b=randomarray(5) #生成由5個隨機數構成的列表 for i in b: print(i) #輸出列表中每個元素5、變量的作用域
變量聲明的位置不同,其可以被訪問的范圍也不同。變量的可被訪問范圍稱為變量的作用域。變量按其作用域大致可以分為全局變量、局部變量和類成員變量。
5.1全局變量
在一個源代碼文件中,在函數和類定義之外聲明的變量稱為全局變量。全局變量的作用域為其定義的模塊,從定義的位置起,直到文件結束位置。
通過import語句導人模塊,也可以通過全限定名稱‘模塊名.變量名”訪問;或者通過from-import語句導人模塊中的變量并訪問。
不同的模塊都可以訪問全局變量,這會導致全局變量的不可預知性。如果多個語句同時修改一個全局變量,則可能導致程序產生錯誤,且很難發現和更正。
全局變量降低了麗數或模塊之間的通用性,也降低了代碼的可讀性。在一般情況下 ,應該盡量避免使用全局變量。全局變量-般作為常量使用。
[例19]全局變量定義示例。
TAX1 = 0.17 #稅率常量17% TAX2 = 0.2 #稅率常量20% TAX3 = 0.05 #稅率常量5% PI = 3.14 #圓周率3.14[例20]全局變量定義示例。
TAX1 = 0.17 #稅率常量17% TAX2 = 0.2 #稅率常量20% TAX3 = 0.05 #稅率常量5% PI = 3.14 #圓周率3.14 def tax(x): #根據稅率常量20%計算納稅值return x * TAX2 #測試代碼 a = [1000, 1200, 1500, 2000] for i in a: #計算并打印4筆數據的納稅值print(i, tax(i))5.2 局部變量
在函數體中聲明的變量(包括的函數參數)稱為局部變量,其有效范圍(作用域)為函數體。
全局代碼不能引用一個函數的局部變量或形式參數變量;一個函數也不能引用在另一個函數中定義的局部變量或形式參數變量。
如果在一個函數中定義的局部變量(或形式參數變量)與全局變量重名,則局部變量(或形式參數變量)優先,即函數中定義的變量是指局部變量(或形式參數變量),而不是全局變量。
[例21]局部變量定義示例。
num = 100 #全局變量 def f():num = 105 #局部變量print(num) #輸出局部變量的值 #測試代碼 f();print(num)5.3全 局語句global
在函數體中可以引用全局變量,但如果函數內部的變量名是第一次出現且在賦值語句之前(變量賦值),則解釋為定義局部變量。
[例22]函數體錯誤引用全局變量的示例。
m = 100 n = 200 def f():print(m+5) n += 10 f()如果要為定義在函數外的全局變量賦值,可以使用global語句,表明變量是在外面定義的全局變量。global 語句可以指定多個全局變量,例如“global x, y, z”。一 般應該盡量避免這樣使用全局變量,全局變量會導致程序的可讀性差。
[例23]全局語句global
pi = 3.141592653589793 #全局變量 e = 2.718281828459045 #全局變量 def my_func():global pi #全局變量,與前面的全局變量pi指向相同的對象pi = 3.14 #改變了全局變量的值print('global pi =', pi) #輸出全局變量的值e = 2.718 #局部變量,與前面的全局變量e指向不同的對象print('local e =', e) #輸出局部變量的值 #測試代碼 print('module pi =', pi) #輸出全局變量的值 print('module e =', e) #輸出全局變量的值 my_func() #調用函數 print('module pi =', pi) #輸出全局變量的值,該值在函數中已被更改 print('module e =', e) #輸出全局變量的值5.4非局部語句nonlocal
在函數體中可以定義嵌套函數,在嵌套函數中如果要為定義在上級函數體的局部變量賦值,可以使用nonlocal語句,表明變量不是所在塊的局部變量而是在上級函數體中定義的局部變量,nonlocal語句可以指定多個非局部變量。例如“nonlocalx,y,z".
[例24]非局部語句
def outer_func():tax_rate = 0.17 #上級函數體中的局部變量print('outer func tax rate =', tax_rate) #輸出上級函數體中局部變量的值def innner_func():nonlocal tax_rate #不是所在塊的局部變量,而是在上級函數體中定義的局部變量tax_rate = 0.05 #上級函數體中的局部變量重新賦值print('inner func tax rate =', tax_rate) #輸出上級函數體中局部變量的值innner_func() #調用函數print('outer func tax rate =', tax_rate) #輸出上級函數體中局部變量的值(已更改) #測試代碼 outer_func()5.5 類成員變量
類成員變量是在類中聲明的變量,包括靜態變量和實例變量,其有效范圍(作用域)為類定義體內。
在外部,通過創建類的對象實例,然后通過“對象.實例變量”訪問類的實例變量,或者通過“類.靜態變量”訪問類的靜態變量。
5.6 輸出局部變量和全局變量
在程序運行過程中,在上下文中會生成各種局部變量和全局變量,使用內置函數global和locals()可以查看并輸出局部變量和全局變量列表。
[例25]局部變量和全局變量列表示例(locals_ globals. py)。
a=1 b=2 def f(a, b): x = 'abc'y = 'xyz' for i in range(2): #i=0~1j = i k = i**2print(locals()) f(1,2) print(globals())6、遞歸函數
6.1遞歸函 數的定義
遞歸函數即自調用函數,在函數體內部直接或間接地自己調用自己,即函數的嵌套調用是函數本身。遞歸函數常用來實現數值計算的方法。
[例26]使 用遞歸函數實現階乘
def factorial(n):if n == 1: return 1return n * factorial(n - 1) #測試代碼 for i in range(1,10): #輸出1~9的階乘print(i,'! =', factorial(i))6.2遞歸函數的原理
遞歸提供了建立數學模型的一種直接方法,與數學上的數學歸納法相對應。
每個遞歸函數必須包括以下兩個主要部分。
(1)終止條件:表示遞歸的結束條件,用于返回函數值,不再遞歸調用。例如,factorial()函數的結束條件為“n等于1”。
(2)遞歸步驟:遞歸步驟把第n步的參數值的函數與第n-1步的參數值的函數關聯。
例如,對于factorial(),其遞歸步驟為“n * factorial(n- 1)”。
另外,一序列的參數值必須逐漸收斂到結束條件。例如,對于factorial(),每次遞歸調用參數值n均遞減1,所以一序列參數值逐漸收斂到結束條件(n=1)。
例如,調和數的計算公式如下。
H。=1十1/2+…+1/n
故可以使用遞歸函數實現。
(1)終止條件: H。= 1
當n==l時
(2)遞歸步驟: H。= H。-1+ 1/n
當n>1時
每次遞歸,n嚴格遞減,故逐漸收斂于1。
[例27]使 用遞歸函數實現調和數 。
6.3編寫遞歸函數時需要注意的問題
雖然使用遞歸函數可以實現簡潔、優雅的程序,但在編寫遞歸雨數時應該注意如下幾個問題。
(1)必須設置終止條件。
缺少終止條件的遞歸函數將導致無限遞歸函數調用,其最終結果是系統會耗盡內存,此時Python會拋出錯誤RuntimeError, 并報告錯誤信息“maximum recursion depth exceeded(超過最大遞歸深度)”。
在遞歸函數中一般需要設置終止條件。在sys模塊中,函數getrecursionlimit()和setrecursionlimit()用于獲取和設置最大遞歸次數。例如:
(2)必須保證收斂。
遞歸調用所解決子問題的規模必須小于原始問題的規模,否則會導致無限遞歸函數調用
(3)必須保證內存和運算消耗控制在定范圍內。
遞歸函數代碼雖然看起來簡單,但往往會導致過量的遞歸函數調用,從而消耗過量的內存(導致內存溢出),或過量的運算能力(運行時間過長)。
6.4 遞歸函數的應用:最大公約數
用于計算最大公約數問題的遞歸方法稱為歐幾里得算法,其描述如下:
如果p>q,則p和q的最大公約數等于q和p % q的最大公約數。
故可以使用遞歸函數實現,步驟如下。
(1)終止條件: gcd(p,q) = p #當q==0時
(2)遞歸步驟: gcd(q, p%q) #當q>1時
每次遞歸,p%q嚴格遞減,故逐漸收斂于0。
[例28]使用遞歸函數計算最大公約數。
import sys def gcd(p, q): #使用遞歸函數計算p和q的最大公約數if q == 0: return p #如果q=0,返回preturn gcd(q, p % q) #否則,遞歸調用gcd(q, p % q) #測試代碼 p = int(sys.argv[1]) #p=命令行第一個參數 q = int(sys.argv[2]) #q=命令行第二個參數 print(gcd(p, q)) #計算并輸出p和q的最大公約數6.5 遞歸函數的應用:漢諾塔
漢諾塔(Towers of Hanoi, 又稱河內塔)源自于印度的古老傳說:大梵天創造世界的時候,在世界中心貝拿勒斯的圣廟里做了3根金剛石柱子,在一根柱子上從下往上按照大小順序摞著64片黃金圓盤,稱之為漢諾塔。
大梵天命令婆羅門把圓盤從一根柱子上按大小順序重新擺放到另一根柱子上,并且規定在3根柱子之間一次只能移動個圓盤,且小圓盤上不能放置大圓盤。這個游戲稱為漢諾塔益智游戲。
漢諾塔益智游戲問題很容易使用遞歸函數實現。假設柱子的編號為a、b、c,定義函數hanoi(n, a, b, c)表示把n個圓盤從柱子a移到柱子c(可以經由柱子b),則有:
(1)終止條件。當n==1時,hanoi(n, a, b, c)為終止條件。即如果柱子a上只有一個圓盤,則可以直接將其移動到柱子c上。
(2)遞歸步驟。hanoi(n,a, b, c)可以分解為3個步驟,即hanoi(n-1,a,c,b)、hanoi(1,a,b,c)和hanoi(n-1,b,a.c)。 如果柱子a上有n個圓盤,可以看成柱子a上有一個圓盤(底盤)和(n- 1)個圓盤,首先需要把柱子a上面的(n-1)個圓盤移動到柱子b.即調用hanoi(n-1,a,c,b); 然后把柱子a上的最后一個圓盤移動到柱子c,即調用hanoi(1,a,b.c);再將柱子b上的(n- 1)個圓盤移動到柱子c,即調用hanoi(n-1,b,a,c)。
每次遞歸,n嚴格遞減,故逐漸收斂于1.
[例29]使用遞歸 函數實現漢諾塔問題。
#將n個從小到大依次排列的圓盤從柱子a移動到柱子c上,柱子b作為中間緩沖 def hanoi(n,a,b,c):if n==1: print(a,'->',c) #只有一個圓盤,直接將圓盤從柱子a移動到柱子c上else:hanoi(n-1,a,c,b) #先將n-1個圓盤從柱子a移動到柱子b上(采用遞歸方式)hanoi(1,a,b,c) #然后將最大的圓盤從柱子a移動到柱子c上hanoi(n-1,b,a,c) #再將n-1個圓盤從柱子b移動到柱子c上(采用遞歸方式) #測試代碼 hanoi(4,'A','B','C')7、內置函數的使用
在python語言中提供了若干內置函數,用于實現常用的功能,可以直接使用。
7.1 內置函數一覽表
各個內置函數的具體功能和用法,可通過訪問 https://docs.python.org/zh-cn/3/library/functions.html 進行查看。
需要注意的是,開發者不建議使用以上內置函數的名字作為標識符使用(作為某個變量、函數、類、模板或其他對象的名稱),雖然這樣做 Python 解釋器不會報錯,但這會導致同名的內置函數被覆蓋,從而無法使用。
8、Python函數式編程基礎
Python是面向對象的程序設計語言,也是面向過程的程序語言,同時也支持函數式編程。
Pyhon標準庫functools 提供了若干關于函數的函數,提供了Haskell和Standard ML中的函數式程序設計工具。
8.1作為對象的函數
在Python語言中函數也是對象,故函數對象可以賦值給變量。
[例30]作為對象的函數。
8.2 高階函數
函數對象也可以作為參數傳遞給函數,還可以作為函數的返回值。參數為函數對象的函數或返回函數對象的函數稱為高階函數,即函數的函數。
[例31]高階函數。
8.3map()函數
map() 函數的基本語法格式如下:
map(function, iterable)其中,function 參數表示要傳入一個函數,其可以是內置函數、自定義函數或者 lambda 匿名函數;iterable 表示一個或多個可迭代對象,可以是列表、字符串等。
map() 函數的功能是對可迭代對象中的每個元素,都調用指定的函數,并返回一個 map 對象。
注意,該函數返回的是一個 map 對象,不能直接輸出,可以通過 for 循環或者 list() 函數來顯示。
【例32】還是對列表中的每個元素乘以 2。
listDemo = [1, 2, 3, 4, 5] new_list = map(lambda x: x * 2, listDemo) print(list(new_list))運行結果為:
[2, 4, 6, 8, 10]【例33】map() 函數可傳入多個可迭代對象作為參數。
listDemo1 = [1, 2, 3, 4, 5] listDemo2 = [3, 4, 5, 6, 7] new_list = map(lambda x,y: x + y, listDemo1,listDemo2) print(list(new_list))運行結果為:
[4, 6, 8, 10, 12]注意,由于 map() 函數是直接由用 C 語言寫的,運行時不需要通過 Python 解釋器間接調用,并且內部做了諸多優化,所以相比其他方法,此方法的運行效率最高。
8.4filter()函數
filter()函數的基本語法格式如下:
filter(function, iterable)此格式中,funcition 參數表示要傳入一個函數,iterable 表示一個可迭代對象。
filter() 函數的功能是對 iterable 中的每個元素,都使用 function 函數判斷,并返回 True 或者 False,最后將返回 True 的元素組成一個新的可遍歷的集合。
【例34】返回一個列表中的所有偶數。
listDemo = [1, 2, 3, 4, 5] new_list = filter(lambda x: x % 2 == 0, listDemo) print(list(new_list))運行結果為:
[2, 4]【例35】filter() 函數可以接受多個可迭代對象。
listDemo = [1, 2, 3, 4, 5] new_list = map(lambda x,y: x-y>0,[3,5,6],[1,5,8] ) print(list(new_list))運行結果為:
[True, False, False]8.5reduce()函數
reduce() 函數通常用來對一個集合做一些累積操作,其基本語法格式為:
reduce(function, iterable)其中,function 規定必須是一個包含 2 個參數的函數;iterable 表示可迭代對象。
注意,由于 reduce() 函數在 Python 3.x 中已經被移除,放入了 functools 模塊,因此在使用該函數之前,需先導入 functools 模塊。
【例36】計算某個列表元素的乘積。
import functools listDemo = [1, 2, 3, 4, 5] product = functools.reduce(lambda x, y: x * y, listDemo) print(product)運行結果為:
120小結:
通常來說,當對集合中的元素進行一些操作時,如果操作非常簡單,比如相加、累積這種,那么應該優先考慮使用 map()、filter()、reduce() 實現。另外,在數據量非常多的情況下(比如機器學習的應用),一般更傾向于函數式編程的表示,因為效率更高。
當然,在數據量不多的情況下,使用 for 循環等方式也可以。不過,如果要對集合中的元素做一些比較復雜的操作,考慮到代碼的可讀性,通常會使用 for 循環。
8.6 lambda表達式(匿名函數)及用法
lambda 表達式(又稱匿名函數)是現代編程語言爭相引入的一種語法,如果說函數是命名的、方便復用的代碼塊,那么 lambda 表達式則是功能更靈活的代碼塊,它可以在程序中被傳遞和調用。回顧局部函數
def get_math_func(type) :# 定義一個計算平方的局部函數def square(n) : # ①return n * n# 定義一個計算立方的局部函數def cube(n) : # ②return n * n * n# 定義一個計算階乘的局部函數def factorial(n) : # ③result = 1for index in range(2 , n + 1):result *= indexreturn result# 返回局部函數if type == "square" :return squareif type == "cube" :return cubeelse:return factorial # 調用get_math_func(),程序返回一個嵌套函數 math_func = get_math_func("cube") # 得到cube函數 print(math_func(5)) # 輸出125 math_func = get_math_func("square") # 得到square函數 print(math_func(5)) # 輸出25 math_func = get_math_func("other") # 得到factorial函數 print(math_func(5)) # 輸出120程序中,定義了一個 get_math_func() 函數,該函數將返回另一個函數。接下來在 get_math_func() 函數體內的 ①、②、③ 號代碼分別定義了三個局部函數,最后 get_math_func() 函數會根據所傳入的參數,使用這三個局部函數之一作為返回值。
在定義了會返回函數的 get_math_func() 函數之后,接下來程序調用 get_math_func() 函數時即可返回所需的函數
由于局部函數的作用域默認僅停留在其封閉函數之內,因此這三個局部函數的函數名的作用太有限了,即僅僅是在程序的 if 語句中作為返回值使用。一旦離開了 get_math_func() 函數體,這三個局部函數的函數名就失去了意義。
既然局部函數的函數名沒有太大的意義,那么就考慮使用 lambda 表達式來簡化局部函數的寫法。使用 lambda 表達式代替局部函數
如果使用 lambda 表達式來簡化 get_math_func() 函數,則可以將程序改寫成如下形式:
在上面代碼中,return 后面的部分使用 lambda 關鍵字定義的就是 lambda 表達式,Python 要求 lambda 表達式只能是單行表達式。
注意:由于 lambda 表達式只能是單行表達式,不允許使用更復雜的函數形式,因此上面 ③ 號代碼處改為計算 1+2+3+…+n 的總和。
lambda 表達式的語法格式如下:
lambda [parameter_list] : 表達式從上面的語法格式可以看出 lambda 表達式的幾個要點:
lambda 表達式必須使用 lambda 關鍵字定義。
在 lambda 關鍵字之后、冒號左邊的是參數列表,可以沒有參數,也可以有多個參數。如果有多個參數,則需要用逗號隔開,冒號右邊是該 lambda 表達式的返回值。
實際上,lambda 表達式的本質就是匿名的、單行函數體的函數。因此,lambda 表達式可以寫成函數的形式。
例如,對于如下 lambda 表達式:
lambda x , y:x + y可改寫為如下函數形式:
def add(x, y):return x+ y上面定義函數時使用了簡化語法:當函數體只有一行代碼時,可以直接把函數體的代碼放在與函數頭同一行。
總體來說,函數比 lambda 表達式的適應性更強,lambda 表達式只能創建簡單的函數對象(它只適合函數體為單行的情形)。但 lambda 表達式依然有如下兩個用途:
- 對于單行函數,使用 lambda 表達式可以省去定義函數的過程,讓代碼更加簡潔。
- 對于不需要多次復用的函數,使用 lambda 表達式可以在用完之后立即釋放,提高了性能。
下面代碼示范了通過 lambda 表達式來調用 Python 內置的 map() 函數:
# 傳入計算平方的lambda表達式作為參數 x = map(lambda x: x*x , range(8)) print([e for e in x]) # [0, 1, 4, 9, 16, 25, 36, 49] # 傳入計算平方的lambda表達式作為參數 y = map(lambda x: x*x if x % 2 == 0 else 0, range(8)) print([e for e in y]) # [0, 0, 4, 0, 16, 0, 36, 0]正如從上面代碼所看到的,內置的 map() 函數的第一個參數需要傳入函數,此處傳入了函數的簡化形式:lambda 表達式,這樣程序更加簡潔,而且性能更好。小結:
lambda 表達式是 Python 編程的核心機制之一。Python 語言既支持面向過程編程,也支持面向對象編程。而 lambda 表達式是 Python 面向過程編程的語法基礎,因此必須引起重視。
Python 的 lambda 表達式只是單行函數的簡化版本,因此 lambda 表達式的功能比較簡單。
8.7@函數裝飾器及用法(超級詳細)
8.7.1 引入
Python 內置的 3 種函數裝飾器,分別是 @staticmethod、@classmethod 和 @property,其中 staticmethod、classmethod 和 property 都是 Python 的內置函數。那么,我們是否可以開發自定義的函數裝飾器呢?答案是肯定的。
當程序使用“@函數”(比如函數 A)裝飾另一個函數(比如函數 B)時,實際上完成如下兩步:
- 將被修飾的函數(函數 B)作為參數傳給 @ 符號引用的函數(函數 A)。
- 將函數 B 替換(裝飾)成第 1 步的返回值。
從上面介紹不難看出,被“@函數”修飾的函數不再是原來的函數,而是被替換成一個新的東西(取決于裝飾器的返回值)。其實所謂的裝飾器,就是通過裝飾器函數,來修改原函數的一些功能,使得原函數不需要修改。
為了讓大家厘清函數裝飾器的作用,下面看一個非常簡單的示例:
def funA(fn):print('A')fn() # 執行傳入的fn參數return 'fkit' ''' 下面裝飾效果相當于:funA(funB), funB 將會替換(裝飾)成 funA() 語句的返回值; 由于funA()函數返回 fkit,因此 funB 就是 fkit ''' @funA def funB():print('B') print(funB) # fkit上面程序使用 @funA 修飾 funB,這意味著程序要完成兩步操作:
- 將 funB 作為 funA() 的參數,也就是上面代碼中 @funA 相當于執行 funA(funB)。
- 將 funB 替換成 funA() 執行的結果,funA() 執行完成后返回 fkit,因此 funB 就不再是函數,而是被替換成一個字符串。
其實,簡單地理解函數裝飾器的作用,上面程序可以等價地轉換成如下程序:
def funA(fn):print('A')fn() # 執行傳入的fn參數return 'fkit' def funB():print('B') funB = funA(funB) print(funB) # fkit注意,此程序中的 funB = funA(funB) 就等同于上面程序中 @funA 所起的作用。
運行上面 2 段程序,可以看到相同的輸出結果:
AB
Fkit
通過這個例子,相信讀者對函數裝飾器的執行關系己經有了一個較為清晰的認識,但讀者可能會產生另一個疑問,這個函數裝飾器導致被修飾的函數變成了字符串,那么函數裝飾器有什么用?
別忘記了,被修飾的函數總是被替換成 @ 符號所引用的函數的返回值,因此被修飾的函數會變成什么,完全由于 @ 符號所引用的函數的返回值決定,換句話說,如果 @ 符號所引用的函數的返回值是函數,那么被修飾的函數在替換之后還是函數。
8.7.2帶參數的函數裝飾器
如果原函數 funB() 中有參數需要傳遞給函數裝飾器,應該如何實現?
一個簡單的辦法是,可以在對應的函數裝飾器 funA() 上,添加相應的參數,例如:
def foo(fn):# 定義一個嵌套函數def bar(a): fn(a * (a - 1))print("*" * 15)return fn(a * (a - 1))return bar ''' 下面裝飾效果相當于:foo(my_test), my_test將會替換(裝飾)成該語句的返回值; 由于foo()函數返回bar函數,因此my_test就是bar 同時,my_test 的參數 a 對應 bar 函數的參數 a ''' @foo def my_test(a):print("==my_test函數==", a) # 打印my_test函數,將看到實際上是bar函數 print(my_test) # 下面代碼看上去是調用my_test(),其實是調用bar()函數 my_test(10)上面程序定義了一個裝飾器函數 foo,該函數執行完成后并不是返回普通值,而是返回 bar 函數(這是關鍵),這意味著被該 @foo 修飾的 my_test() 函數最終都會被替換成 bar() 函數。
上面程序使用 @foo 修飾 my_test() 函數,因此程序同樣會執行 foo(my_test),并將 my_test 替換成 foo() 函數的返回值:bar 函數。所以,上面程序第 18 行代碼在打印 my_test 函數時,實際上輸出的是 bar 函數,這說明 my_test 已經被替換成 bar 函數。接下來程序調用 my_test() 函數,實際上就是調用 bar() 函數。
運行上面程序,可以看到如下輸出結果:
<function foo..bar at 0x0000012D7E246598>== my_test函數== 90
== my_test函數== 90
在此基礎上,還有一個問題,如果程序中另外還有一個函數,也需要使用 funA 裝飾器,但是這個新的函數有 2 個參數,此時又該怎么辦呢?例如:
@foo def new_test(a,b): ....在這種情況下,最簡單的解決方式是用 *args 和 **kwargs 作為 foo 函數裝飾器內部函數 bar() 的參數,*args 和 **kwargs 表示接受任意數量和類型的參數,因此函數裝飾器可以寫成下面的形式:
def foo(fn):# 定義一個嵌套函數def bar(*args,**kwargs):fn(*args,**kwargs)return bar @foo def my_test(a):print("==my_test函數==", a) @foo def new_test(a,b):print("==new_test函數==",a," ",b) my_test(10) new_test(6, 5)運行結果為:
== my_test函數== 10== new_test函數== 6 5
8.7.3帶自定義參數的函數裝飾器
其實,函數裝飾器還有更大程度的靈活性。剛剛說了,裝飾器可以接受原函數任意類型和數量的參數,除此之外,它還可以接受自己定義的參數。
舉個例子,比如要定義一個參數,來表示裝飾器內部函數被執行的次數,那么就可以寫成下面這種形式:
def foo(num):def my_decorator(fn):def bar(*args,**kwargs):for i in range(num):fn(*args,**kwargs)return barreturn my_decorator @foo(3) def my_test(a):print("==my_test函數==", a) @foo(5) def new_test(a,b):print("==new_test函數==",a," ",b) my_test(10) new_test(6, 5)運行結果為:
== my_test函數== 10== my_test函數== 10
== my_test函數== 10
== new_test函數== 6 5
== new_test函數== 6 5
== new_test函數== 6 5
== new_test函數== 6 5
== new_test函數== 6 5
8.7.4函數裝飾器也可以嵌套
上面示例中,都是使用一個裝飾器的情況,但實際上,Python 也支持多個裝飾器,比如:
@decorator1 @decorator2 @decorator3 def func():...上面程序的執行順序是里到外,所以它等效于下面這行代碼:
decorator1( decorator2( decorator3(func) ) )本文轉載于:
Python函數和函數式編程(兩萬字長文警告!一文徹底搞定函數,建議收藏!)_司夏的博客-CSDN博客_python 函數式編程?gxhxl.blog.csdn.net總結
以上是生活随笔為你收集整理的过程或函数的副作用是_Python函数和函数式编程(两万字长文警告!一文彻底搞定函数,建议收藏!)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: usb 系统消息_4. Autoware
- 下一篇: python 堆_40道Python经典