Python閉包與裝飾器

1. 閉包

概念:在函數(shù)嵌套的前提下,內(nèi)層函數(shù)引用了外層函數(shù)的變量(包括參數(shù)),外層函數(shù)又把內(nèi)層函數(shù)當(dāng)做返回值進(jìn)行返回椭员。這個(gè)內(nèi)層函數(shù)+多引用的外層變量,稱為‘閉包’笛园。
實(shí)例1:

def test1(a):
  b=10
  def test2()
    print(a)
    print(b)
  return test2

應(yīng)用場(chǎng)景:外層函數(shù)隘击,根據(jù)不同的參數(shù)生成不同作用功能的函數(shù)。
注意事項(xiàng):

  1. 閉包中研铆,如果要修改引用的外層變量埋同,則需要使用nonlocal變量聲明,否則會(huì)當(dāng)做是閉包內(nèi)棵红,新定義的變量凶赁。
  2. 當(dāng)閉包內(nèi),引用了一個(gè)后期會(huì)發(fā)生變化的變量時(shí)逆甜,一定要注意虱肄。
    實(shí)例2:
def test()
  funcs = []
  for i in range(1, 4):
    def test2():
      print(i)
    funcs.append(test2)
  return funcs
newfuncs = test()

newfuncs[0]()
newfuncs[1]()
newfuncs[2]()

>>>3
>>>3
>>>3

以上情況的出現(xiàn)是因?yàn)椋m然在最后newfuncs中調(diào)用不同的test2函數(shù)忆绰,但在此之前funcs函數(shù)內(nèi)的test2沒(méi)有被執(zhí)行浩峡,而test2函數(shù)已經(jīng)被定義了3次了,其中range(1, 4)也已經(jīng)走了3次了错敢,相應(yīng)的i也已經(jīng)從1走到3了翰灾,所以在最后調(diào)用test2的,會(huì)去取i的值稚茅,此時(shí)i=3纸淮,故三次調(diào)用都會(huì)返回3。
案例2改進(jìn):

def test():
  funcs = []
  for i in range(1, 4):
    def test2(num):
      def inner():
        print(num)
      return inner
    funcs.append(test2=(i))
  return funcs
newfuncs = test()

newfuncs[0]()
newfuncs[1]()
newfuncs[2]()

>>>1
>>>2
>>>3

解釋:在改進(jìn)方案中亚享,其實(shí)就是給之前的test2內(nèi)再嵌套了一個(gè)函數(shù)inner咽块,inner負(fù)責(zé)之前test2的功能,然后每次test2加入funcs列表的時(shí)欺税,直接把的i傳入test2侈沪,同時(shí)又不執(zhí)行inner函數(shù),之后newfuncs調(diào)用test2時(shí)晚凿,其中已經(jīng)包含的每次不同的i的值亭罪,inner函數(shù)執(zhí)行輸出的結(jié)果也就會(huì)不同。

2. 裝飾器

2.1 概念理解

裝飾器本質(zhì)上是一個(gè)函數(shù)歼秽,它可以讓其他函數(shù)在不需要做任何改動(dòng)的前提下增加額外的功能应役,裝飾器的返回值也是一個(gè)函數(shù)對(duì)象。
裝飾器經(jīng)常用于有切面需求的場(chǎng)景:插入日志、性能測(cè)試箩祥、事務(wù)處理院崇、緩存、權(quán)限校驗(yàn)等場(chǎng)景袍祖。
分步理解裝飾器:
(1). 需要給一個(gè)函數(shù)a()添加一個(gè)功能底瓣,功能寫(xiě)在decorator()內(nèi),當(dāng)我們調(diào)用'decrotor(a)`的時(shí)候就會(huì)在執(zhí)行a()之前執(zhí)行我們要的功能盲泛。

def a():
  print('i am a')

def decorator(a):
  print('新添加的功能')
  a()

