Python閉包與裝飾器

閉包

1.函數(shù)引用

def test1():
    print("---in test1 func---")
    
#調用函數(shù)
test1()

#引用函數(shù)
ret=test1

print(id(ret))
print(id(test1))

#通過引用調用函數(shù)
ret()

運行結果:

---in test1 func---
123456789000
123456789000
---in test1 func---

2.什么是閉包

#定義一個函數(shù)
def test(number):
    
    #在函數(shù)內(nèi)部在定義一個函數(shù)舆瘪,并且這個函數(shù)用到了外邊函數(shù)的變量舞吭,那么將這個函數(shù)稱為閉包
    def test_in(number_in):
        print("in test_in 函數(shù)俐镐,number_in is %d"%number_in)
        return number+number_in
    return test_in

#給test函數(shù)賦值铜幽,這個10就是給參數(shù)number    
ret=test(10)

#注意這里的100其實給參數(shù)number_in
print(ret(100))

#注意這里的200其實給參數(shù)number_in
print(ret(200))

運行結果:

in test_in函數(shù)亡电,number_in is 100
110
in test_in函數(shù)髓迎,number_in is 200
210

3.重講閉包

內(nèi)部函數(shù)對外部函數(shù)作用域里變量的引用(非全局變量)則稱內(nèi)部函數(shù)為閉包。

#test2.py
def counter(start=0):
    count=[start]
    def incr():
        count[0]+=1
        return count[0]
    return incr

應用test2.py

import test2
c1=test2.counter(10)
print(c1())

結果:11
print(c1())
結果:12

nonlocal訪問外部函數(shù)的局部變量

def counter(start=0):
    def incr():
        nonlocal start
        start+=1
        return start
    return incr

c1=counter(5)
print(c1())
print(c1())

c2=counter(50)
print(c2())
print(c2())

print(c1())
print(c1())

print(c2())
print(c2())

4.實例

def line_conf(a,b):
    def line(x):
        return a*x+b
    return line

line1=line_conf(1,2)
line2=line_conf(3,4)
print(line1(5))
print(line2(6))

在這個例子中,函數(shù)line與變量a腿堤,b構成閉包阀坏,在創(chuàng)建閉包的時候,我們通過line_conf的參數(shù)a,b說明了這兩個變量的取值笆檀,這樣忌堂,我們就確定了函數(shù)的最終形式(y=x+2和y=3x+4)。我們只需要變化參數(shù)a酗洒,b士修,就可以獲得不同的直線表達函數(shù),因此樱衷,我們可以看到棋嘲,閉包也具有提高代碼可復用性的作用。

如果沒有閉包矩桂,我們需要每次創(chuàng)建直線函數(shù)的時候同時說明a沸移,b,x侄榴。這樣雹锣,我們就需要傳遞更多的參數(shù)

裝飾器

1.首先理解一段代碼

def foo():
    print('foo')
    
foo  #表示是函數(shù)
foo()  #表示執(zhí)行foo函數(shù)


def foo():
    print('foo')
    
foo=lambda x: x+1
foo()  #執(zhí)行下面的lambda表達式,而不是原來的foo函數(shù)

2.需求

某公司由N個業(yè)務部門和一個基礎平臺部門構成牲蜀,基礎平臺負責提供底層的功能笆制,如:數(shù)據(jù)庫操作、redis調用涣达、監(jiān)控API等功能在辆。業(yè)務部門使用基礎功能時,只需調用基礎平臺提供的功能即可度苔。如:

"""基礎平臺提供的功能如下"""
def f1():
    print('f1')
    
def f2():
    print('f2')
    
def f3():
    print('f3')

def f4():
    print('f4')
    
    
    
"""業(yè)務部門a調用基礎平臺提供的功能"""
f1()
f2()
f3()
f4()


"""業(yè)務部門b調用基礎平臺提供的功能"""
f1()
f2()
f3()
f4()

基礎平臺提供的功能可以被任何人使用〈衣ǎ現(xiàn)在需要對基礎平臺的所有功能進行重構,為平臺提供所有功能添加驗證機制寇窑,既要在執(zhí)行功能前進行驗證鸦概。

程序員A的做法:

"""基礎平臺提供的功能"""

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è)務部門代碼不變"""
"""A,B分別調用"""
f1()
f2()
f3()
f4()

程序員B的做法:

只對基礎平臺的代碼進行重構,其他業(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')
    
    

程序員C的做法:

寫代碼遵循開放封閉原則甩骏,雖然在這個原則是用面向對象開發(fā)窗市,但是也適用于函數(shù)式編程,簡單來說饮笛,它規(guī)定已經(jīng)實現(xiàn)的代碼不允許被修改咨察,但可以被擴展即:

  • 封閉:對實現(xiàn)的功能代碼塊
  • 開放:對擴展開發(fā)

若實現(xiàn)上述需求,則不允許在f1福青、f2摄狱、f3脓诡、f4內(nèi)部進行修改代碼,實現(xiàn)方案:

def w1(func):
    def inner():
        #驗證1
        #驗證2
        #驗證3
        func()
    return inner

@w1
def f1():
    print('f1')
    
def f2():
    print('f2')
    
def f3():
    print('f3')
   
def f4():
    print('f4')
    
    

執(zhí)行過程

python解釋器將代碼從上至下進行解釋:

  1. def w1(func):將函數(shù)w1加載到內(nèi)存媒役;
  2. @w1
f1=w1(f1)

表面上解釋器僅僅會解釋這兩句代碼祝谚,因為函數(shù)在沒有被調用之前其內(nèi)部代碼不會被執(zhí)行。

從表面上看解釋器會執(zhí)行這兩句酣衷,但是@w1這一句在python中是一種語法糖(@函數(shù)名)

@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ù),
w1的返回值

將執(zhí)行完的2w1函數(shù)返回值賦值為@w1下面的函數(shù)f1钠龙,即將w1的返回值再重新賦值為w1炬藤,即:

新
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è)務調用者羹膳。

如此一來,即執(zhí)行了驗證功能根竿,又執(zhí)行了原來f1函數(shù)的內(nèi)容陵像,并將原f1函數(shù)返回給業(yè)務調用者。

3.再議裝飾器

#定義函數(shù):完成數(shù)據(jù)包裹
def makeBold(fn):
    def wrapped():
        return "<b>"+fn()+"</b>"
    return wrapped

#定義函數(shù):完成數(shù)據(jù)包裹
def makeItalic(fn):
    def wrapped():
        return "<i>"+fn()+"</i>"
    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())

運行結果:

<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>

4.裝飾器(decorator)功能

  1. 引入日志
  2. 函數(shù)執(zhí)行時間統(tǒng)計
  3. 執(zhí)行函數(shù)前預備處理
  4. 執(zhí)行函數(shù)后清理功能
  5. 權限校驗等場景
  6. 緩存

5.示例

例1.無參數(shù)的函數(shù)
from time import ctime,sleep

def timefun(func):
    def wrappedfunc():
        print("%s called at %s"%(func.__name__,ctime()))
        func()
    return wrappedfunc

@timefun
def foo():
    print("I am foo")
    
foo()
sleep(2)
foo()

上面代碼裝飾器執(zhí)行過程可表述為:

foo=timefun(foo)
#foo先作為參數(shù)賦值給func后寇壳,foo接收指向timefun返回的wrappedfunc
foo()
#調用foo()醒颖,即等價于調用wrappedfunc()
#內(nèi)部函數(shù)wrappedfunc被引用,所以外部函數(shù)的func變量(自由變量)并沒有釋放
#func里保存的是原foo函數(shù)對象

代碼執(zhí)行結果

執(zhí)行結果.png
例2.被裝飾的函數(shù)有參數(shù)
from time import ctime,sleep

def timefun(func):
    def wrappedfunc(a,b):
        print("%s called at %s"%(func.__name__,ctime()))
        print(a,b)
        func(a,b)
    return wrappedfunc

@timefun
def foo(a,b):
    print(a+b)
    
    
foo(2,8)
sleep(2)
foo(4,6)

執(zhí)行結果

執(zhí)行結果.png
例3.被裝飾的函數(shù)有不定長參數(shù)
from time import ctime,sleep

def timefun(func):
    def wrappedfunc(*args,**kwargs):
        print("%s called at %s"%(func.__name__,ctime()))
        func(*args,**kwargs)
    return wrappedfunc

@timefun
def foo(a,b,c):
    print(a+b+c)
    
foo(1,3,5)
sleep(2)
foo(2,4,6)

執(zhí)行結果

執(zhí)行結果.png
例4壳炎,裝飾器中的return
from time import ctime,sleep

def timefun(func):
    def wrappedfunc():
        print("%s called at %s"%(func.__name__,ctime()))
        func()
    return wrappedfunc

@timefun
def foo():
    print('I am foo')
    
@timefun
def getInfo():
    return '---hello---'

foo()
sleep(2)
foo()


print(getInfo())

運行結果

執(zhí)行結果.png

如果修改裝飾器為return func泞歉,則運行結果為:

執(zhí)行結果.png

或者參考如下代碼

from time import ctime,sleep

def timefun(func):
    def wrappedfunc():
        print("%s called at %s"%(func.__name__,ctime()))
        ret=func()  #保存返回來的hello
        return ret   #把hello返回給調用函數(shù)
    return wrappedfunc

@timefun
def foo():
    print('I am foo')
    
@timefun
def getInfo():
    return '---hello---'

foo()
sleep(2)
foo()


print(getInfo())
例5.裝飾器帶參數(shù),在原有裝飾器的基礎上匿辩,設置外部變量
from time import ctime, sleep

def timefun_arg(pre="hello"):
    def timefun(func):
        def wrappedfunc():
            print("%s called at %s %s"%(func.__name__, ctime(), pre))
            return func()
        return wrappedfunc
    return timefun

@timefun_arg("itcast")
def foo():
    print("I am foo")

@timefun_arg("python")
def too():
    print("I am too")

foo()
sleep(2)
foo()

too()
sleep(2)
too()

可以理解為:

foo()==timefun_arg("itcast")(foo)()
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腰耙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子铲球,更是在濱河造成了極大的恐慌挺庞,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睬辐,死亡現(xiàn)場離奇詭異挠阁,居然都是意外死亡,警方通過查閱死者的電腦和手機溯饵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來隘谣,“玉大人寻歧,你說我怎么就攤上這事秩仆〕嗡#” “怎么了齐莲?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵选酗,是天一觀的道長芒填。 經(jīng)常有香客問我殿衰,道長,這世上最難降的妖魔是什么椎工? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任维蒙,我火速辦了婚禮果覆,結果婚禮上,老公的妹妹穿的比我還像新娘斑响。我一直安慰自己,他們只是感情好纽门,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布赏陵。 她就那樣靜靜地躺著,像睡著了一般吃型。 火紅的嫁衣襯著肌膚如雪败玉。 梳的紋絲不亂的頭發(fā)上镜硕,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天血淌,我揣著相機與錄音财剖,去河邊找鬼。 笑死躺坟,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的咪橙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼美侦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了菠剩?” 一聲冷哼從身側響起易猫,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎具壮,沒想到半個月后哈蝇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炮赦,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了批旺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖暇赤,靈堂內(nèi)的尸體忽然破棺而出心例,到底是詐尸還是另有隱情,我是刑警寧澤鞋囊,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布止后,位于F島的核電站,受9級特大地震影響溜腐,放射性物質發(fā)生泄漏译株。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一挺益、第九天 我趴在偏房一處隱蔽的房頂上張望歉糜。 院中可真熱鬧,春花似錦望众、人聲如沸匪补。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叉袍。三九已至,卻和暖如春刽酱,著一層夾襖步出監(jiān)牢的瞬間喳逛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工棵里, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留润文,地道東北人姐呐。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像典蝌,于是被迫代替她去往敵國和親曙砂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 運行結果如下: 閉包的定義:在函數(shù)內(nèi)部再定義一個函數(shù)骏掀,并且這個函數(shù)用到了外邊函數(shù)的變量鸠澈,那么將這個函數(shù)以及用到的一...
    魔法高校的劣等生閱讀 518評論 0 0
  • # 本次講述的知識點也是非常重要的(嚴肅臉) # 先有的閉包,之后才生成了裝飾器截驮,同樣的也是非常簡單的東西笑陈。 # ...
    米蘭的小鐵匠閱讀 465評論 0 4
  • python萬物皆對象! 閉包 閉包:兩個函數(shù)的嵌套,外部函數(shù)返回內(nèi)部函數(shù)的引用,外部函數(shù)一定有參數(shù) def 外部...
    風舞柏楊閱讀 781評論 2 0
  • 1.1==葵袭,is的使用 ·is是比較兩個引用是否指向了同一個對象(引用比較)涵妥。 ·==是比較兩個對象是否相等。 1...
    TENG書閱讀 731評論 0 0
  • ?前幾天在醫(yī)院排隊的時候坡锡,想發(fā)一個關于我媽媽的段子蓬网,主要想吐槽一下她后媽的體質。于是編輯了如下一段: 好多人問我鹉勒,...
    芳言芳語閱讀 570評論 2 2