Python——裝飾器 閉包的函數(shù)

閉包的定義

將組成函數(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ù)在內(nèi)存中的情況

既然函數(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!!!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末讥蔽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子画机,更是在濱河造成了極大的恐慌冶伞,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件步氏,死亡現(xiàn)場(chǎng)離奇詭異响禽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)荚醒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門芋类,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人腌且,你說(shuō)我怎么就攤上這事梗肝。” “怎么了铺董?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)禀晓。 經(jīng)常有香客問我精续,道長(zhǎng),這世上最難降的妖魔是什么粹懒? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任重付,我火速辦了婚禮,結(jié)果婚禮上凫乖,老公的妹妹穿的比我還像新娘确垫。我一直安慰自己弓颈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布删掀。 她就那樣靜靜地躺著翔冀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪披泪。 梳的紋絲不亂的頭發(fā)上纤子,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音款票,去河邊找鬼控硼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛艾少,可吹牛的內(nèi)容都是我干的卡乾。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼缚够,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼说订!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起潮瓶,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤陶冷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后毯辅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埂伦,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年思恐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沾谜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胀莹,死狀恐怖基跑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情描焰,我是刑警寧澤媳否,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站荆秦,受9級(jí)特大地震影響篱竭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜步绸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一掺逼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓤介,春花似錦吕喘、人聲如沸赘那。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)募舟。三九已至,卻和暖如春病梢,著一層夾襖步出監(jiān)牢的瞬間胃珍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工蜓陌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留觅彰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓钮热,卻偏偏與公主長(zhǎng)得像填抬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子隧期,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

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

  • 呵呵飒责!作為一名教python的老師,我發(fā)現(xiàn)學(xué)生們基本上一開始很難搞定python的裝飾器仆潮,也許因?yàn)檠b飾器確實(shí)很難懂...
    TypingQuietly閱讀 19,559評(píng)論 26 186
  • 原文出處: dzone 譯文出處:Wu Cheng(@nullRef) 1. 函數(shù) 在python中宏蛉,函數(shù)通過...
    DraculaWong閱讀 527評(píng)論 0 3
  • 無(wú)論項(xiàng)目中還是面試都離不開裝飾器話題拾并,裝飾器的強(qiáng)大在于它能夠在不修改原有業(yè)務(wù)邏輯的情況下對(duì)代碼進(jìn)行擴(kuò)展,權(quán)限校驗(yàn)鹏浅、...
    liuzhijun閱讀 1,662評(píng)論 0 30
  • 前任對(duì)很多很多人來(lái)說(shuō)都是從內(nèi)心深處不愿提及的一類人嗅义,那個(gè)時(shí)候懵懂的愛過, 狠狠地恨過隐砸,沒心沒肺地笑過之碗,也傻傻地哭過...
    假裝我是流浪歌手閱讀 304評(píng)論 0 1
  • 在社會(huì)上胖眷,像我這樣平凡的人應(yīng)該是占多的武通。 畢業(yè)12年多,僥幸混跡于一家國(guó)企珊搀;至今還是一個(gè)基層科員,干著不緊不慢的活...
    winyear閱讀 295評(píng)論 0 1