python高阶函数闭包装饰器_Python自学从入门到就业之高阶函数、嵌套函数、闭包、装饰器...
高階函數(shù)
在Python中,函數(shù)其實也是一種數(shù)據(jù)類型。
def test():
return 'hello world'
print(type(test)) #
函數(shù)對應的數(shù)據(jù)類型是 function,可以把它當做是一種復雜的數(shù)據(jù)類型。
既然同樣都是一種數(shù)據(jù)類型,我們就可以把它當做數(shù)字或者字符串來處理。
定義一個變量指向函數(shù)
在Python中,我們還可以定義一個變量,讓它來指向一個函數(shù),相當于給函數(shù)起了一個別名。
def test():
return 'hello wrold'
fun = test # 定義了一個變量fun,讓它指向了 test 這個函數(shù)
print(fun()) # 使用fun()可以直接調(diào)用test這個函數(shù)
print(id(fun)) # 1819677672040
print(id(test)) # 1819677672040注意:在定義一個變量表示一個函數(shù)時,函數(shù)后面不能加括號!加括號表示的是調(diào)用這個函數(shù)。
def test():
return 'hello world'
result = test() # 這種寫法是調(diào)用test函數(shù),并把函數(shù)的返回值賦值給result變量
print(result()) # 這里會報錯 TypeError: 'str' object is not callable
fun = test # 這種寫法是給test函數(shù)起了一個別名,注意,這里的test后面不能加()
fun() # 可以使用別名調(diào)用這個函數(shù)
高階函數(shù)
既然變量可以指向函數(shù),函數(shù)的參數(shù)能接收變量,那么一個函數(shù)就可以接收另一個函數(shù)作為參數(shù),同樣,我們還可以把一個函數(shù)當做另一個函數(shù)的返回值。這種函數(shù)的使用方式我們稱之為高階函數(shù)。
函數(shù)做為另一個函數(shù)的參數(shù)
def test(age,action):
if age < 18:
print('您還沒滿十八歲,請退出')
action() # 把參數(shù)action直接當做一個函數(shù)來調(diào)用
def smoke():
print('我已經(jīng)年滿十八歲了,我想抽煙')
my_action = smoke # 定義一個變量my_action,讓它指向smoke函數(shù)
test(21, my_action) # 將my_action傳給 test 函數(shù)作為它的參數(shù)
test(21,smoke) # 還可以不再定義一個新的變量,直接傳入函數(shù)名
函數(shù)作為另一個函數(shù)的返回值
def test():
print('我是test函數(shù)里輸入的內(nèi)容')
def demo():
print('我是demo里輸入的內(nèi)容')
return test # test 函數(shù)作為demo函數(shù)的返回值
result = demo() # 我是demo里輸入的內(nèi)容 調(diào)用 demo 函數(shù),把demo函數(shù)的返回值賦值給 result
print(type(result)) # result 的類型是一個函數(shù)
result() # 我是demo里輸入的內(nèi)容 我是test函數(shù)里輸入的內(nèi)容 既然result是一個函數(shù),那么就可以直接使用() 調(diào)用這個函數(shù)
demo()() # 我是demo里輸入的內(nèi)容 我是test函數(shù)里輸入的內(nèi)容
函數(shù)嵌套
在函數(shù)里面還可以定義函數(shù),可以嵌套多層,執(zhí)行需要被調(diào)用。
def outer():
print('outer----hello')
def inner(): # inner這個函數(shù)是在outer函數(shù)內(nèi)部定義的
print('inner----hello')
inner() # inner函數(shù)只在outer函數(shù)內(nèi)部可見
outer()
# inner() 這里會報錯,在outer函數(shù)外部無法訪問到inner函數(shù)
閉包
函數(shù)只是一段可執(zhí)行代碼,編譯后就“固化”了,每個函數(shù)在內(nèi)存中只有一份實例,得到函數(shù)的入口點便可以執(zhí)行函數(shù)了。函數(shù)可以作為另一個函數(shù)的參數(shù)甚至返回值(高階函數(shù))。函數(shù)還可以嵌套定義,即在一個函數(shù)內(nèi)部可以定義另一個函數(shù),有了嵌套函數(shù)這種結(jié)構(gòu),便會產(chǎn)生閉包問題。
什么是閉包
閉包是由函數(shù)及其相關的引用環(huán)境組合而成的實體(即:閉包=函數(shù)塊+引用環(huán)境)。
def outer(n):
num = n
def inner():
return num+1
return inner
print(outer(3)()) # 4
print(outer(5)()) # 5
在這段程序中,函數(shù) inner 是函數(shù) outer 的內(nèi)嵌函數(shù),并且 inner 函數(shù)是outer函數(shù)的返回值。我們注意到一個問題:內(nèi)嵌函數(shù) inner 中引用到外層函數(shù)中的局部變量num,Python解釋器會這么處理這個問題呢? 先讓我們來看看這段代碼的運行結(jié)果,當我們調(diào)用分別由不同的參數(shù)調(diào)用 outer 函數(shù)得到的函數(shù)時,得到的結(jié)果是隔離的(相互不影響),也就是說每次調(diào)用outer函數(shù)后都將生成并保存一個新的局部變量num,這里outer函數(shù)返回的就是閉包。 如果在一個內(nèi)部函數(shù)里,對在外部作用域(但不是在全局作用域)的變量進行引用,那么內(nèi)部函數(shù)就被認為是閉包(closure).
修改外部變量的值
閉包里默認不能修改外部變量。
def outer(n):
num = n
def inner():
num = num + 1
return num
return inner
print(outer(1)())
上述代碼運行時會報錯!
UnboundLocalError: local variable 'num' referenced before assignment
原因分析
在python里,只要看到了賦值語句,就會認為賦值語句的左邊是一個局部變量。num = num + 1 這段代碼里,num 在=的左邊,python解析器會認為我們要修改inner函數(shù)里num這個局部變量,而這個變量使用之前是未聲明的,所以會報錯。
解決方案
我們分析過,報錯的原因在于當我們在閉包內(nèi)修改外部變量時,會被python解析器誤會為內(nèi)部函數(shù)的局部變量。所以,解決方案就在于,我們需要想辦法,讓解析器知道我們不是要修改局部變量,而是要修改外部變量。解決方法一:使用列表解決
def outer(n):
num = [n] # 定義一個變量num,將 n 包裹到一個列表里
def inner():
num[0] = num[0] + 1 # 從列表里取出并修改數(shù)據(jù)
return num[0]
return inner
print(outer(1)())解決方法二:python3后使用 nonlocal 關鍵字
def outer(n):
num = n
def inner():
nonlocal num # 修改前使用nonlocal關鍵字對 num 變量進行說明
num = num + 1
return num
return inner
print(outer(2)())
裝飾器
裝飾器是程序開發(fā)中經(jīng)常會用到的一個功能,用好了裝飾器,開發(fā)效率如虎添翼,所以這也是Python面試中必問的問題。但對于好多初次接觸這個知識的人來講,這個功能有點繞,自學時直接繞過去了,然后面試問到了就掛了,因為裝飾器是程序開發(fā)的基礎知識,這個都不會,別跟人家說你會Python, 看了下面的文章,保證你學會裝飾器。
1、先明白這段代碼
#### 第一波 ####
def foo():
print('foo')
foo # 表示是函數(shù)
foo() # 表示執(zhí)行foo函數(shù)
#### 第二波 ####
def foo():
print('foo')
foo = lambda x: x + 1
foo() # 執(zhí)行l(wèi)ambda表達式,而不再是原來的foo函數(shù),因為foo這個名字被重新指向了另外一個匿名函數(shù)
函數(shù)名僅僅是個變量,只不過指向了定義的函數(shù)而已,所以才能通過 函數(shù)名()調(diào)用,如果 函數(shù)名=xxx被修改了,那么當在執(zhí)行 函數(shù)名()時,調(diào)用的就不知之前的那個函數(shù)了
2、需求來了
初創(chuàng)公司有N個業(yè)務部門,基礎平臺部門負責提供底層的功能,如:數(shù)據(jù)庫操作、redis調(diào)用、監(jiān)控API等功能。業(yè)務部門使用基礎功能時,只需調(diào)用基礎平臺提供的功能即可。如下:
############### 基礎平臺提供的功能如下 ###############
def f1():
print('f1')
def f2():
print('f2')
def f3():
print('f3')
def f4():
print('f4')
############### 業(yè)務部門A 調(diào)用基礎平臺提供的功能 ###############
f1()
f2()
f3()
f4()
############### 業(yè)務部門B 調(diào)用基礎平臺提供的功能 ###############
f1()
f2()
f3()
f4()
目前公司有條不紊的進行著,但是,以前基礎平臺的開發(fā)人員在寫代碼時候沒有關注驗證相關的問題,即:基礎平臺的提供的功能可以被任何人使用。現(xiàn)在需要對基礎平臺的所有功能進行重構(gòu),為平臺提供的所有功能添加驗證機制,即:執(zhí)行功能前,先進行驗證。
老大把工作交給 Low B,他是這么做的:跟每個業(yè)務部門交涉,每個業(yè)務部門自己寫代碼,調(diào)用基礎平臺的功能之前先驗證。誒,這樣一來基礎平臺就不需要做任何修改了。太棒了,有充足的時間泡妹子...
當天Low B 被開除了…
老大把工作交給 Low BB,他是這么做的:
############### 基礎平臺提供的功能如下 ###############
def f1():
# 驗證1
# 驗證2
# 驗證3
print('f1')
def f2():
# 驗證1
# 驗證2
# 驗證3
print('f2')
def f3():
# 驗證1
# 驗證2
# 驗證3
print('f3')
def f4():
# 驗證1
# 驗證2
# 驗證3
print('f4')
############### 業(yè)務部門不變 ###############
### 業(yè)務部門A 調(diào)用基礎平臺提供的功能###
f1()
f2()
f3()
f4()
### 業(yè)務部門B 調(diào)用基礎平臺提供的功能 ###
f1()
f2()
f3()
f4()
過了一周 Low BB 被開除了…
老大把工作交給 Low BBB,他是這么做的:只對基礎平臺的代碼進行重構(gòu),其他業(yè)務部門無需做任何修改
############### 基礎平臺提供的功能如下 ###############
def check_login():
# 驗證1
# 驗證2
# 驗證3
pass
def f1():
check_login()
print('f1')
def f2():
check_login()
print('f2')
def f3():
check_login()
print('f3')
def f4():
check_login()
print('f4')
老大看了下Low BBB 的實現(xiàn),嘴角漏出了一絲的欣慰的笑,語重心長的跟Low BBB聊了個天:
老大說:
寫代碼要遵循開放封閉原則,雖然在這個原則是用的面向?qū)ο箝_發(fā),但是也適用于函數(shù)式編程,簡單來說,它規(guī)定已經(jīng)實現(xiàn)的功能代碼不允許被修改,但可以被擴展,即:封閉:已實現(xiàn)的功能代碼塊
開放:對擴展開發(fā)
如果將開放封閉原則應用在上述需求中,那么就不允許在函數(shù) f1 、f2、f3、f4的內(nèi)部進行修改代碼,老板就給了Low BBB一個實現(xiàn)方案:
def w1(func):
def inner():
# 驗證1
# 驗證2
# 驗證3
func()
return inner
@w1
def f1():
print('f1')
@w1
def f2():
print('f2')
@w1
def f3():
print('f3')
@w1
def f4():
print('f4')
對于上述代碼,也是僅僅對基礎平臺的代碼進行修改,就可以實現(xiàn)在其他人調(diào)用函數(shù) f1 f2 f3 f4 之前都進行【驗證】操作,并且其他業(yè)務部門無需做任何操作。
Low BBB心驚膽戰(zhàn)的問了下,這段代碼的內(nèi)部執(zhí)行原理是什么呢?
老大正要生氣,突然Low BBB的手機掉到地上,恰巧屏保就是Low BBB的女友照片,老大一看一緊一抖,喜笑顏開,決定和Low BBB交個好朋友。
詳細的開始講解了:
單獨以f1為例:
def w1(func):
def inner():
# 驗證1
# 驗證2
# 驗證3
func()
return inner
@w1
def f1():
print('f1')
python解釋器就會從上到下解釋代碼,步驟如下:def w1(func): ==>將w1函數(shù)加載到內(nèi)存
@w1
沒錯, 從表面上看解釋器僅僅會解釋這兩句代碼,因為函數(shù)在 沒有被調(diào)用之前其內(nèi)部代碼不會被執(zhí)行。
從表面上看解釋器著實會執(zhí)行這兩句,但是 @w1 這一句代碼里卻有大文章, @函數(shù)名 是python的一種語法糖。
上例@w1內(nèi)部會執(zhí)行一下操作:
執(zhí)行w1函數(shù)執(zhí)行w1函數(shù) ,并將 @w1 下面的函數(shù)作為w1函數(shù)的參數(shù),即:@w1 等價于 w1(f1) 所以,內(nèi)部就會去執(zhí)行:
def inner():
#驗證 1
#驗證 2
#驗證 3
f1() # func是參數(shù),此時 func 等于 f1
return inner# 返回的 inner,inner代表的是函數(shù),非執(zhí)行函數(shù) ,其實就是將原來的 f1 函數(shù)塞進另外一個函數(shù)中
w1的返回值將執(zhí)行完的w1函數(shù)返回值 賦值 給@w1下面的函數(shù)的函數(shù)名f1 即將w1的返回值再重新賦值給 f1,即:
新f1 = def inner():
#驗證 1
#驗證 2
#驗證 3
原來f1()
return inner所以,以后業(yè)務部門想要執(zhí)行 f1 函數(shù)時,就會執(zhí)行 新f1 函數(shù),在新f1 函數(shù)內(nèi)部先執(zhí)行驗證,再執(zhí)行原來的f1函數(shù),然后將原來f1 函數(shù)的返回值返回給了業(yè)務調(diào)用者。
如此一來, 即執(zhí)行了驗證的功能,又執(zhí)行了原來f1函數(shù)的內(nèi)容,并將原f1函數(shù)返回值 返回給業(yè)務調(diào)用者。Low BBB 你明白了嗎?要是沒明白的話,我晚上去你家?guī)湍憬鉀Q吧!!!
3. 再議裝飾器
# 定義函數(shù):完成包裹數(shù)據(jù)
def makeBold(fn):
def wrapped():
return "" + fn() + ""
return wrapped
# 定義函數(shù):完成包裹數(shù)據(jù)
def makeItalic(fn):
def wrapped():
return "" + fn() + ""
return wrapped
@makeBold
def test1():
return "hello world-1"
@makeItalic
def test2():
return "hello world-2"
@makeBold
@makeItalic
def test3():
return "hello world-3"
print(test1())
print(test2())
print(test3())
運行結(jié)果:
hello world-1
hello world-2
hello world-3
4. 裝飾器(decorator)功能引入日志
函數(shù)執(zhí)行時間統(tǒng)計
執(zhí)行函數(shù)前預備處理
執(zhí)行函數(shù)后清理功能
權(quán)限校驗等場景
緩存
5. 裝飾器示例
例1:無參數(shù)的函數(shù)
def check_time(action):
def do_action():
action()
return do_action
@check_time
def go_to_bed():
print('去睡覺')
go_to_bed()
上面代碼理解裝飾器執(zhí)行行為可理解成
result = check_time(go_to_bed) # 把go_to_bed 當做參數(shù)傳入給 check_time函數(shù),再定義一個變量用來保存check_time的運行結(jié)果
result() # check_time 函數(shù)的返回值result是一個函數(shù), result()再調(diào)用這個函數(shù),讓它再調(diào)用go_to_bed函數(shù)
例2:被裝飾的函數(shù)有參數(shù)
def check_time(action):
def do_action(a,b):
action(a,b)
return do_action
@check_time
def go_to_bed(a,b):
print('{}去{}睡覺'.format(a,b))
go_to_bed("zhangsan","床上")
例3:被裝飾的函數(shù)有不定長參數(shù)
def test(cal):
def do_cal(*args,**kwargs):
cal(*args,**kwargs)
return do_cal
@test
def demo(*args):
sum = 0
for x in args:
sum +=x
print(sum)
demo(1, 2, 3, 4)
例4:裝飾器中的return
def test(cal):
def do_cal(*args,**kwargs):
return cal(*args,**kwargs) # 需要再這里寫return語句,表示調(diào)用函數(shù),獲取函數(shù)的返回值并返回
return do_cal
@test
def demo(a,b):
return a + b
print(demo(1, 2)) #3
總結(jié):一般情況下為了讓裝飾器更通用,可以有return
例5:裝飾器帶參數(shù)
def outer_check(time):
def check_time(action):
def do_action():
if time < 22:
return action()
else:
return '對不起,您不具有該權(quán)限'
return do_action
return check_time
@outer_check(23)
def play_game():
return '玩兒游戲'
print(play_game())
提高:使用裝飾器實現(xiàn)權(quán)限驗證
以下代碼不要求掌握,如果能看懂最好,如果能自己手動寫出來,那就太棒了!
def outer_check(base_permission):
def check_permission(action):
def do_action(my_permission):
if my_permission & base_permission:
return action(my_permission)
else:
return '對不起,您不具有該權(quán)限'
return do_action
return check_permission
READ_PERMISSION = 1
WRITE_PERMISSION = 2
EXECUTE_PERMISSION = 4
@outer_check(base_permission=READ_PERMISSION)
def read(my_permission):
return '讀取數(shù)據(jù)'
@outer_check(base_permission=WRITE_PERMISSION)
def write(my_permission):
return '寫入數(shù)據(jù)'
@outer_check(base_permission=EXECUTE_PERMISSION)
def execute(my_permission):
return '執(zhí)行程序'
print(read(5))
函數(shù)應用:名片管理系統(tǒng)
# 定一個列表,用來存儲所有的名片信息(每個名片是一個字典)
info_list = []
def print_menu():
print("---------------------------")
print(" 名片管理系統(tǒng) V1.0")
print(" 1:添加名片")
print(" 2:刪除名片")
print(" 3:修改名片")
print(" 4:查詢名片")
print(" 5:顯示所有名片")
print(" 6:退出系統(tǒng)")
print("---------------------------")
def add_new_info():
"""添加名片信息"""
new_name = input("請輸入姓名:")
new_tel = input("請輸入手機號:")
new_qq = input("請輸入QQ:")
for temp_info in info_list:
if temp_info['name'] == new_name:
print("此用戶名已經(jīng)被占用,請重新輸入")
return # 如果一個函數(shù)只有return就相當于讓函數(shù)結(jié)束,沒有返回值
# 定義一個字典,用來存儲用戶的名片信息(這是一個字典)
info = {}
# 向字典中添加數(shù)據(jù)
info["name"] = new_name
info["tel"] = new_tel
info["qq"] = new_qq
# 向列表中添加這個字典
info_list.append(info)
def del_info():
"""刪除名片信息"""
del_num = int(input("請輸入要刪除的序號:"))
if 0 <= del_num < len(info_list):
del_flag = input("你確定要刪除么?yes or no")
if del_flag == "yes":
del info_list[del_num]
else:
print("輸入序號有誤,請重新輸入")
def modify_info():
"""修改名片信息"""
modify_num = int(input("請輸入要修改的序號:"))
if 0 <= modify_num < len(info_list):
print("你要修改的信息是:")
print("name:%s, tel:%s, QQ:%s" % (info_list[modify_num]['name'],
info_list[modify_num]['tel'],info_list[modify_num]['qq']))
info_list[modify_num]['name'] = input("請輸入新的姓名:")
info_list[modify_num]['tel'] = input("請輸入新的手機號:")
info_list[modify_num]['qq'] = input("請輸入新QQ:")
else:
print("輸入序號有誤,請重新輸入")
def search_info():
"""查詢名片信息"""
search_name = input("請輸入要查詢的名片姓名:")
for temp_info in info_list:
if temp_info['name'] == search_name:
print("查詢到的信息如下:")
print("name:%s, tel:%s, QQ:%s" % (temp_info['name'],
temp_info['tel'], temp_info['qq']))
break
else:
print("沒有您要找的信息....")
def print_all_info():
"""遍歷名片信息"""
print("序號\t姓名\t\t手機號\t\tQQ")
i = 0
for temp in info_list:
# temp是一個字典
print("%d\t%s\t\t%s\t\t%s" % (i, temp['name'], temp['tel'], temp['qq']))
i += 1
def main():
"""用來控制整個流程"""
while True:
# 1. 打印功能
print_menu()
# 2. 獲取用戶的選擇
num = input("請輸入要進行的操作(數(shù)字)")
# 3. 根據(jù)用戶選擇,做相應的事情
if num == "1":
# 添加名片
add_new_info()
elif num == "2":
# 刪除名片
del_info()
elif num == "3":
# 修改名片
modify_info()
elif num == "4":
# 查詢名片
search_info()
elif num == "5":
# 遍歷所有的信息
print_all_info()
elif num == "6":
# 退出系統(tǒng)
exit_flag = input("親,你確定要退出么?~~~~(>_<)~~~~(yes or no) ")
if exit_flag == "yes":
break
else:
print("輸入有誤,請重新輸入......")
# 程序的開始
main()
總結(jié)
以上是生活随笔為你收集整理的python高阶函数闭包装饰器_Python自学从入门到就业之高阶函数、嵌套函数、闭包、装饰器...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python批量合并csv_如何在Pyt
- 下一篇: Hadoop 中zoo_数据分析中的Ex