本文通過理論+代碼的方式介紹了Python函數的相關知識:包括:函數的參數、參數傳遞方式、參數解包、返回值、文檔字符串、作用域、命名空間、遞歸、高階函數、匿名函數、閉包、裝飾器。在開發過程中或者看別人源碼的過程中,這些知識是非常有用和經常遇到的,因此掌握這些知識很重要。
系列文章:
【Python 基礎】一文補齊Python基礎知識
【趣學Python:B站四大惡人】一文掌握列表、元組、字典、集合
文章目錄
1. 函數
1.1 函數簡介(function)
- 函數也是一個對象(object)
- 對象是內存中專門用來存儲數據的一塊區域
fn
是函數對象 ;fn()
調用函數;例如:print
是函數對象print()
調用函數;- 函數可以用來保存一些可執行的代碼,並且可以在需要時,對這些語句進行多次的調用;
- 創建函數:
def 函數名([形參1,形參2,...形參n]) :
代碼塊
- 函數名必須要符號標識符的規範;
(可以包含字母、數字、下劃線、但是不能以數字開頭)
- 函數中保存的代碼不會立即執行,需要調用函數代碼纔會執行;
- 調用函數:
函數對象()
1.2 函數的參數
在定義函數時,可以在函數名後的()中定義數量不等的形參,多個形參之間使用,
隔開;
1.2.1 形參和實參
- 形參(形式參數),定義形參就相當於在函數內部聲明瞭變量,但是並不賦值;
- 實參(實際參數);如果函數定義時,指定了形參,那麼在調用函數時也必須傳遞實參;實參將會賦值給對應的形參,簡單來說,有幾個形參就得傳幾個實參。
# 定義函數時,指定形參
def fn(a, b) :
print(a,"+",b,"=",a + b)
# 調用函數時,傳遞實參
fn(10,20)
fn(123,456)
實參的傳遞方式:
def mul(a,b,c):
print(a*b*c)
- 位置參數;位置參數就是將對應位置的實參複製給對應位置的形參;第一個實參賦值給第一個形參,第二個實參賦值給第二個形參 ;
fn(1 , 2 , 3)
; - 關鍵字參數;關鍵字參數,可以不按照形參定義的順序去傳遞,而直接根據參數名去傳遞參數:
fn(b=1 , c=2 , a=3)
; - 位置參數和關鍵字參數可以混合使用;混合使用關鍵字和位置參數時,必須將位置參數寫到前面:
fn(1,c=30)
; - 函數在調用時,解析器不會檢查實參的類型;實參可以傳遞任意類型的對象;
- 如果形參執行的是一個對象,當我們通過形參去修改對象時,會影響到所有指向該對象的變量;
def fn4(a):
a[0] = 30
print('a =',a,id(a))
c = 10
c = [1,2,3]
fn4(c)
# 不改變對象
fn4(c.copy()) # or fn4(c[:])
1.2.2 不定長參數
# 定義一個函數,可以求任意個數字的和
def sum(*nums):
# 定義一個變量,來保存結果
result = 0
# 遍歷元組,並將元組中的數進行累加
for n in nums :
result += n
print(result)
- 在定義函數時,可以在形參前邊加上一個
*
,這樣這個形參將會獲取到所有的實參;它將會將所有的實參保存到一個元組中。 *a
會接受所有的位置實參,並且會將這些實參統一保存到一個元組中(裝包)
def fn(*a):
print("a =",a,type(a))
fn()
'''
輸出:
a = () <class 'tuple'>
'''
- 帶星號的形參只能有一個;帶星號的參數,可以和其他參數配合使用;第一個參數給a,第二個參數給b,剩下的都保存到c的元組中;
def fn2(a,b,*c):
print('a =',a)
print('b =',b)
print('c =',c)
fn2(1,2,3,4,5,6)
'''
輸出:
a = 1
b = 2
c = (3, 4, 5, 6)
'''
- 可變參數不是必須寫在最後,但是注意,帶
*
的參數後的所有參數,必須以關鍵字參數的形式傳遞;第一個參數給a,剩下的位置參數給b的元組,c必須使用關鍵字參數;
def fn3(a,*b,c):
print('a =',a)
print('b =',b)
print('c =',c)
fn3(1,2,3,4,5,c=6)
'''
輸出:
a = 1
b = (2, 3, 4, 5)
c = 6
'''
- 所有的位置參數都給a,b和c必須使用關鍵字參數;
def fn4(*a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
fn4(1,2,3,4,b=5,c=6)
'''
輸出:
a = (1, 2, 3, 4)
b = 5
c = 6
'''
- 如果在形參的開頭直接寫一個
*
,則要求所有的參數必須以關鍵字參數的形式傳遞;
def fn5(*,a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
fn5(a=4,b=5,c=6)
'''
輸出:
a = 4
b = 5
c = 6
'''
*
形參只能接收位置參數,而不能接收關鍵字參數;**
形參可以接收其他的關鍵字參數,它會將這些參數統一保存到一個字典中;字典的key就是參數的名字,字典的value就是參數的值;**
形參只能有一個,並且必須寫在所有參數的最後;
def fn6(b,c,**a) :
print('a =',a,type(a))
print('b =',b)
print('c =',c)
fn6(b=1,d=2,c=3,e=10,f=20)
'''
輸出:
a = {'d': 2, 'e': 10, 'f': 20} <class 'dict'>
b = 1
c = 3
'''
1.2.3 參數解包
- 傳遞實參時,也可以在序列類型的參數前添加星號,這樣他會自動將序列中的元素依次作爲參數傳遞;這裏要求序列中元素的個數必須和形參的個數的一致;
def fn7(a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
# 創建一個元組
t = (10,20,30)
fn7(t[0],t[1],t[2])
# 創建一個列表
s = [10,20,30]
fn7(*s)
# 創建一個字典
d = {'a':10,'b':20,'c':30}
fn7(**d)
'''
輸出:
a = 10
b = 20
c = 30
'''
1.2.3 返回值
- 返回值,返回值就是函數執行以後返回的結果;
- 可以通過 return 來指定函數的返回值;
- 可以之間使用函數的返回值,也可以通過一個變量來接收函數的返回值;
- return 後邊可以跟任意的對象,返回值甚至可以是一個函數;
def fn():
def fn2() :
print('hello')
return fn2 # 返回值也可以是一個函數
r = fn() # 這個函數的執行結果就是它的返回值
- 如果僅僅寫一個return 或者 不寫return,則相當於return None ;
break
用來退出當前循環;continue
用來跳過當次循環;return
用來結束函數;
def fn4() :
for i in range(5):
if i == 3 :
# break 用來退出當前循環
# continue 用來跳過當次循環
return # return 用來結束函數
print(i)
print('循環執行完畢!')
'''
break執行結果:0,1,2,循環執行完畢!
continue執行結果:0,1,2,4,循環執行完畢!
return執行結果:0,1,2
'''
def fn2() :
a = 10
return
- 在函數中,return後的代碼都不會執行,return 一旦執行函數自動結束;
參數解包:
def sum(*nums):
# 定義一個變量,來保存結果
result = 0
# 遍歷元組,並將元組中的數進行累加
for n in nums :
result += n
return result
r = sum(1,2,3)
print(r)
print(r + 4)
def fn5():
return 10
fn5
和 fn5()
的區別:
- fn5是函數對象,打印fn5實際是在打印函數對象;
- fn5()是在調用函數,打印fn5()實際上是在打印fn5()函數的返回值;
print(fn5)
輸出<function fn5 at 0x05771BB8>;
print(fn5())
輸出10;
1.3 文檔字符串(doc str)
-
help()
是Python中的內置函數; -
通過
help()
函數可以查詢python中的函數的用法; -
語法:
help(函數對象)
; -
help(print)
獲取print()函數的使用說明; -
文檔字符串(doc str);
-
在定義函數時,可以在函數內部編寫文檔字符串,文檔字符串就是函數的說明;
-
當我們編寫了文檔字符串時,就可以通過
help()
函數來查看函數的說明; -
文檔字符串非常簡單,其實直接在函數的第一行寫一個字符串就是文檔字符串;
-
-> int
表示函數返回值是int
類型;
def fn(a:int,b:bool,c:str='hello') -> int:
'''
這是一個文檔字符串的示例
函數的作用:。。。。。
函數的參數:
a,作用,類型,默認值。。。。
b,作用,類型,默認值。。。。
c,作用,類型,默認值。。。。
'''
return 10
help(fn)
1.4 作用域與命名空間
1.4.1 作用域
- 作用域(scope):指變量生效的區域;在Python中一共有兩種作用域;
全局作用域
- 全局作用域在程序執行時創建,在程序執行結束時銷燬;
- 所有函數以外的區域都是全局作用域;
- 在全局作用域中定義的變量,都屬於全局變量,全局變量可以在程序的任意位置被訪問;
函數作用域
- 函數作用域在函數調用時創建,在調用結束時銷燬;
- 函數每調用一次就會產生一個新的函數作用域;
- 在函數作用域中定義的變量,都是局部變量,它只能在函數內部被訪問;
變量的查找
- 當我們使用變量時,會優先在當前作用域中尋找該變量,如果有則使用;如果沒有則繼續去上一級作用域中尋找,如果有則使用,如果依然沒有則繼續去上一級作用域中尋找,以此類推,直到找到全局作用域,依然沒有找到,則會拋出異常
NameError: name 'a' is not defined
;
def fn2():
a = 10
def fn3():
print('fn3中:','a =',a)
fn3()
fn2()
'''
輸出:
10
'''
在函數中爲變量賦值時,默認都是爲局部變量賦值;如果希望在函數內部修改全局變量,則需要使用 global
關鍵字,來聲明變量;
def fn():
global a # 聲明在函數內部的使用a是全局變量,此時再去修改a時,就是在修改全局的a
a = 10 # 修改全局變量
print('函數內部:','a =',a)
fn()
print('函數外部:','a =',a)
1.4.2 命名空間
命名空間(namespace)
- 命名空間指的是變量存儲的位置,每一個變量都需要存儲到指定的命名空間當中;
- 每一個作用域都會有一個它對應的命名空間;
- 全局命名空間,用來保存全局變量。函數命名空間用來保存函數中的變量;
- 命名空間實際上就是一個字典,是一個專門用來存儲變量的字典;
locals()
用來獲取當前作用域的命名空間
- 如果在全局作用域中調用
locals()
則獲取全局命名空間;如果在函數作用域中調用locals()
則獲取函數命名空間;返回的是一個字典;scope = locals()
獲取當前命名空間; - 在函數中可以獲取全局命名空間,但在全局中無法獲取函數的命名空間;
def fn4():
a = 10
# scope = locals() # 在函數內部調用locals()會獲取到函數的命名空間
# scope['b'] = 20 # 可以通過scope來操作函數的命名空間,但是也是不建議這麼做
# globals() 函數可以用來在任意位置獲取全局命名空間
global_scope = globals()
# print(global_scope['a'])
global_scope['a'] = 30
# print(scope)
fn4()
1.5 遞歸
# 嘗試求10的階乘(10!)
創建一個變量保存結果
n = 10
for i in range(1,10):
n *= i
print(n)
創建一個函數,可以用來求任意數的階乘:
def factorial(n):
'''
該函數用來求任意數的階乘
參數:n 要求階乘的數字
'''
# 創建一個變量,來保存結果
result = n
for i in range(1,n):
result *= i
return result
遞歸式的函數
- 無窮遞歸,如果這個函數被調用,程序的內存會溢出,效果類似於死循環
def fn():
fn()
fn()
- 遞歸是解決問題的一種方式,和循環很像。它的整體思想是,將一個大問題分解爲一個個的小問題,直到問題無法分解時,再去解決問題。
遞歸式函數的兩個要件:
1.基線條件
- 問題可以被分解爲的最小問題,當滿足基線條件時,遞歸就不再執行了;
2.遞歸條件
- 將問題繼續分解的條件;
使用遞歸重寫計算任意階乘的函數:
def factorial(n):
'''
該函數用來求任意數的階乘
參數:n 要求階乘的數字
'''
# 基線條件;判斷n是否爲1,如果爲1則此時不能再繼續遞歸
if n == 1 :
# 1的階乘就是1,直接返回1
return 1
# 遞歸條件
return n * factorial(n-1)
print(factorial(10))
- 遞歸和循環類似,基本是可以互相代替的;
- 循環編寫起來比較容易,閱讀起來稍難;遞歸編寫起來難,但是方便閱讀。
'''
練習
創建一個函數 power 來爲任意數字做冪運算 n ** i
10 ** 5 = 10 * 10 ** 4
10 ** 4 = 10 * 10 ** 3
...
10 ** 1 = 10
'''
def power(n , i):
'''
power()用來爲任意的數字做冪運算
參數:
n 要做冪運算的數字
i 做冪運算的次數
'''
# 基線條件
if i == 1:
# 求1次冪
return n
# 遞歸條件
return n * power(n , i-1)
print(power(8,6))
print(8**6)
創建一個函數,用來檢查一個任意的字符串是否是迴文字符串,如果是返回True,否則返回False。
'''
迴文字符串,字符串從前往後念和從後往前念是一樣的
abcba
abcdefgfedcba
先檢查第一個字符和最後一個字符是否一致,如果不一致則不是迴文字符串
如果一致,則看剩餘的部分是否是迴文字符串
檢查 abcdefgfedcba 是不是迴文
檢查 bcdefgfedcb 是不是迴文
檢查 cdefgfedc 是不是迴文
檢查 defgfed 是不是迴文
檢查 efgfe 是不是迴文
檢查 fgf 是不是迴文
檢查 g 是不是迴文
'''
def hui_wen(s):
'''
該函數用來檢查指定的字符串是否迴文字符串,如果是返回True,否則返回False
參數:
s:就是要檢查的字符串
'''
# 基線條件
if len(s) < 2 :
# 字符串的長度小於2,則字符串一定是迴文
return True
elif s[0] != s[-1]:
# 第一個字符和最後一個字符不相等,不是迴文字符串
return False
# 遞歸條件
return hui_wen(s[1:-1])
print(hui_wen('abcdefgfedcba'))
1.6 高階函數
- 接收函數作爲參數,或者將函數作爲返回值的函數是高階函數;
- 當使用一個函數作爲參數時,實際上是將指定的代碼傳遞進了目標函數;
# 創建一個列表
l = [1,2,3,4,5,6,7,8,9,10]
# 定義一個函數,用來檢查一個任意的數字是否是偶數
def fn2(i) :
if i % 2 == 0 :
return True
return False
# 這個函數用來檢查指定的數字是否大於5
def fn3(i):
if i > 5 :
return True
return False
def fn(func , lst) :
'''
fn()函數可以將指定列表中的所有偶數獲取出來,並保存到一個新列表中返回
參數:
lst:要進行篩選的列表
'''
# 創建一個新列表
new_list = []
# 對列表進行篩選
for n in lst :
# 判斷n的奇偶
if func(n) :
new_list.append(n)
# 返回新列表
return new_list
def fn4(i):
return i % 3 == 0
print(fn(fn4 , l))
1.6.1 filter()
filter()
可以從序列中過濾出符合條件的元素,保存到一個新的序列中
參數:
1.函數,根據該函數來過濾序列(可迭代的結構)
2.需要過濾的序列(可迭代的結構)
返回值:過濾後的新序列(可迭代的結構)
r = filter(fn4, 1)
print(r)
fn4
是作爲參數傳遞進filter()
函數中;fn4實際上只有一個作用,就是作爲filter()
的參數;filter()
調用完畢以後,fn4
就已經沒用;
1.6.2 lambda 函數表達式 (語法糖)
- lambda函數表達式專門用來創建一些簡單的函數,他是函數創建的又一種方式;
- 語法:
lambda 參數列表 : 返回值
; - 匿名函數一般都是作爲參數使用,其他地方一般不會使用;
r = filter(lambda i : i > 5 , l)
print(list(r))
1.6.3 map()
map()
函數可以對可跌倒對象中的所有元素做指定的操作,然後將其添加到一個新的對象中返回;
l = [1,2,3,4,5,6,7,8,9,10]
r = map(lambda i : i ** 2 , l)
print(list(r))
1.6.4 sort()
- 該方法用來對列表中的元素進行排序;
sort()
方法默認是直接比較列表中的元素的大小;
在sort()
可以接收一個關鍵字參數key=
,key=
需要一個函數作爲參數,當設置了函數作爲參數,每次都會以列表中的一個元素作爲參數來調用函數,並且使用函數的返回值來比較元素的大小。
l = ['bb','aaaa','c','ddddddddd','fff']
l.sort(key=len)
print(l)
# 輸出:['c', 'bb', 'fff', 'aaaa', 'ddddddddd']
l = [2,5,'1',3,'6','4']
l.sort(key=int)
print(l)
# 輸出:['1', 2, 3, '4', 5, '6']
1.6.5 sorted()
- 這個函數和
sort()
的用法基本一致,但是sorted()可以對任意的序列進行排序; - 並且使用sorted()排序不會影響原來的對象,而是返回一個新對象
l = [2,5,'1',3,'6','4']
print('排序前:',l)
print(sorted(l,key=int))
print('排序後:',l)
'''
輸出:
排序前: [2, 5, '1', 3, '6', '4']
['1', 2, 3, '4', 5, '6']
排序後: [2, 5, '1', 3, '6', '4']
'''
1.6.6 閉包
- 將函數作爲返回值返回,也是一種高階函數;這種高階函數我們也稱爲叫做閉包;
- 通過閉包可以創建一些只有當前函數能訪問的變量;也可以將一些私有的數據藏到的閉包中;
def fn():
a = 10
# 函數內部再定義一個函數
def inner():
print('我是fn2' , a)
# 將內部函數 inner作爲返回值返回
return inner
r = fn()
r
是一個函數,是調用fn()
後返回的函數;- 這個函數只在
fn()
內部定義,並不是全局函數; - 所以這個函數總是能訪問到
fn()
函數內的變量;
形成閉包的要件:
① 函數嵌套;
② 將內部函數作爲返回值返回;
③ 內部函數必須要使用到外部函數的變量;
def make_averager():
# 創建一個列表,用來保存數值
nums = []
# 創建一個函數,用來計算平均值
def averager(n) :
# 將n添加到列表中
nums.append(n)
# 求平均值
return sum(nums)/len(nums)
return averager
averager = make_averager()
print(averager(10)) # 輸出:10.0
print(averager(20)) # 輸出:15.0
print(averager(30)) # 輸出:20.0
print(averager(40)) # 輸出:25.0
1.7 裝飾器
# 創建幾個函數
def fn():
print('我是fn函數....')
def add(a , b):
'''
求任意兩個數的和
'''
r = a + b
return r
def mul(a , b):
'''
求任意兩個數的積
'''
r = a * b
return r
需求:希望函數可以在計算前,打印開始計算,計算結束後打印計算完畢。
我們可以直接通過修改函數中的代碼來完成這個需求,但是會產生以下一些問題:
1.如果要修改的函數過多,修改起來會比較麻煩;
2.並且不方便後期的維護;
3.並且這樣做會違反開閉原則(OCP):
程序的設計,要求開發對程序的擴展,要關閉對程序的修改;
爲了解決這個問題,我們創建一個函數,讓這個函數可以自動的幫助我們生產函數;
def begin_end(old):
'''
用來對其他函數進行擴展,使其他函數可以在執行前打印開始執行,執行後打印執行結束
參數:
old 要擴展的函數對象
'''
# 創建一個新函數
def new_function(*args , **kwargs):
print('開始執行~~~~')
# 調用被擴展的函數
result = old(*args , **kwargs)
print('執行結束~~~~')
# 返回函數的執行結果
return result
# 返回新函數
return new_function
*args
接收所有位置參數;**args
接收所有的關鍵字參數;- 函數定義
def new_function(*args , **kwargs)
中的兩個參數是把傳入的參數裝包成一個元組(*args
)或一個字典(**kwargs
); result = old(*args , **kwargs)
中的兩個參數,*args
將元組拆包成位置參數傳入函數,**kwargs
將字典拆包成關鍵字參數傳入函數。
調用:
f = begin_end(fn)
f2 = begin_end(add)
f3 = begin_end(mul)
r = f()
print('第一次輸出:\n',r)
r = f2(123,456)
print('第二次輸出:\n',r)
r = f3(123,456)
print('第三次輸出:\n',r)
'''
輸出:
開始執行~~~~
我是fn函數....
執行結束~~~~
第一次輸出:
None
開始執行~~~~
執行結束~~~~
第二次輸出:
579
開始執行~~~~
執行結束~~~~
第三次輸出:
56088
'''
像 begin_end()
這種函數我們就稱它爲裝飾器;
- 通過裝飾器,可以在不修改原來函數的情況下來對函數進行擴展;
- 在開發中,通過裝飾器來擴展函數的功能;
- 在定義函數時,可以通過
@裝飾器
,來使用指定的裝飾器,來裝飾當前的函數;
裝飾器的標準用法:
@begin_end
def say_hello():
print('大家好~~~')
say_hello()
'''
輸出:
大家好~~~
'''
- 可以同時爲一個函數指定多個裝飾器,這樣函數將會按照從內向外的順序被裝飾 ;
def fn3(old):
'''
用來對其他函數進行擴展,使其他函數可以在執行前打印開始執行,執行後打印執行結束
參數:
old 要擴展的函數對象
'''
# 創建一個新函數
def new_function(*args , **kwargs):
print('fn3裝飾~開始執行~~~~')
# 調用被擴展的函數
result = old(*args , **kwargs)
print('fn3裝飾~執行結束~~~~')
# 返回函數的執行結果
return result
# 返回新函數
return new_function
@fn3
@begin_end
def say_hello():
print('大家好~~~')
say_hello()
'''
輸出:
fn3裝飾~開始執行~~~~
開始執行~~~~
大家好~~~
執行結束~~~~
fn3裝飾~執行結束~~~~
'''
交換裝飾器執行順序
@begin_end
@fn3
def say_hello():
print('大家好~~~')
say_hello()
'''
輸出:
開始執行~~~~
fn3裝飾~開始執行~~~~
大家好~~~
fn3裝飾~執行結束~~~~
執行結束~~~~
'''