(2).我們需要該函數(shù)a()在業(yè)務(wù)邏輯代碼中調(diào)用的時(shí)候名稱還是a()(函數(shù)的單一職責(zé)性)濒持,而不是decrotor(a),那么最簡(jiǎn)單的方法就是在把它賦值給a寺滚,即a = decorator(a)
(3). 但是在進(jìn)行a = decorator(a)時(shí)柑营,右邊的decorator(a)函數(shù)會(huì)被直接執(zhí)行,而此時(shí)邏輯代碼還沒(méi)有調(diào)用它村视,為避免該情況發(fā)生官套,我們使用閉包的思想。
(4). 這里使用閉包思想:也就是decorator(a)返回一個(gè)函數(shù)(后面我們會(huì)把這個(gè)函數(shù)寫(xiě)成warpper)蚁孔,將這個(gè)函數(shù)賦值給a奶赔,這樣在進(jìn)行a = decorator(a)時(shí)右邊就不會(huì)被立即執(zhí)行了。最后我們把需要添加的新功能杠氢,以及a()都寫(xiě)在warpper函數(shù)里站刑,這樣寫(xiě)就可以使得只有真正在業(yè)務(wù)邏輯代碼中調(diào)用該函數(shù)的時(shí)候,warpper才會(huì)被執(zhí)行鼻百,同時(shí)我們會(huì)加上一句a = decorator(a)绞旅,來(lái)確保業(yè)務(wù)邏輯代碼中調(diào)用的時(shí)候名稱還是a()。所以寫(xiě)出的結(jié)果如下:

def a():
  print('i am a')

def decorator(a):
  def warpper():
    print('新添加的功能')
    a()
  return warpper

a = decorator(a)
a()
# 以上寫(xiě)法中
decorator(a) 等價(jià)于
def warpper():
    print('新添加的功能')
    a()

最后 Python給我們給定了以個(gè)語(yǔ)法糖@温艇,即可以用@decorator 代替a = decorator(a)因悲,這也就演變出裝飾器最終的寫(xiě)法:

def decorator(a):
  def warpper():
    print('新添加的功能')
    a()
  return warpper

@decorator
def a():
  print('i am a')

a()

>>>新添加的功能
>>>i am a

裝飾器采用了閉包的思想,在裝飾函數(shù)的同時(shí)不執(zhí)行函數(shù)勺爱,只有到正正的業(yè)務(wù)邏輯代碼調(diào)用的時(shí)候再執(zhí)行函數(shù)晃琳。

2.2 裝飾器的執(zhí)行時(shí)間

當(dāng)@decorator出現(xiàn)的時(shí)候,decorator裝飾器函數(shù)就立即被執(zhí)行了琐鲁。

2.3 裝飾器的執(zhí)行順序

從上到下去裝飾卫旱,從下到上去執(zhí)行。

@a
@b
@c
def func():
# 等效于
func = a(b(c(func)))

2.4 對(duì)有參數(shù)的函數(shù)進(jìn)行裝飾

如果需要被裝飾的的函數(shù)a()中有參數(shù)呢围段?這時(shí)候就需要有一個(gè)東西來(lái)接收函數(shù)a()中的參數(shù)顾翼,而函數(shù)a()中的參數(shù)也可能是多種多樣的,這時(shí)候我們可以這樣來(lái)寫(xiě)warpper函數(shù):warpper(*args, **kwargs)蒜撮,但是在warpper函數(shù)中調(diào)用的的func函數(shù)也需要用:func(**args, **kwargs)的寫(xiě)法來(lái)解包warpper中接收的函數(shù),這樣func才可以正常執(zhí)行。代碼如下:

def decorator(func):
  def warpper(*args, **kwargs):
    print('新添加的功能')
    func(*args, **kwargs)
  return warpper

@decorator
def a(i='我', am='是',):
  print(i, am, "a")
  
a()
>>>新添加的功能
>>>我 是 a

2.5 對(duì)有返回值的函數(shù)進(jìn)行裝飾

如果需要被裝飾的的函數(shù)a()中有return返回值呢段磨?這時(shí)候就需要有一個(gè)東西來(lái)返回函數(shù)a()中的返回值取逾,所以我們?cè)趙arpper中也應(yīng)該寫(xiě)入return的部分,代碼如下:

def decorator(func):
  def warpper(*args, **kwargs):
    print('新添加的功能')
    return func(*args, **kwargs)
  return warpper

