閉包的定義
將組成函數(shù)的語(yǔ)句和這些語(yǔ)句的執(zhí)行環(huán)境打包在一起時(shí),得到的對(duì)象稱為閉包
我們知道函數(shù)在Python中是第一類對(duì)象,也就是說(shuō)可以把它們當(dāng)作參數(shù)傳遞給其他函數(shù)芽腾,放在數(shù)據(jù)結(jié)構(gòu)中歉提,以及作為函數(shù)的返回結(jié)果。
我們定義一個(gè)函數(shù)狠持,它接受另一函數(shù)作為輸入并調(diào)用它
foo.py
def call(func):
return func
我們?cè)诹硪粋€(gè)程序中去調(diào)用這個(gè)函數(shù)
cal_close.py
from python_book import foo
def helloworld():
return 'hello world'
print(foo.call(helloworld))
我們把函數(shù)當(dāng)作數(shù)據(jù)處理時(shí)疟位,它將隱式地?cái)y帶與定義該函數(shù)的周圍環(huán)境相關(guān)的信息,這將影響到函數(shù)中自由變量的綁定方式喘垂。
我們?cè)趧偛诺睦踝又屑尤胍粋€(gè)變量的定義
foo.py
x = 42
def call(func):
return func
然后在調(diào)用函數(shù)的地方同樣也加入自己的函數(shù)定義
cal_close.py
from python_book import foo
x = 37
def helloworld():
return 'hello world is %d' % x
print(foo.call(helloworld))
>>> hello world is 37
在這個(gè)栗子中甜刻,函數(shù)helloworld()使用的x的值是在與他相同的環(huán)境中定義的,即使在foo.py中也定義了一個(gè)變量x正勒,而且這里也是實(shí)際調(diào)用helloworld()函數(shù)的地方得院,但x的值與helloworld()函數(shù)執(zhí)行時(shí)使用的x不同
事實(shí)上所有函數(shù)都擁有一個(gè)指向了定義該函數(shù)的全局命名空間的globals屬性,始終對(duì)應(yīng)于定義函數(shù)的閉包模塊章贞,閉包將捕捉內(nèi)部函數(shù)執(zhí)行所需的整個(gè)環(huán)境
我們可以看一個(gè)閉包函數(shù)的栗子
def countdown(n):
def next():
nonlocal n
r = n
n -= 1
return r
return next
# 使用
next = countdown(10)
while True:
v = next()
if not v: break
print(v)
在Python中祥绞,裝飾器是很重要的高級(jí)函數(shù)。要掌握裝飾器我們必須掌握相關(guān)的知識(shí)
函數(shù)作用域
LEGB原則
函數(shù)即對(duì)象
在Python中,函數(shù)和我們之前的數(shù)據(jù)類型等都一樣都是對(duì)象蜕径,而且函數(shù)是最高級(jí)的對(duì)象
在內(nèi)存中和對(duì)象一樣两踏,也是將函數(shù)的指針存儲(chǔ)在函數(shù)名中
既然函數(shù)和對(duì)象相同那么函數(shù)自然滿足對(duì)象的幾個(gè)條件
1.可以被賦值給其他變量
def foo():
print('foo')
bar=foo
bar()
foo()
print(id(foo),id(bar)) #4321123592 4321123592
函數(shù)的函數(shù)名其實(shí)就是函數(shù)的指針,可以將函數(shù)的函數(shù)名賦值給其他的變量
2.其可以被定義在另外一個(gè)函數(shù)內(nèi)(作為參數(shù)&作為返回值):
#*******函數(shù)名作為參數(shù)**********
def foo(func):
print('foo')
func()
def bar():
print('bar')
foo(bar)
#*******函數(shù)名作為返回值*********
def foo():
print('foo')
return bar
def bar():
print('bar')
b=foo()
b()
函數(shù)的嵌套及閉包
Python允許創(chuàng)建嵌套函數(shù)兜喻,通過在函數(shù)內(nèi)部使用def關(guān)鍵字聲明一個(gè)函數(shù)作為內(nèi)部函數(shù)
看下面一個(gè)例子:
#想執(zhí)行inner函數(shù),兩種方法
def outer():
x = 1
def inner():
print (x) # 1
# inner() # 2
return inner
# outer()
in_func=outer()
in_func()
作為調(diào)用內(nèi)部函數(shù)inner,有下面兩種方法
in_func=outer()
in_func()
###########
inner()(已經(jīng)加載到內(nèi)存啦)
如果直接在外部調(diào)用內(nèi)部函數(shù)inner就會(huì)報(bào)錯(cuò)梦染,原因就是這里找不到這個(gè)引用變量
但是這里就會(huì)有一個(gè)問題,在內(nèi)部函數(shù)被調(diào)用執(zhí)行的時(shí)候朴皆,它的外部函數(shù)也就是outer()已經(jīng)執(zhí)行完畢了弓坞,那么為什么inner還是可以調(diào)用聲明在外部outer函數(shù)中的變量x呢?
這里就涉及到我們說(shuō)的閉包的概念车荔,因?yàn)閛uter里return的inner是一個(gè)閉包函數(shù)渡冻,所以就會(huì)有這個(gè)x變量
我們也就可以拋出閉包的定義
如果在一個(gè)內(nèi)部函數(shù)中,對(duì)在外部作用域(且不是在全局作用域)的變量進(jìn)行引用忧便,那么內(nèi)部函數(shù)就被認(rèn)為是閉包族吻,內(nèi)部函數(shù)可以稱為閉包函數(shù)
在上面的例子中,inner就是內(nèi)部函數(shù)珠增,inner中引用了外部作用域的變量x(x在外部作用域outer超歌,不在模塊的全局作用域)。
所以這個(gè)inner函數(shù)就是閉包函數(shù)
如下函數(shù)蒂教,內(nèi)部閉包函數(shù)的作用是給外部的函數(shù)增加字符串參數(shù)
>>> def saying(something):
... def outsaying():
... return "is inner read params %s" %something
... return outsaying
...
>>> a = saying('hello')
>>> a()
'is inner read params hello'
上面這個(gè)函數(shù)中 outsaying作為內(nèi)部函數(shù)直接訪問外部的變量所以是一個(gè)閉包函數(shù)巍举,outsaying()函數(shù)可以得到參數(shù)something的值并且記錄下來(lái),return outsaying 這一行返回的是outsaying函數(shù)的一個(gè)復(fù)制(并沒有直接調(diào)用)
裝飾器概念
裝飾器本質(zhì)上來(lái)說(shuō)是一個(gè)函數(shù)凝垛,該函數(shù)用來(lái)處理其他函數(shù)懊悯,它可以讓其他函數(shù)在不需要修改代碼的前提下增加額外的功能,裝飾器的返回值也會(huì)一個(gè)函數(shù)對(duì)象梦皮。裝飾器可以被用于:插入日志炭分、性能測(cè)試、事物處理剑肯、緩存捧毛、權(quán)限校驗(yàn)等應(yīng)用場(chǎng)景,我們有了裝飾器我們就可以抽離大量與函數(shù)功能本身無(wú)關(guān)的雷同代碼并繼續(xù)重用让网。裝飾器的作用就是為已經(jīng)存在的對(duì)象添加額外的功能呀忧。
裝飾器定義
裝飾器是一個(gè)函數(shù),只要用途是包裝另一個(gè)函數(shù)或類溃睹。語(yǔ)法上使用特殊符號(hào)@表示裝飾器
使用裝飾器時(shí)而账,它們必須出現(xiàn)在函數(shù)或者類定義之前的單獨(dú)行上,可以同時(shí)使用多個(gè)裝飾器
···
@foo
@bar
@spam
def grok(x)
pass
···
上述代碼等同于
def grok(x):
pass
grok = foo(bar(spam(grok)))
裝飾器也可以接受參數(shù)
@eventhandler('a')
def handle_a(msg):
pass
@eventhandler('b')
def handle_b(msg):
pass
如果裝飾器提供參數(shù)丸凭,裝飾器的語(yǔ)義如下所示:
def handle_button(msg):
tmp = eventhandle('a')# 使用提供的參數(shù)調(diào)用裝飾器
handle_button = tmp(handle_button) # 調(diào)用裝飾器返回的函數(shù)
@trace
def square(x):
return x*x
等價(jià):
def square(x):
return x*x
square = trace(square)
現(xiàn)在在生產(chǎn)中有這樣的函數(shù)
def foo():
print('hello foo')
foo()
現(xiàn)在有一個(gè)新的需求福扬,希望可以記錄下函數(shù)的執(zhí)行時(shí)間腕铸,在這種需求下我們最好不要修改源代碼來(lái)實(shí)現(xiàn)這個(gè)功能,我們可以引入一個(gè)內(nèi)置函數(shù)铛碑,而且為了不影響結(jié)構(gòu)狠裹,我們還不應(yīng)該改變函數(shù)的調(diào)用方式
這里我們可以引入一個(gè)內(nèi)置函數(shù)
import time
def show_time(func):
def wrapper():
start_time=time.time()
func()
end_time=time.time()
print('spend %s'%(end_time-start_time))
return wrapper
def foo():
print('hello foo')
time.sleep(3)
foo=show_time(foo)
foo()
在上面這個(gè)例子中函數(shù)show_time就是裝飾器。它將真正的業(yè)務(wù)方法func包裹在函數(shù)里面汽烦。
@符號(hào)是裝飾器的語(yǔ)法糖涛菠,在定義函數(shù)的時(shí)候使用,避免多次的賦值操作
import time
def show_time(func):
def wrapper():
start_time=time.time()
func()
end_time=time.time()
print('spend %s'%(end_time-start_time))
return wrapper
@show_time #foo=show_time(foo)
def foo():
print('hello foo')
time.sleep(3)
@show_time #bar=show_time(bar)
def bar():
print('in the bar')
time.sleep(2)
foo()
print('***********')
bar()
這里:foo=show_time(foo)其實(shí)就是把內(nèi)置函數(shù)wrapper引用的對(duì)象引用給了foo,而wrapper里的變量func之所以可以使用撇吞,就是因?yàn)閣rapper是一個(gè)閉包函數(shù)
帶參數(shù)的被裝飾函數(shù)
import time
def show_time(func):
def wrapper(a,b):
start_time=time.time()
func(a,b)
end_time=time.time()
print('spend %s'%(end_time-start_time))
return wrapper
@show_time #add=show_time(add)
def add(a,b):
time.sleep(1)
print(a+b)
add(2,4)
- 不定長(zhǎng)參數(shù)
#***********************************不定長(zhǎng)參數(shù)
import time
def show_time(func):
def wrapper(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
end_time=time.time()
print('spend %s'%(end_time-start_time))
return wrapper
@show_time #add=show_time(add)
def add(*args,**kwargs):
time.sleep(1)
sum=0
for i in args:
sum+=i
print(sum)
add(2,4,8,9)
裝飾器帶參數(shù)
import time
def time_logger(flag=0):
def show_time(func):
def wrapper(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
end_time=time.time()
print('spend %s'%(end_time-start_time))
if flag:
print('將這個(gè)操作的時(shí)間記錄到日志中')
return wrapper
return show_time
@time_logger(3)
def add(*args,**kwargs):
time.sleep(1)
sum=0
for i in args:
sum+=i
print(sum)
add(2,7,5)
@time_logger(3) 做了兩件事:
- (1)time_logger(3):得到閉包函數(shù)show_time俗冻,里面保存環(huán)境變量flag
- (2)@show_time :add=show_time(add)
多層裝飾器
def makebold(fn):
def wrapper():
return "<b>" + fn() + "</b>"
return wrapper
def makeitalic(fn):
def wrapper():
return "<i>" + fn() + "</i>"
return wrapper
@makebold
@makeitalic
def hello():
return "hello alvin"
hello()
匿名函數(shù): lambda()函數(shù)
Python中,lambda函數(shù)是用一個(gè)語(yǔ)句表達(dá)的匿名函數(shù)牍颈,可以用它來(lái)代替小的函數(shù)迄薄。
# 定義一個(gè)函數(shù),參數(shù)接收一個(gè)列表煮岁,并遍歷
>>> def edit_story(words,func):
... for word in words:
... print(func(word))
...
>>> stairs = ['a','ab','abc','abcd']
>>> def add_str(word):
... return word+'!!!'
...
>>> edit_story(stairs,add_str)
a!!!
ab!!!
abc!!!
abcd!!!