@Author : Roger TX (425144880@qq.com)
@Link : https://github.com/paotong999
一赎败、Python 閉包
1潜慎、函數(shù)作為返回值
高階函數(shù)除了可以接受函數(shù)作為參數(shù)外,還可以把函數(shù)作為結(jié)果值返回子姜。
我們來實(shí)現(xiàn)一個(gè)可變參數(shù)的求和洗做。通常情況下,求和的函數(shù)是這樣定義的:
def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
但是址芯,如果不需要立刻求和灾茁,而是在后面的代碼中,根據(jù)需要再計(jì)算怎么辦谷炸?可以不返回求和的結(jié)果北专,而是返回求和的函數(shù):
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
當(dāng)我們調(diào)用 lazy_sum()
時(shí),返回的并不是求和結(jié)果旬陡,而是求和函數(shù)拓颓。
在這里,我們?cè)诤瘮?shù) lazy_sum
中又定義了函數(shù) sum
描孟,并且驶睦,內(nèi)部函數(shù) sum
可以引用外部函數(shù) lazy_sum
的參數(shù)和局部變量砰左,當(dāng) lazy_sum
返回函數(shù) sum
時(shí),相關(guān)參數(shù)和變量都保存在返回的函數(shù)中场航,這種稱為 閉包(Closure)
的程序結(jié)構(gòu)擁有極大的威力缠导。
2、閉包
在一個(gè)外函數(shù)中定義了一個(gè)內(nèi)函數(shù)溉痢,內(nèi)函數(shù)里運(yùn)用了外函數(shù)的臨時(shí)變量僻造,并且外函數(shù)的返回值是內(nèi)函數(shù)的引用。
一般情況下孩饼,在我們認(rèn)知當(dāng)中髓削,如果一個(gè)函數(shù)結(jié)束,函數(shù)的內(nèi)部所有東西都會(huì)釋放掉镀娶,還給內(nèi)存立膛,局部變量都會(huì)消失。但是閉包是一種特殊情況汽畴,如果外函數(shù)在結(jié)束的時(shí)候發(fā)現(xiàn)有自己的臨時(shí)變量將來會(huì)在內(nèi)部函數(shù)中用到旧巾,就把這個(gè)臨時(shí)變量綁定給了內(nèi)部函數(shù),然后自己再結(jié)束忍些。
閉包是由函數(shù)及其相關(guān)的引用環(huán)境組合而成的實(shí)體(即:閉包=函數(shù)+引用環(huán)境)
函數(shù):
- 函數(shù)只是一段可執(zhí)行代碼鲁猩,編譯后“固化”,每個(gè)函數(shù)在內(nèi)存中只有一份實(shí)例罢坝,得到函數(shù)的入口點(diǎn)便可以執(zhí)行函數(shù)廓握。
如果函數(shù)名后緊跟一對(duì)括號(hào),相當(dāng)于現(xiàn)在我就要調(diào)用這個(gè)函數(shù)
如果函數(shù)名后不跟括號(hào)嘁酿,相當(dāng)于只是一個(gè)函數(shù)的名字隙券,里面存了函數(shù)所在位置的引用
- 在函數(shù)式編程語言中,函數(shù)是一等公民闹司,函數(shù)可以作為另一個(gè)函數(shù)的參數(shù)或返回值娱仔,可以賦給一個(gè)變量。
(First class value:第一類對(duì)象游桩,我們不需要像命令式語言中那樣借助函數(shù)指針牲迫,委托操作函數(shù))
- 函數(shù)可以嵌套定義,即在一個(gè)函數(shù)內(nèi)部可以定義另一個(gè)函數(shù)借卧,有了嵌套函數(shù)這種結(jié)構(gòu)盹憎,便會(huì)產(chǎn)生閉包問題。
引用環(huán)境:
- 當(dāng)內(nèi)嵌函數(shù)體內(nèi)引用到體外的變量時(shí)铐刘,將會(huì)把定義時(shí)涉及到的引用環(huán)境和函數(shù)體打包成一個(gè)整體返回
注意事項(xiàng):
- 閉包中無法修改外部作用域的局部變量
def foo():
m = 0
def foo1():
m = 1
print (m) # m = 1
print (m) # m = 0
foo1()
print (m) # m = 0
foo()
- 在閉包中陪每,所有在賦值語句左面的變量都是局部變量
def foo():
a = 1
def bar():
a = a + 1
return a
return bar
運(yùn)行時(shí),會(huì)報(bào)錯(cuò)
UnboundLocalError: local variable 'a' referenced before assignment
,變量a在賦值語句左邊被認(rèn)為是局部變量檩禾,會(huì)在bar()
中去找在賦值語句右面的a的值挂签,而找到上層的變量a是不允許在局部內(nèi)被修改
- 修改賦值和返回,將a改為b
- 將a設(shè)定為一個(gè)容器盼产,
a = [1]
竹握,a[0] = a[0] + 1
- 在
a = a + 1
之前,使用語句nonloacal a就可以指定a不是閉包的局部變量辆飘,需要向上一層變量空間找這個(gè)變量。
- 返回閉包時(shí)谓传,返回函數(shù)不要引用任何循環(huán)變量蜈项,或者后續(xù)會(huì)發(fā)生變化的變量
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count() # 調(diào)用f1(),f2()和f3()結(jié)果應(yīng)該是1续挟,4紧卒,9,但實(shí)際結(jié)果是9诗祸,9跑芳,9
閉包的應(yīng)用:
- 坐標(biāo)連續(xù)移動(dòng)
origin = [0, 0] # 坐標(biāo)系統(tǒng)原點(diǎn)
legal_x = [0, 50] # x軸方向的合法坐標(biāo)
legal_y = [0, 50] # y軸方向的合法坐標(biāo)
def create(pos=origin):
def player(direction,step):
# 這里應(yīng)該首先判斷參數(shù)direction,step的合法性,比如direction不能斜著走直颅,step不能為負(fù)等
# 然后還要對(duì)新生成的x博个,y坐標(biāo)的合法性進(jìn)行判斷處理,這里主要是想介紹閉包功偿,就不詳細(xì)寫了
new_x = pos[0] + direction[0]*step
new_y = pos[1] + direction[1]*step
pos[0] = new_x
pos[1] = new_y
#注意盆佣!此處不能寫成 pos = [new_x, new_y],原因在上文有說過
return pos
return player
player = create() # 創(chuàng)建棋子player械荷,起點(diǎn)為原點(diǎn)
print (player([1,0],10)) # 向x軸正方向移動(dòng)10步共耍,結(jié)果為[10, 0]
print (player([0,1],20)) # 向y軸正方向移動(dòng)20步,結(jié)果為[10, 20]
print (player([-1,0],10)) # 向x軸負(fù)方向移動(dòng)10步吨瞎,結(jié)果為[0, 20]
- 取得文件"result.txt"中含有"pass"關(guān)鍵字的行
def make_filter(keep):
def the_filter(file_name):
file = open(file_name)
lines = file.readlines()
file.close()
filter_doc = [i for i in lines if keep in i]
return filter_doc
return the_filter
filter = make_filter("yield")
filter_result = filter("example.py")
print(filter_result)
- 裝飾器
python中的使用@語法實(shí)現(xiàn)的單例模式就是利用閉包實(shí)現(xiàn)的痹兜,只不過用了@作為語法糖,使寫法更簡潔颤诀,閉包函數(shù)將函數(shù)的唯一實(shí)例保存在它內(nèi)部的__closure__
屬性中字旭,在再次創(chuàng)建函數(shù)實(shí)例時(shí),閉包檢查該函數(shù)實(shí)例已存在自己的屬性中着绊,不會(huì)再讓他創(chuàng)建新的實(shí)例谐算,而是將現(xiàn)有的實(shí)例返給它。
__closure__
屬性返回的是一個(gè)元組對(duì)象归露,包含了閉包引用的外部變量洲脂。
- 若主函數(shù)內(nèi)的閉包不引用外部變量,就不存在閉包,主函數(shù)的
_closure__
屬性永遠(yuǎn)為None- 若主函數(shù)沒有return子函數(shù)恐锦,就不存在閉包往果,主函數(shù)不存在
_closure__
屬性,拋出異常
二一铅、Python 裝飾器
裝飾器分為:不帶參數(shù)的裝飾器 和 帶參數(shù)的裝飾器
本質(zhì)上陕贮,decorator就是一個(gè)返回函數(shù)的高階函數(shù),實(shí)現(xiàn)了以下兩步
- 將被修飾的函數(shù)(函數(shù) B)作為參數(shù)傳給 @ 符號(hào)引用的函數(shù)(函數(shù) A)
- 將函數(shù) B 替換(裝飾)成第 1 步的返回值
1潘飘、不帶參數(shù)的裝飾器
@a_decorator
def f(...):
經(jīng)過a_decorator后肮之, 函數(shù)f就相當(dāng)于以f為參數(shù)調(diào)用a_decorator返回結(jié)果
f = a_decorator(f)
def makeBold(fn):
print("makeBold"*2)
def wrapped1(): #注意為了演示結(jié)果這里講wrapped函數(shù),分為wrapped1,wrapped2
print("makeBold"*3)
print("asd" + fn() + "asd")
return wrapped1
def makeItalic(fn):
print("makeItalic"*2)
def wrapped2(): #注意為了演示結(jié)果這里講wrapped函數(shù)卜录,分為wrapped1,wrapped2
print("makeItalic" *3)
return "zzz" + fn() + "zzz"
return wrapped2
#使用兩個(gè)裝飾器同時(shí)裝飾一個(gè)函數(shù)戈擒,可以三個(gè),甚至多個(gè)艰毒。原理一樣
@makeBold #其效果等同于執(zhí)行test_B_I=makeBold(makeItalic(test_B_I))
@makeItalic #其效果等同于執(zhí)行test_B_I=makeItalic(test_B_I)
def test_B_I():
print("test_B_I"*2)
return "this is the test_B_I"
2筐高、帶參數(shù)的裝飾器
這里就給一個(gè)式子, 剩下的問題可以自己去想
@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
這個(gè)式子相當(dāng)于
func = decomaker(argA, argB, ...)(func)
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2015-3-25')
和兩層嵌套的decorator相比丑瞧,3層嵌套的效果是這樣的:
now = log('execute')(now)
我們來剖析上面的語句柑土,首先執(zhí)行l(wèi)og('execute'),返回的是decorator函數(shù)绊汹,再調(diào)用返回的函數(shù)稽屏,參數(shù)是now函數(shù),返回值最終是wrapper函數(shù)
>>> now()
execute now():
2015-3-25
>>> print(now.__name__)
'wrapper'
函數(shù)也是對(duì)象灸促,它有 __name__
等屬性诫欠,但你去看經(jīng)過decorator裝飾之后的函數(shù),它們的 __name__
已經(jīng)從原來的 now
變成了 wrapper
:
- 需要把原始函數(shù)的
__name__
等屬性復(fù)制到wrapper()
函數(shù)中浴栽,需要使用Python內(nèi)置的functools.wraps
裝飾器 - 需要導(dǎo)入
functools
模塊荒叼。然后在定義wrapper()
的前面加上@functools.wraps(func)
即可
3、裝飾器類
在Python中典鸡, 其實(shí)函數(shù)也是對(duì)象被廓。 反過來, 對(duì)象其實(shí)也可以像函數(shù)一樣調(diào)用萝玷,只要在類的方法中實(shí)現(xiàn) __call__
方法嫁乘,就可以實(shí)現(xiàn)裝飾器類。
裝飾器要求接受一個(gè)callable對(duì)象球碉,并返回一個(gè)callable對(duì)象蜓斧。那么用類來實(shí)現(xiàn)也是也可以的。我們可以讓類的構(gòu)造函數(shù) __init__()
接受一個(gè)函數(shù)睁冬,然后重載 __call__()
并返回一個(gè)函數(shù)挎春,也可以達(dá)到裝飾器函數(shù)的效果。
class logging(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print "function: {func}()".format(func=self.func.__name__)
return self.func(*args, **kwargs)
@logging
def say(something):
print "say {}!".format(something)
如果需要通過類形式實(shí)現(xiàn)帶參數(shù)的裝飾器,那么會(huì)比前面的例子稍微復(fù)雜一點(diǎn)直奋。那么在構(gòu)造函數(shù)里接受的就不是一個(gè)函數(shù)能庆,而是傳入的參數(shù)。通過類把這些參數(shù)保存起來脚线。然后在重載 __call__
方法是就需要接受一個(gè)函數(shù)并返回一個(gè)函數(shù)搁胆。
class logging(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func): # 接受函數(shù)
def wrapper(*args, **kwargs):
print "{level}: {func}()".format(level=self.level, func=func.__name__)
func(*args, **kwargs)
return wrapper #返回函數(shù)
@logging(level='INFO')
def say(something):
print "say {}!".format(something)
4、多個(gè)裝飾器
關(guān)于多個(gè)裝飾器修飾一個(gè)函數(shù)
- 當(dāng)一個(gè)函數(shù)被多個(gè)裝飾器裝飾時(shí)邮绿,裝飾器的加載順序是從內(nèi)到外的(從下往上的)渠旁。其實(shí)很好理解:裝飾器是給函數(shù)裝飾的,所以要從靠近函數(shù)的裝飾器開始從內(nèi)往外加載
- 外層的裝飾器船逮,是給里層裝飾器裝飾后的結(jié)果進(jìn)行裝飾一死。相當(dāng)于外層的裝飾器裝飾的函數(shù)是里層裝飾器的裝飾原函數(shù)后的結(jié)果函數(shù)(裝飾后的返回值函數(shù))。