@decorator
def a(i='我', am='是',):
  print(i, am, "a")
  b = i+am+"b"
  return b

b = a()
print(b)
>>>新添加的功能
>>>我 是 a
>>>我是b

原則上要保證苹支,裝飾器中的warpper函數(shù)的格式和被裝飾的函數(shù)格式一致砾隅。

2.6 帶有參數(shù)的裝飾器

如果我們可以給裝飾器本身傳入一個(gè)參數(shù),那么裝飾器就可以隨著傳入?yún)?shù)的變化而做出不同的裝飾功能债蜜,例如:print('新添加的功能')晴埂、print('新添加的技術(shù)')、print('新添加的顏色')寻定。那么儒洛,該如何實(shí)現(xiàn)帶有參數(shù)的裝飾器呢? 思路:我們定義個(gè)一個(gè)外函數(shù)狼速,把裝飾器放入其中琅锻,然后裝飾器中可以使用這個(gè)外函數(shù)傳入的值,同時(shí)在外函數(shù)的最后返回其中的裝飾器向胡,那么這個(gè)整體就可以成為一個(gè)新的帶有參數(shù)的裝飾器恼蓬。代碼如下:

def zhuangshiqi(i):
  def decorator(func):
    def warpper(*args, **kwargs):
      print('新添加的'+i)
      return func(*args, **kwargs)
    return warpper
  return decorator

@zhuangshiqi(i="顏色而不是功能")
def a(i='我', am='是'):
  print(i, am, "a")
  return a

a()
>>>新添加的顏色而不是功能
>>>我 是 a

實(shí)例1:簡(jiǎn)單的裝飾器

def decorator(func):
  def wrapper(*args, *kwargs):
    logging.warn("%s is running" %  func.__name__)
    return func(*args, *kwargs)
  return wrapper

@decorator
def a():
  print('i am a')

a()

>>> a is running
>>> i am a

實(shí)例2:裝飾器用于驗(yàn)證權(quán)限的例子

userAge = 40

def canYou(func):
  def decorator(*args, **kwargs):
      if userAge > 1 and userAge < 10:
          return func(*args, **kwargs)
      print('你的年齡不符合要求,不能看')
  return decorator

@canYou
def play():
  print('開(kāi)始播放動(dòng)畫(huà)片 《喜洋洋和灰太狼》')

play()

>>> 你的年齡不符合要求僵芹,不能看

內(nèi)置裝飾器

@staticmathod 处硬、@classmethod 、@property

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拇派,一起剝皮案震驚了整個(gè)濱河市荷辕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌攀痊,老刑警劉巖桐腌,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異苟径,居然都是意外死亡案站,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)棘街,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蟆盐,“玉大人,你說(shuō)我怎么就攤上這事遭殉∈遥” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵险污,是天一觀的道長(zhǎng)痹愚。 經(jīng)常有香客問(wèn)我富岳,道長(zhǎng),這世上最難降的妖魔是什么拯腮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任窖式,我火速辦了婚禮,結(jié)果婚禮上动壤,老公的妹妹穿的比我還像新娘萝喘。我一直安慰自己,他們只是感情好琼懊,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布阁簸。 她就那樣靜靜地躺著,像睡著了一般哼丈。 火紅的嫁衣襯著肌膚如雪启妹。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天削祈,我揣著相機(jī)與錄音翅溺,去河邊找鬼。 笑死髓抑,一個(gè)胖子當(dāng)著我的面吹牛咙崎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吨拍,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼褪猛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了羹饰?” 一聲冷哼從身側(cè)響起伊滋,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎队秩,沒(méi)想到半個(gè)月后笑旺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馍资,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年筒主,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸟蟹。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乌妙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出建钥,到底是詐尸還是另有隱情藤韵,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布熊经,位于F島的核電站泽艘,受9級(jí)特大地震影響欲险,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匹涮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一盯荤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧焕盟,春花似錦、人聲如沸宏粤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)绍哎。三九已至来农,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間崇堰,已是汗流浹背沃于。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留海诲,地道東北人繁莹。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像特幔,于是被迫代替她去往敵國(guó)和親咨演。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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