欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

實(shí)例詳解Python裝飾器與閉包

 更新時(shí)間:2019年07月29日 13:42:53   作者:再見紫羅蘭  
閉包是Python裝飾器的基礎(chǔ)。要理解閉包,先要了解Python中的變量作用域規(guī)則。本文主要給大家介紹Python裝飾器與閉包的相關(guān)知識(shí),需要的朋友可以參考下

閉包是Python裝飾器的基礎(chǔ)。要理解閉包,先要了解Python中的變量作用域規(guī)則。

變量作用域規(guī)則

首先,在函數(shù)中是能訪問全局變量的:

>>> a = 'global var'
>>> def foo():
 print(a)
>>> foo()
global var

然后,在一個(gè)嵌套函數(shù)中,內(nèi)層函數(shù)能夠訪問在外層函數(shù)中定義的局部變量:

>>> def foo():
 a = 'free var'
 def bar():
  print(a)
 return bar

>>> foo()()
free var

閉包

上面的嵌套函數(shù)就是閉包。 閉包 是指延伸了作用域的函數(shù),在其中能夠訪問未在函數(shù)定義體中定義的非全局變量。未在函數(shù)定義體中定義的非全局變量一般都是在嵌套函數(shù)中出現(xiàn)的。

上述示例中的變量a就是一個(gè)并未在函數(shù)bar中定義的非全局變量。對(duì)于bar來說,它有個(gè)專業(yè)名字,叫做 自由變量 。

自由變量的名稱可以在字節(jié)碼對(duì)象中查看:

>>> bar = foo()
>>> bar.__code__.co_freevars
('a',)

自由變量的值綁定在函數(shù)的__closure__屬性中:

>>> bar.__closure__
(<cell at 0x000001CB2912DF48: str object at 0x000001CB291D3D70>,)

其中保存了對(duì)應(yīng)自由變量的cell對(duì)象的序列,cell對(duì)象的cell_contents屬性保存了變量的值:

>>> bar.__closure__[0].cell_contents
'free var'

這與JavaScript中閉包的行為是類似的,JavaScript中嵌套函數(shù)會(huì)將外層函數(shù)的活動(dòng)對(duì)象添加到它的作用域鏈中。但與JavaScript不同的是,當(dāng)Python函數(shù)中的全局變量或者自由變量是不可變對(duì)象(數(shù)字、字符串、元組等)時(shí),是只能讀取,無法更新的:

>>> a = 1
>>> def foo():
 print(a)
 a += 1

>>> foo()
UnboundLocalError: local variable 'a' referenced before assignment

>>> def foo():
 a = 1
 def bar():
  print(a)
  a += 1
 return bar

>>> foo()()
UnboundLocalError: local variable 'a' referenced before assignment

兩種情況下,都會(huì)報(bào)錯(cuò)。這并不是缺陷,而是Python的設(shè)計(jì)選擇。Python不要求聲明變量,但是會(huì)假定在函數(shù)定義體中賦值的變量是局部變量,以避免在不知情的情況下修改全局變量。

a += 1 與 a = a + 1 相同,編譯函數(shù)的定義體時(shí),會(huì)將a當(dāng)做局部變量,不會(huì)當(dāng)做自由變量保存。然后嘗試獲取a的值時(shí),發(fā)現(xiàn)a并沒有綁定值,于是報(bào)錯(cuò)。

解決這個(gè)問題的辦法,一是將變量置于一些可變對(duì)象,如列表、字典中:

def foo():
 ns = {}
 ns['a'] = 1
 def bar():
  ns['a'] += 1
  print (ns['a'])
 return bar

另外的方法就是使用 global 或者 nonlocal 將變量聲明為全局變量或者自由變量:

>>> def foo():
 a = 1
 def bar():
  nonlocal a
  a += 1
  print(a)
 return bar

>>> foo()()
2

當(dāng)自由變量本身是可變對(duì)象時(shí),是可以直接進(jìn)行操作的:

def make_avg():
 ls = []
 def avg(x):
  ls.append(x)
  print(sum(ls)/len(ls))
 return avg

裝飾器

裝飾器是可調(diào)用對(duì)象,參數(shù)一般是另一個(gè)函數(shù)。裝飾器可以以某種方式增強(qiáng)被裝飾函數(shù)的行為,然后返回被裝飾的函數(shù)或者將其替換成一個(gè)新的函數(shù)。

一個(gè)最簡(jiǎn)單的不做任何額外行為的裝飾器:

def decorate(func):
 return func

decorate 函數(shù)就是一個(gè)最簡(jiǎn)單的裝飾器,使用方法:

def target():
 pass
target = decorate(target)

Python為裝飾器的使用提供了語法糖,可以簡(jiǎn)便的寫為:

@decorate
def target():
 pass

導(dǎo)入時(shí)運(yùn)行

裝飾器一個(gè)很重要的特性是它是導(dǎo)入時(shí)(加載模塊時(shí))運(yùn)行的:

def decorate(func):
 print('running decorator when import')
 return func
@decorate
def foo():
 print('running foo')
 pass
if __name__ == '__main__':
 print('start foo')
 foo()

結(jié)果:

running decorator when import start foo running foo

可以看到,裝飾器是導(dǎo)入時(shí)運(yùn)行的,而被裝飾的函數(shù)是明確調(diào)用時(shí)運(yùn)行的。

裝飾器可以返回被裝飾的函數(shù)本身,和運(yùn)行時(shí)導(dǎo)入的特性結(jié)合起來,可以實(shí)現(xiàn)簡(jiǎn)單的注冊(cè)器功能:

view_registry = []
def register(func):
 view_registry.append(func)
 return func
@register
def view1():
 pass
@register
def view2():
 pass
def main():
 print(view_registry)
if __name__ == '__main__':
 main()

返回新函數(shù)

上述裝飾器的例子都返回了被裝飾的原函數(shù),但裝飾器的典型行為還是返回一個(gè)新函數(shù):把被裝飾的函數(shù)替換成新函數(shù),新函數(shù)接受與原函數(shù)相同的參數(shù),并且返回原函數(shù)本該返回的值。寫法類似于:

def deco(func):
 def new_func(*args, **kwargs):
  return func(*args, **kwargs)
 return new_func

這種情況下裝飾器就使用到了閉包。JavaScript中的防抖與節(jié)流函數(shù)就是這種典型的裝飾器行為。新函數(shù)一般會(huì)使用外部裝飾器函數(shù)中的變量當(dāng)做自由變量,對(duì)函數(shù)作出某種增強(qiáng)行為。

舉個(gè)例子,我們知道,當(dāng)Python函數(shù)的參數(shù)是個(gè)可變對(duì)象時(shí),會(huì)產(chǎn)生意料之外的行為:

def foo(x, y=[]):
  y.append(x)
  print(y)

foo(1)
foo(2)
foo(3)

輸出:

[1] [1, 2] [1, 2, 3]

這是因?yàn)?,函?shù)的參數(shù)默認(rèn)值保存在__defaults__屬性中,指向了同一個(gè)列表:

>>> foo.__defaults__
([1, 2, 3],)

我們就可以用一個(gè)裝飾器在函數(shù)執(zhí)行前取出默認(rèn)值做深復(fù)制,然后覆蓋函數(shù)原先的參數(shù)默認(rèn)值:

import copy
def fresh_defaults(func):
  defaults = func.__defaults__
  def deco(*args, **kwargs):
    func.__defaults__ = copy.deepcopy(defaults)
    return func(*args, **kwargs)
  return deco
@fresh_defaults
def foo(x, y=[]):
  y.append(x)
  print(y)
foo(1)
foo(2)
foo(3)

輸出:

[1] [2] [3]

接收參數(shù)的裝飾器

裝飾器除了可以接受函數(shù)作為參數(shù)外,還可以接受其他參數(shù)。使用方法是:創(chuàng)建一個(gè)裝飾器工廠,接受參數(shù),返回一個(gè)裝飾器,再把它應(yīng)用到被裝飾的函數(shù)上,語法如下:

def deco_factory(*args, **kwargs):
  def deco(func):
    print(args)
    return func
  return deco
@deco_factory('factory')
def foo():
  pass

在Web框架中,通常要將URL模式映射到生成響應(yīng)的view函數(shù),并將view函數(shù)注冊(cè)到某些中央注冊(cè)處。之前我們?cè)?jīng)實(shí)現(xiàn)過一個(gè)簡(jiǎn)單的注冊(cè)裝飾器,只是注冊(cè)了view函數(shù),卻沒有URL映射,是遠(yuǎn)遠(yuǎn)不夠的。

在Flask中,注冊(cè)view函數(shù)需要一個(gè)裝飾器:

@app.route('/hello')
def hello():
  return 'Hello, World'

原理就是使用了裝飾器工廠,可以簡(jiǎn)單的模擬一下實(shí)現(xiàn):

class App:
  def __init__(self):
    self.view_functions = {}
  def route(self, rule):
    def deco(view_func):
      self.view_functions[rule] = view_func
      return view_func
    return deco
app = App()
@app.route('/')
def index():
  pass
@app.route('/hello')
def hello():
  pass
for rule, view in app.view_functions.items():
  print(rule, ':', view.__name__)

輸出:

/ : index /hello : hello

還可以使用裝飾器工廠來確定view函數(shù)可以允許哪些HTTP請(qǐng)求方法:

def action(methods):
  def deco(view):
    view.allow_methods = [method.lower() for method in methods]
    return view
  return deco
@action(['GET', 'POST'])
def view(request):
  if request.method.lower() in view.allow_methods:
    ...

重疊的裝飾器

裝飾器也是可以重疊使用的:

@d1
@d2
def foo():
  pass

等同于:

foo = d1(d2(foo))

類裝飾器

裝飾器的參數(shù)也可以是一個(gè)類,也就是說,裝飾器可以裝飾類:

import types
def deco(cls):
  for key, method in cls.__dict__.items():
    if isinstance(method, types.FunctionType):
      print(key, ':', method.__name__)
  return cls
@deco
class Test:
  def __init__(self):
    pass
  def foo(self):
    pass

總結(jié)

以上所述是小編給大家介紹的實(shí)例詳解Python裝飾器與閉包,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!

相關(guān)文章

  • python 實(shí)現(xiàn)的發(fā)送郵件模板【普通郵件、帶附件、帶圖片郵件】

    python 實(shí)現(xiàn)的發(fā)送郵件模板【普通郵件、帶附件、帶圖片郵件】

    這篇文章主要介紹了python 實(shí)現(xiàn)的發(fā)送郵件模板,包含Python發(fā)送普通郵件、帶附件及帶圖片郵件相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2019-07-07
  • Python八個(gè)自動(dòng)化辦公的技巧

    Python八個(gè)自動(dòng)化辦公的技巧

    這篇文章主要介紹了幾個(gè)Python自動(dòng)化辦公的技巧,可以大大提高工作效率,例如:Word文檔doc轉(zhuǎn)docx、Excel文件批量合并、Word文件批量轉(zhuǎn)pdf等,需要的可以參考一下
    2022-01-01
  • 淺談怎么給Python添加類型標(biāo)注

    淺談怎么給Python添加類型標(biāo)注

    今天給大家?guī)淼奈恼率荘ython的相關(guān)知識(shí),文章圍繞著怎么給Python添加類型標(biāo)注展開,文中有非常詳細(xì)的介紹,需要的朋友可以參考下
    2021-06-06
  • Python日志模塊logging簡(jiǎn)介

    Python日志模塊logging簡(jiǎn)介

    這篇文章主要介紹了Python日志模塊logging簡(jiǎn)介,本文講解了Logger、Handler、Formatter、日志配置管理、通過文件配置管理日志等內(nèi)容,需要的朋友可以參考下
    2015-04-04
  • Python入門之實(shí)例方法、類方法和靜態(tài)方法的區(qū)別講解

    Python入門之實(shí)例方法、類方法和靜態(tài)方法的區(qū)別講解

    這篇文章主要介紹了Python入門之實(shí)例方法、類方法和靜態(tài)方法的區(qū)別講解,實(shí)例方法是在創(chuàng)建了類的實(shí)例之后才能被調(diào)用的方法,類方法是在不需要?jiǎng)?chuàng)建類的實(shí)例的情況下就可以調(diào)用的方法,最后,靜態(tài)方法是與類和類的實(shí)例都沒有綁定關(guān)系的方法,需要的朋友可以參考下
    2023-10-10
  • 基于Python實(shí)現(xiàn)復(fù)刻人生重開模擬器

    基于Python實(shí)現(xiàn)復(fù)刻人生重開模擬器

    人生重開模擬器是由VickScarlet上傳至GitHub的一款簡(jiǎn)單的文字網(wǎng)頁游戲。本文將用Python復(fù)刻一下這個(gè)游戲,感興趣的小伙伴可以嘗試一下
    2022-10-10
  • Pycharm中使用git進(jìn)行合作開發(fā)的教程詳解

    Pycharm中使用git進(jìn)行合作開發(fā)的教程詳解

    這篇文章主要介紹了Pycharm中使用git進(jìn)行合作開發(fā),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • python實(shí)現(xiàn)銀行實(shí)戰(zhàn)系統(tǒng)

    python實(shí)現(xiàn)銀行實(shí)戰(zhàn)系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)銀行實(shí)戰(zhàn)系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • selenium設(shè)置proxy、headers的方法(phantomjs、Chrome、Firefox)

    selenium設(shè)置proxy、headers的方法(phantomjs、Chrome、Firefox)

    這篇文章主要介紹了selenium設(shè)置proxy、headers的方法(phantomjs、Chrome、Firefox),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • Pandas讀寫CSV文件的方法示例

    Pandas讀寫CSV文件的方法示例

    這篇文章主要介紹了Pandas讀寫CSV文件的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03

最新評(píng)論