首頁 >後端開發 >Python教學 >理解python中的閉包和裝飾器

理解python中的閉包和裝飾器

高洛峰
高洛峰原創
2017-03-03 13:58:011213瀏覽

python中的閉包從表現形式上定義(解釋)為:如果在一個內部函數裡,對在外部作用域(但不是在全域作用域)的變數進行引用,那麼內部函數就被認為是閉包(closure)。

以下說明主要針對 python2.7,其他版本可能有差異。

也許直接看定義並不太能明白,下面我們先來看什麼叫做內部函數:

def wai_hanshu(canshu_1):

  def nei_hanshu(canshu_2): # 我在函数内部有定义了一个函数
    return canshu_1*canshu_2

  return nei_hanshu  # 我将内部函数返回出去

a = wai_hanshu(123)   # 此时 canshu_1 = 123
print a
print a(321)  # canshu_2 = 321

理解python中的閉包和裝飾器

#我在函數裡面有嵌套了一個函數,當我向外層函數傳遞一變量的之後,並賦值給a ,我們發現a 變成了一個函數對象,而我再次為這個函數物件傳參的時候,又得到了內部函數的回傳值。我們知道,依照作用域的原則來說,我們在全域作用域是不能存取局部作用域的。但是,這裡透過討巧的方法存取到了內部函數。 。

下面我們繼續看一個例子:

def wai_hanshu():
  a = []
  def nei_hanshu(canshu):
    a.append(canshu)
    return a

  return nei_hanshu

a = wai_hanshu()
print a(123)
print a(321)

理解python中的閉包和裝飾器

#可以看出函數位於外部函數中的列表a 竟然改變了。要知道為什麼,就要先知道什麼是python的命名空間,而命名空間就是作用域表現的原因,這裡我簡單說明一下。

引入命名空間的主要原因還是為了避免變數衝突,因為python中的模組眾多,模組中有函數,類別等,它們都要使用到變數。但如果每次都要注意不和其他變數名稱衝突,那就太麻煩了,開發人員應該專注於自己的問題,而不是考慮別人寫的程式中用到了什麼變量,所以python引入了命名空間。命名空間分為模組層,模組內又分為全域作用域和局部作用域,用一個圖來表示的話:

理解python中的閉包和裝飾器

模組之間命名空間不同,而裡面還有全域作用域和局部作用域,局部作用域之前還能嵌套,這樣就能保證變數名稱不衝突了。這裡順便補充一下,可以透過__name__ 屬性來取得命名空間的名字:

理解python中的閉包和裝飾器

#主檔案的命名空間是叫做'__main__',而模組的命名空間就是模組名。

作用域的誕生,是因為當python在尋找變數的時候,首先會在目前的命名空間中尋找,如果目前命名空間中沒有,就到上一層的命名空間中找,以此類推,如果最後都沒找到,觸發變數沒找到的異常。

我們之前一直說:全域作用域無法存取局部作用域,而局部作用域能夠存取全域作用域就這這個原因。而當我在局部作用域創建了一個和外面同名的變數時,python在找這個變數的時候首先會在當前作用域中找,找到了,就不繼續往上一級找了。

在早期的python版本時,局部作用域是不能存取其他的局部作用域的,只能存取全域的,而現在的版本都是依序向上一級找,這裡就提一下。

也就是因為這個特性,我們可以在內部函數中存取外部函數中的變量,這也就是所謂的閉包了。

注意:這裡要做好物件之間的區分,例如:

def wai_hanshu():
  a = []
  def nei_hanshu(canshu):
    a.append(canshu)
    return a

  return nei_hanshu

a = wai_hanshu()  # 我创建了一个对象
b = wai_hanshu()  # 我又创建了一个对象
print a
print b
print a(123)
print b(321)

理解python中的閉包和裝飾器

在這裡,我們雖然都是操作wai_hanshu 中的變量,但是a 和b 完全是兩個對象,它們所在的記憶體空間也是不同的,所以裡面的資料也是獨立的。要注意不要搞混。

裝飾器

其實裝飾器就是在閉包的基礎上多進行了幾步,看程式碼:

#
def zsq(func): # 装饰函数
  def nei():
    print '我在传入的函数执行之前做一些操作'
    func() # 执行函数
    print '我在目标函数执行后再做一些事情'
  return nei

def login():  # 被装饰函数
  print '我进行了登录功能'

login = zsq(login)  # 我将被装饰的函数传入装饰函数中,并覆盖了原函数的入口

login()   # 此时执行的就是被装饰后的函数了

理解python中的閉包和裝飾器

#

在看这段代码的时候,要知道几件事:

1.函数的参数传递的其实是引用,而不是值。

2.函数名也是一个变量,所以可以重新赋值。

3.赋值操作的时候,先执行等号右边的。

只有明白了上面这些事之后,再结合一下代码,应该就能明白什么是装饰器了。所谓装饰器就是在闭包的基础上传递了一个函数,然后覆盖原来函数的执行入口,以后调用这个函数的时候,就可以额外实现一些功能了。装饰器的存在主要是为了不修改原函数的代码,也不修改其他调用这个函数的代码,就能实现功能的拓展。

而python觉得让你每次都进行重命名操作实在太不方便,于是就给出了一个便利的写法:

def zsq(func):
  def nei():
    print '我在传入的函数执行之前做一些操作'
    func() # 执行函数
    print '我在目标函数执行后再做一些事情'
  return nei

@zsq  # 自动将其下面的函数作为参数传到装饰函数中去
def login():
  print '我进行了登录功能'


login()

理解python中的閉包和裝飾器

这些小便利也叫做python的语法糖,你可能在很多地方见过这个说法。

带参数的装饰器:

def zsq(a):
  print '我是装饰器的参数', a
  def nei(func):
    print '我在传入的函数执行之前做一些操作'
    func() # 执行函数
    print '我在目标函数执行后再做一些事情'
  return nei


@zsq('123')
def login():
  print '我进行了登录功能'

理解python中的閉包和裝飾器

相当于: login = zsq(123)(login) ,所以在这里没有调用就执行了。

装饰器的嵌套:

这里就不完整写个例子了:

@deco1(deco_arg) 
@deco2 
def func(): 
  pass

相当于: func = deco1(deco_arg)(deco2(func)) 

也就是从上到下的嵌套了。

关于闭包和装饰器就先讲到这里,以后有需要再补充。

以上这篇深入理解python中的閉包和裝飾器就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持PHP中文网。

更多理解python中的閉包和裝飾器相关文章请关注PHP中文网!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn