閉包
什麼是閉包
閉是封閉(函數中的函數),包是包含(該內部函數對外部函數作用域而非全局作用域變量的引用。)
閉包:
內部函數
對外部函數作用域
裏的變量
的引用- 函數內的屬性,都是有生命週期,都是在函數執行期間
- 閉包內的閉包函數私有化了變量,完成了數據的封裝,類似面向對象
demo:
def foo():
print("in foo()")
def bar():
print("in bar()")
# 1.直接運行內部函數報錯
# bar()
# 2.考慮先運行內部函數,在運行外部函數,依然會報錯
# foo()
# bar()
由於作用域的問題,函數內的屬性,都是有生命週期的,只有在函數執行期間
再考慮在這段代碼,只有調用foo()時,內部的print()及bar()才能存活。
現在爲了讓foo()內的bar()存活,就是調用bar(),我們該怎麼做?
[外鏈圖片轉存失敗(img-yOx9xCiJ-1564800430268)(file:///C:\Users\11980\AppData\Roaming\Tencent\Users\1198081282\QQ\WinTemp\RichOle`BOUPYX(C}M5AOKWH}0S~)]G.png)
把bar()函數返回給函數
def foo():
print("in foo()")
def bar():
print("in bar()")
return bar
var = foo()
var()
# in foo()
# in bar()
前面說,內部函數對外部函數作用域變量的引用, —> 如果是變量呢?
def foo():
a = 66
print("in foo()")
def bar(num):
print("in bar()")
print(a + num)
return bar
var = foo()
var(22)
# in foo()
# in bar()
# 88
'''閉包實現棋子移動'''
def outer():
# 在外部函數中定義一個保存座標的列表
position = [0, 0]
# 定義一個內部函數,參數爲移動方式和步長
# 移動方式爲列表[x, y] x,y分別只能去 -1,0,1三個值,表示反向,不動,正向
def inner(direction, step):
# 計算座標值
position[0] = position[0] + direction[0] * step
position[1] = position[1] + direction[1] * step
# 返回移動後的座標
return position
# 返回內部函數
return inner
# 獲取內部函數
move = outer()
# 移動
print(move([1, 0], 10))
print(move([0, 1], 10))
print(move([-1, 0], 10))
裝飾器
首先,看一個demo:
@func1
def func():
print('aaa')
裝飾器存在的意義
- 不影響原有函數的功能
- 可以添加新功能
一般常見的,比如拿到第三方的API接口,第三方不允許修改這個接口。這個時候,裝飾器就派上用場了。
裝飾器本身也是一個函數,作用是爲現有存在的函數,在不改變函數的基礎上,增加一些功能進行裝飾。
它是以閉包的形式去實現的。
在使用裝飾器函數時,在被裝飾的函數的前一行,使用@裝飾器函數名
形式來進行裝飾。
Demo:
現在在一個項目中,有很多函數,由於我們的項目越來越大,功能也越來越多,導致程序越來越慢。
其中一個功能函數的功能,是實現一百萬次的累加。
def my_count():
s = 0
for i in range(1000001):
s += i
print("sum:", s)
假如我們要計算函數的運行時間
import time
start = time.time()
my_count()
end = time.time()
print("執行時間爲:", (end-start))
# sum: 500000500000
# 執行時間爲: 0.05837249755859375
成果的實現時間的計算。But,假如有成千上萬個函數。每個函數寫一遍,非常麻煩,代碼量也憑空增加很多。
考慮將上述代碼中的時間計算封裝爲函數,如下
import time
def my_count():
s = 0
for i in range(1000001):
s += i
print("sum:", s)
def count_time(func):
start = time.time()
func()
end = time.time()
print("執行時間爲:", (end - start))
count_time(my_count)
# sum: 500000500000
# 執行時間爲: 0.057378530502319336
經過修改後,定義一個函數來實現時間計算功能。
初看上去,比之前好很多。
只是說在使用的時候,需要將對應的函數傳入到時間計算函數中去。
但任然影響了原來的使用。
接下來思考,能不能在使用時不影響函數原來的使用方式,同時實現時間的計算?
考慮閉包
import time
def count_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print("執行時間爲:", (end - start))
return wrapper()
def my_count():
s = 0
for i in range(1000001):
s += i
print("sum:", s)
count_time(my_count) # 仍然改變了調用方式
# sum: 500000500000
# 執行時間爲: 0.057892560958862305
在使用時,讓my_count函數重新指向count_time函數返回的函數引用。
import time
def count_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print("執行時間爲:", (end - start))
return wrapper
@count_time
def my_count():
s = 0
for i in range(1000001):
s += i
print("sum:", s)
my_count()
# sum: 500000500000
# 執行時間爲: 0.07386374473571777
這樣實現的好處,定義閉包函數後,只需要通過@裝飾器函數名
形式的裝飾器語法,就可以將@裝飾器函數名
加到要裝飾的函數前即可。
這種不改變原有函數功能,對函數進行拓展的形式,就稱爲裝飾器
再執行@裝飾器函數名
時,就是將原函數傳遞到閉包中。然後,原函數的引用指向閉包返回的裝飾過的內部函數的引用。
裝飾器的幾種形式
1.無參數無返回值
def setfunc(func):
def wrapper():
print("start")
func()
print("end")
return wrapper
@setfunc
def show():
print("show")
show()
# start
# show
# end
2.無參有返回值
def setfunc(func):
def wrapper():
print("start")
func()
print("end")
return wrapper
@setfunc
def show():
return "show"
print(show()) # show指向wrapper,show雖然有返回值,但wrapper並沒有返回值,所以輸出None
# start
# end
# None
3.有參無返回值
def setfunc(func):
def wrapper(s):
print("start")
func(s)
print("end")
return wrapper
@setfunc
def show(s):
print("hello %s" % s)
show("zucc")
# start
# hello zucc
# end
4.有參有返回值
def setfunc(func):
def wrapper(x, y):
print("start")
return func(x, y)
print("end") # 遇到return,該語句不執行
return wrapper
@setfunc
def myAdd(x, y):
return x + y
print(myAdd(2, 3))
# start
# 5
萬能裝飾器
根據被裝飾函數的定義不同,分出了四種形式。
能不能實現一種,適用於任何形式函數定義的裝飾器?
通過可變參數(*args/**kwargs)來接收不同的參數類型。
def setFunc(func):
def wrapper(*args, **kwargs):
print("Wrapper context.")
return func(*args, **kwargs)
return wrapper
@setFunc
def func(name, age, job='IT'):
print(name, age, job)
func("liyue", 21, "student")
# Wrapper context.
# liyue 21 student
@setFunc
def demo(a, b, *c, **d):
print((a, b))
print(c)
print(d)
demo('city', 'college', 1999, 1, 1, school= 'zucc')
# Wrapper context.
# ('city', 'college')
# (1999, 1, 1)
# {'school': 'zucc'}
函數被多個裝飾器所裝飾
一個函數在使用時,通過一個裝飾器來拓展,可能並不能達到預期。
一個函數被多個裝飾器所裝飾。
def setFunc1(func):
def wrapper1(*args, **kwargs):
print("Wrapper Context 1 Start".center(40, '*'))
func(*args, **kwargs)
print("Wrapper Context 1 End".center(40, '*'))
return wrapper1
def setFunc2(func):
def wrapper2(*args, **kwargs):
print("Wrapper Context 2 Start".center(40, '*'))
func(*args, **kwargs)
print("Wrapper Context 2 End".center(40, '*'))
return wrapper2
@setFunc1
@setFunc2
def show(*args, **kwargs):
print("show run")
show()
# ********Wrapper Context 1 Start*********
# ********Wrapper Context 2 Start*********
# show run
# *********Wrapper Context 2 End**********
# *********Wrapper Context 1 End**********
類裝飾器
首先,__call__
方法。
引子
def func():
print("Hello")
func()
print(func())
# 調用 ---> call
對應的,類實例對象的調用,需要使用到__call__
特殊方法
class Student:
def __init__(self, name):
self.name = name
def __call__(self, classmate):
print("我的名字是%s,我的同桌名字是%s" % (self.name, classmate))
stu = Student("樑子恢")
stu("楊琪")
用類實現裝飾器
通過__init__
和__call__
方法實現
import time
class Test:
def __init__(self, func):
print("裝飾器")
self.__func = func
def __call__(self, *args, **kwargs):
print("Start")
start_time = time.time()
self.__func(*args, **kwargs)
time.sleep(1)
end_time = time.time()
print(end_time - start_time)
@Test
def show():
print("Hello")
print("flag")
show()
# 裝飾器
# flag
# Start
# Hello
# 1.0004355907440186
總結
1.函數可以像普通變量一樣作爲函數的參數或者返回值進行傳遞。
2.函數的內部可以去定義另外一個函數。目的,隱藏函數功能的實現。
3.閉包實際上也是函數定義的一種形式。
4.閉包定義的規則,在外部函數內定義一個內部函數,內部函數使用外部函數的變量,並返回內部函數的引用。
5.Python中,裝飾器就是用閉包來實現的。
6.裝飾器的作用,不改變現有函數的基礎上,爲函數增加功能。
7.裝飾器的使用,通過@裝飾器函數名
的形式,來給已有函數進行裝飾,添加功能。
8.裝飾器四種形式,根據參數的不同以及返回值的不同。
9.萬能裝飾器,通過可變參數(*args/**kwargs)來實現。
10.一個裝飾器可以爲多個函數提供裝飾功能。
11.一個函數也可以被多個裝飾器所裝飾。
12.通過類實現裝飾器,重寫__init__
和__call__
函數。