函數 函數允許程式的控制在不同的代碼片段之間切換,函數的重要意義在於可以在程式中清晰地分離不同的任務,將複雜的問題分解為幾個相對簡單的子問題,並逐個解決。即“分而治之”。 Python的自建模塊一般體現為函數。Python函數有如下特點: (1) 函數是組織好的、可重覆使用的,用來實現單一或者相關聯 ...
函數
函數允許程式的控制在不同的代碼片段之間切換,函數的重要意義在於可以在程式中清晰地分離不同的任務,將複雜的問題分解為幾個相對簡單的子問題,並逐個解決。即“分而治之”。
Python的自建模塊一般體現為函數。Python函數有如下特點:
(1) 函數是組織好的、可重覆使用的,用來實現單一或者相關聯功能的代碼段。
(2) 函數首先關註所有任務,然後關註如何完成每項任務。函數類型有兩種:有返回值的函數和僅僅執行代碼而不返回值的函數。
(3) 函數能提高應用程式的模塊化程度和代碼的重要性。
Python有很多內建函數(即內置函數)例如:print()、int()、float()等。但也可以自己創建函數,在python中成為用戶自定義函數。
- 基本原理:
函數的定義:
(1) 語法: def 函數名(參數1,參數2,參數3,,,,):2
“描述信息”3
函數體4
return #用來定義返回值,可以跟任意數據類型<br><br>
(2)函數定義應該遵循的規則:
- 函數代碼塊以 def 關鍵詞開頭,後接函數標識符名稱和圓括弧(),最後是冒號(:)
- 函數命名應該能夠描述函數的功能,而且必須符合標識符的命名規則。
- 任何傳入參數和自變數必須放在圓括弧中間。圓括弧之間可以用於定義參數。
- 函數的第一行語句可以選擇性地使用文檔字元串—用於存放函數說明。
- 函數內容(語句塊)放於冒號後,每條語句都要縮進相應數量的空格。
return [表達式] 結束函數,選擇性地返回一個值給調用方。不帶表達式的return相當於返回 None。
函數的調用:通過輸入實參來替換形參完成函數的調用
定義時無參,調用時也無參(無參函數)
定義時有參,調用時需要傳參(有參函數)
2.形參與實參
在定義函數時,它的輸入變數被稱為函數的形參,而執行函數時的輸入變數被稱為實參。
(1) 參數傳遞-----通過位置和關鍵字
例如:def subtract(x1,x2): #函數定義
return(x1-x2)
位置實參:在調用函數的時候,必須將每個實參都關聯到函數定義的每一個形參,最簡單的關聯方式就是基於實參的順序。
註意:使用位置實參的方式傳值,傳入的實參個數必須與形參相同,否則運行程式會報錯。
通過位置傳遞參數來調用函數,當調用函數subtract時,每個形參都被實參所取代,只有實參的順序是重要的,實參可以是任意對象。
z=3
e=subtract(5,z)
關鍵字實參:是通過關鍵字-值的方式,關鍵字實參的方式就不需要考慮函數調用過程中實參的順序。同一個參數不能傳兩個值
z=3
e=subtract(x2=z,x1=5)
#在這裡的函數調用中,實參時通過名稱賦值給形參而不是通過位置
傳參的規則:
在實參的角度:
規則:按位置傳值必須在按關鍵字傳值的前面
對一個形參只能賦值一次
1.按照位置傳值
2.按照關鍵字傳值
3.混著用
在形參的角度:
規則:預設參數必須放到位置參數的後面
1.位置參數
2.預設參數
3.*args (接收位置傳值)
4.**kwargs(接收關鍵字傳值)
(2) 更改實參
實參的作用是為函數提供必要的輸入數據,更改函數內部的參數值通常不會影響函數外部的實參值
例如1:對於所有不可變參數(字元串、數字和元組)更改函數內部的實參值通常不會影響函數外部的實參值。
def subtract(x1,x2):
z=x1-x2
x2=50.
return (z)
a=20.
b=subtract(10,a) #返回-10
print(b)
print(a) #返回20.0
示例2:將可變參數(例如:列表或字典)傳遞給函數併在函數內部將其改變,那麼函數外部也會發生改變
def subtract(x):
z=x[0]-x[1]
x[1]=50.
return(z)
a=[10,20]
b=subtract(a)
print(b) #返回-10
print(a) #返回[10,50.0]
(3) 預設參數
預設值是定義函數時已經給出的值。如果在不提供該參數的情況下調用函數,python將使用程式員在定義函數時所提供的值。
def subtract(x1,x2=0):
z=x1-x2
return (z)
x=subtract(10)
print(x) #必須給出所有的位置參數,只要那些省略的參數在函數定義中有預設值,就不必提供所有的關鍵字參數。
註意:可變預設參數:使用可變數據類型的參數作為預設參數時,如果更改函數內部的可變類型參數,則會產生副作用。例如:
def my_list(x1,x2=[]):
x2.append(x1)
return(x2)
print(my_list(1)) #結果為:[1]
print(my_list(2)) #結果為[1,2]
(4) 可變參數:傳入的參數的個數是可變的。
*args 位置參數,表示把args這個list(列表)或者tuple(元組)的所有元素作為可變參數傳進去
def foo(x,*args): #x為位置參數, args是可變參數
print(x)
print(args)
foo(1,2,3,4) #1傳給位置參數x,剩下的全部傳給args
foo(1,*(2,3,4)) #可以直接把一個tupleh或list傳給可變參數args
返回結果為:1
(2, 3, 4)
**kwargs關鍵字參數:允許傳入0個或者任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個dict(字典)。
示例:
def foo(x,**kwargs):
print(x)
print(kwargs)
#兩種傳遞方式:**傳遞或者鍵值對傳遞
foo(1,**{"y":2,"z":3}) #可以直接把一個字典通過加**傳遞給關鍵字參數,
# 返回 1
{'y': 2, 'z': 3}
foo(1,y=2,z=3) #返回結果同上
列表和字典可用來定義或調用參數個數可變的函數。
例如:
import matplotlib.pyplot as plt
data=[[1,2],[3,4]]
style=dict({'linewidth':3,'marker':'o','color':'green'})
plt.plot(*data,**style) #以*為首碼的變數名稱(*data)是指提供了在函數調用中解包的列表,這樣一來,列表就會產生位置參數。以**為首碼的變數名稱(**style)是將字典解包為關鍵字參數。
3. 返回值
return[表達式]用於退出函數。Python中的函數總是返回單個對象。如果一個函數必須返回多個對象,那麼這些對象將被打包並作為一個元組對象返回。
示例:import numpy as np
def complex_to_polar(z):
r = np.sqrt(z.real**2+z.imag**2)
phi = np.arctan2(z.imag,z.real)
return (r,phi)
z=3+5j
a=complex_to_polar(z)
r=a[0]
phi=a[1] #可寫為一行:r,phi=complex_to_polar(z)
print(r) 運行結果為:5.830951894845301
1.0303768265243125
print(phi)
如果函數沒有return語句,則返回None。因為由於傳遞給函數的變數可能會有所修改,則在很多情況下,函數不需要返回任何值。
示例:def append_to_list(L,x):
- append(x) #註意:該函數沒有返回值,是由於它修改了給出的參數對象中的其中一個L。
這裡僅提到了列表方法,如append、extend、reverse、sort方法不返回任何值(返回None),當通過這種方法來修改對象時,修改被稱為原位修改。
4. 遞歸函數
在一個函數內部,可以調用其他函數。假如一個函數在其內部可以調用自己,那麼這個函數是遞歸函數。
遞歸是一種直接和間接地調用函數自身的過程。遞歸的特性有三點:
(1)必須有明確的結束條件。
(2)每次進入更深一層的遞歸時,問題規模相比上次遞歸應有所減少。
(3)遞歸效率不高,遞歸層次過多會導致棧溢出。
遞歸的優點與缺點:
優點:遞歸使代碼看起來更加整潔、優雅;可以用遞歸將複雜任務分解成更加簡單的子問題;
使用遞歸比使用一些嵌套迭代更加容易。
缺點:遞歸的邏輯很難調試、跟進;遞歸調用的代價高昂(效率低)。
例如:def recursion():
return(recursion()) #註意:這個遞歸定義顯然什麼都沒有,如果運行該函數的結果就是一段時間後程式就崩掉了。因此每次調用函數都將會消耗一些記憶體,當記憶體爆滿就自然就掛了。這個函數中的遞歸為無窮遞歸,就好比一個while死迴圈。
正常的遞歸函數應該包含以下兩個部分:
基線條件(針對最小問題):滿足條件時函數將直接返回一個值
遞歸條件:包含一個或者多個調用,這些調用旨在解決問題的一部分。
示例:
#用傳統的迴圈方式寫:
def factorial(n):
result = n
for i in range(1,n):
result *= i
return result
print(factorial(2))
#通過遞歸的方式實現的,n的階乘看做是n乘以(n-1)的階乘,而1 的階乘為1
def factorial(n):
if n == 1:
return 1
else:
return n*factorial(n-1)
print(factorial(2))
尾遞歸:
在電腦中,函數調用是通過棧這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀;每當函數返回,棧就會減少一層棧幀。由於棧的大小不是無限的,所以遞歸調用的次數越多會導致棧溢出。)
為了防止棧的溢出;我們可以使用尾遞歸優化,尾遞歸是指:在函數返回的時候,調用自身本身,並且return語句不能包含表達式。這樣,編譯器或者解釋器就可以把尾遞歸做優化,使遞歸本身無論調用多少次,都只占用一個棧幀,不會出現棧溢出的情況,尾遞歸的實現方式是 :使函數本身返回的是函數本身。
示例如下:
def chebyshev(n,x):
if n==0:
return(1.)
elif n==1:
return(x)
else:
return(2*x*chebyshev(n-1,x)-chebyshev(n-2,x))
a=chebyshev(5,0.52)
print(a) #返回結果:0.39616645119999994
5. 函數文檔、函數是對象、偏函數應用
(1) 文檔字元串:在使用def 關鍵字定義一個函數時, 其後必須跟有函數名和包括形式參數的圓括弧。函數體的下一行開始,必須是縮進的。函數體的第一個的邏輯行的字元串,這個字元串就是這個函數的文檔字元串,通常稱作docstring
文檔字元串的定義:
在函數體的第一行,我們使用一對三個單引號或者一對三個雙引號來定義文檔字元串,文檔字元串通常第一行以大寫字母開頭,以句號結束,第二行是空行,從第三行開始是詳細描述。
文檔字元串的作用:
文檔字元串是我們使用python過程中的一個重要的工具,它對文檔很有幫助,使程式容易理解。甚至當程式運行的時候,我們可以從一個函數中返回字元文檔。把函數當做一個對象來看的話,相當於我們獲取了一個對象的屬性(_doc_)
def printMax(x,y):
'''列印兩個數中的最大值。
兩個數必須都是整形數。'''
x=int(x)
y=int(y)
if x>y:
print(x,'最大')
else:
print(y,'最大')
print(printMax(3,5))
help(printMax) #調用函數help()時,可以將該文檔字元串餘函數的調用一起進行展示
結果為:5 最大
None
Help on function printMax in module __main__:
printMax(x, y)
列印兩個數中的最大值。
兩個數必須都是整形數。
補充:查看Python的模塊和函數幫助文檔方法:
Python自帶的查看幫助功能,可以在編程時不中斷地迅速找到所需模塊和函數的使用方法。
- l通用幫助函數help(),進入help幫助文檔界面,根據屏幕提示可以繼續鍵入相應關鍵詞進行查詢,繼續鍵入modules可以列出當前所有安裝的模塊。
- l查詢特定的模塊和函數幫助信息:
- 查詢.py結尾的普通模塊help(module_name),使用help(module_name)時首先需要導入(import)模塊
- 查詢內建模塊sys.bultin_modulenames 註意需要導入sys模塊
- l 查詢函數信息:
查看模塊下所有函數dir(module_name)
import math
print(dir(math)) #運行結果:列舉出math模塊下所有的函數模塊
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']
查看模塊下特定函數信息help(module_name.func_name)
import math
print(help(math.sin))
運行結果:
Help on built-in function sin in module math:
sin(x, /)
Return the sine of x (measured in radians).
None
查看函數信息的另一種方法:print(func_name._doc_)
例如:print(print.__doc__)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
(2) 函數是對象,可以將函數作為參數傳遞,也可更改名稱或者刪除它們。
(3) 偏函數應用:
(w,t):-f(w,t)=sin(2*np.pi*wt)是一個雙變數函數。對於給定的參數值w,這種解釋解釋將兩個變數中的函數簡化為變數t。
部分應用程式:這種通過固定(凍結)函數的一個函數或者多個參數來定義新函數的過程稱為部分應用程式。
偏函數可以使用python模塊functools來輕鬆創建,該模塊為實現這個目的提供一個名為partial函數。
6. 匿名函數--lambda關鍵字
Python使用lambda來創建匿名函數。所謂匿名,即不再使用def關鍵字以標準的形式定義一個函數。開發者可能只想對能夠用簡單表達式來表示的函數執行操作,而不想對函數進行命名或者通過冗長的def塊來定義函數。
(1) Lambda表達式如下特點:
- Lambda只是一個表達式,函數體比def簡單很多。
- Lambda的主體是一個表達式,而不是一個代碼塊,因而僅僅能在lambda表達式中封裝有限的邏輯
- Lambda函數擁有自己的命名空間,且不能訪問自有參數列表之外或全局命名空間里的參數
- 雖然lambda函數看起來只能寫一行,卻不等於C或C++的內聯函數,後者的目的是調用小函數時不占用棧記憶體從而提高運行記憶體。
(2) Lambda函數的語法只包含一個語句,格式如下:
Lambda[arg1,[arg2,......,argn]]:expression
Lambda函數的定義只能由單個表達式組成,尤其不能包含迴圈。像其他函數一樣,lambda函數也可以作為對象分配給變數。
註意:使用lambda函數應該註意的幾點:
Lambda定義的單行函數,如需要複雜的函數,應該定義普通函數。
Lambda參數列表可以包含多個參數,如lambdax,y:x+y
Lambda中的表達式不能含有命令,而且只限一條表達式。
如:parabola=lambda x: x**2+5
print(parabola(3))
示例如下:lambda函數的使用--加法與減法
#自定義函數
sum=lambda arg1,arg2:arg1+arg2
sub=lambda arg1,arg2:arg1-arg2
#調用sum函數
print('相加的值:',sum(10,22))
print('相減的值:',sub(20,5)) 結果為: 相加的值: 32
相減的值: 15
(4) lambda函數提供了製作閉包的途徑
閉包的含義:一個定義在函數內部的函數,閉包使得變數即使脫離了該函數的作用域範圍也依然能被訪問到(在一個外函數中定義一個內函數,內函數里運用了外函數的臨時變數,並且外函數的返回值是內函數的引用,這樣就構成了一個閉包)。
7.裝飾器
① 背景:如果想要看看以前寫過的一個函數在數集上的運行時間是多少,這時可以修改之前的代碼為它加上新的東西,來實現這樣的功能。但這樣做有些繁瑣,那麼應該採用一種可以不對源代碼做任何修飾,並且能夠很好的實現所有需求的手段--這裡就是用Python裝飾器來實現的。
② 前提是:知道閉包函數,這種函數只可以在外部函數的作用域內被正常調用,在外部函數的作用域之外調用繪報錯。如果內部函數里引用了外部函數里定義的對象(甚至是外層之外,但不是全局變數),那麼此時內部函數就會被稱為閉包函數,閉包函數所引用的外部定義的變數被叫做自由變數。閉包函數可以將其自己的代碼和作用域以及外部函數的作用結合在一起。
例如:
def count():
a=1
b=2
def sum():
c=1
return(a+c) #註意:a是自由變數,return sum
③ 定義:裝飾器是python中的語法元素,它可以不改變函數本身定義的情況下很方便地變更函數的行為。Python裝飾器本質上就是一個函數,它可以讓其他函數在不需要代碼變動的前提下增加額外的功能,裝飾器的返回值也是一個函數對象。
裝飾器函數的外部函數傳入我要裝飾的函數名字,返回經過修飾後函數的名字;內層函數(閉包)負責修飾被修飾函數。
④ 裝飾函數屬性:
實質: 是一個函數
參數:是你要裝飾的函數名(並非函數調用)
返回:是裝飾完的函數名(也非函數調用)
作用:為已經存在的對象添加額外的功能
特點:不需要對對象做任何的代碼上的變動
⑤ 作用及應用:裝飾函數最大的作用是對於已經寫好的程式,我們可以抽離出一些雷同的代碼組建多個特定功能的裝飾器,這樣就可以針對不同的需求去使用特定的裝飾器。
比如應用於插入日誌、性能測試、事務處理、許可權校驗等應用場景。
⑥ 示例:為函數添加計時功能:
示例1. 既不需要侵入,也不需要函數重覆執行
import time
def deco(func):
def wrapper():
startTime = time.time()
func()
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
return wrapper
@deco #相當於執行func=deco(func),為func函數裝飾函數並返回
def func():
print("hello")
time.sleep(1)
print("world")
if __name__ == '__main__':
f = func #這裡f被賦值為func,執行f()就是執行func()
f() #func是要裝飾器的函數,想用裝飾器顯示func函數運行的時間
#分析:裝飾器函數--decorator,該函數傳入參數是被裝飾函數(func),返回參數是內層函數即閉包函數(wrapper),起到裝飾給定函數的作用。其中作為參數的函數func()就在返回函數wrapper()的內部執行。然後在函數func()前面加上@decorator,func()函數相當於被註入了計時功能,現在只要調用func(),其就已經變為了“新功能更多”的函數。
#註意:Python中函數返回值為func和func()的區別:
使用return func返回的func這個函數;
而使用return func()是返回func()執行後的返回值,如果func()函數沒有返回值則返回值是None。
示例2: 帶有不定參數的裝飾器
import time
def deco(func):
def wrapper(*args, **kwargs):
startTime = time.time()
func(*args, **kwargs)
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
return wrapper
@deco
def func(a,b):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b))
@deco
def func2(a,b,c):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b+c))
if __name__ == '__main__':
f = func
func2(3,4,5)
f(3,4) #func()
示例3. 帶有不定參數的多個裝飾器
import time
def deco01(func):
def wrapper(*args, **kwargs):
print("this is deco01")
startTime = time.time()
func(*args, **kwargs)
endTime = time.time()
msecs = (endTime - startTime)*1000
print("time is %d ms" %msecs)
print("deco01 end here")
return wrapper
def deco02(func):
def wrapper(*args, **kwargs):
print("this is deco02")
func(*args, **kwargs)
print("deco02 end here")
return wrapper
@deco01
@deco02
def func(a,b):
print("hello,here is a func for add :")
time.sleep(1)
print("result is %d" %(a+b))
if __name__ == '__main__':
f = func
f(3,4)
#func()
運行結果:
this is deco01
this is deco02
hello,here is a func for add :
result is 7
deco02 end here
time is 1001 ms
deco01 end here #註意:多個裝飾器執行的順序就是從最後一個裝飾器開始,執行到第一個裝飾器,再執行函數本身