首頁  >  文章  >  後端開發  >  Python的幾個高階語法概念淺析(lambda表達式閉包裝飾器)

Python的幾個高階語法概念淺析(lambda表達式閉包裝飾器)

高洛峰
高洛峰原創
2017-01-23 14:38:461430瀏覽

1. 匿名函數
匿名函數(anonymous function)是指未與任何標識符綁定的函數,多用在functional programming languages領域,典型應用場合:
1) 作為參數傳給高階函數(higher-order function ),如python中的built-in函數filter/map/reduce都是典型的高階函數
2) 作為高階函數的回傳值(雖然此處的"值"實際上是個函數物件)
與命名函數(named function)相比,若函數只被呼叫1次或有限次,則匿名函數在語法上較輕量級。
具體語法上,python透過lambda語法支援函數體為表達式的匿名函數,即:python的lambda表達式本質上是個匿名函數,但其函數體只能是個表達式,不能包含其它語句。
此外,高階動態語言常藉助匿名函數實作閉包(closure)或裝飾器(decorator)等高階語法。
在某些場合下,lambda表達式的使用使得python程式看起來非常簡潔。例如,以下是根據value對dict元素做排序的程式碼範例:

>>> foo = {'father' : 65, 'mother' : 62, 'sister' : 38, 'brother' : 29, 'me' : 28}
>>> sorted(foo.iteritems(), key=lambda x: x[1])
[('me', 28), ('brother', 29), ('sister', 38), ('mother', 62), ('father', 65)]

2. 閉包
閉包(closure)本質上是一個包含了其引用環境(referencing environment)的函數或函數引用,這裡的"引用環境"通常由一張表來維護,該表儲存了函數體會存取的非局部變數(non-local variables)的引用。
與C語言中的函數指標相比,閉包允許巢狀函數存取其作用域外的non-local變量,這與Python解釋器對變數的作用域查找規則有關(Python支援LEGB的查找規則,想深究的話,可以參考第4版第17章Scopes關於作用域及查找規則的詳細講解,或查看這篇文章做快速了解)。
對於執行時間記憶體分配模型會在線性堆疊上建立局部變數的語言來說(典型如C語言),通常很難支援閉包。因為這些語言底層實作中,若函數傳回,函數中定義的局部變數均會隨著函數棧被回收而銷毀。但閉包在底層實作上要求其要存取的non-local變數在閉包被執行的時候保持有效,直到這個閉包的生命週期結束,這意外著這些non-local變數只有在其確定不再被使用時才能銷毀,而不能隨著定義這些變數的函數返回而銷毀。因此,天生支援閉包的語言通常採用garbage collection的方式管理內存,因為gc機制保證了變數只有不再被引用時才會由系統銷毀並回收其內存空間。
具體語法上,閉包通常伴隨著函數巢狀定義。以Python為例,一個簡單的閉包範例如下:

#!/bin/env python
#-*- encoding: utf-8 -*-
 
def startAt_v1(x):
 def incrementBy(y):
  return x + y 
 print 'id(incrementBy)=%s' % (id(incrementBy))
 return incrementBy
 
def startAt_v2(x):
 return lambda y: x + y 
 
if '__main__' == __name__:
 c1 = startAt_v1(2)
 print 'type(c1)=%s, c1(3)=%s' % (type(c1), c1(3))
 print 'id(c1)=%s' % (id(c1))
  
 c2 = startAt_v2(2)
 print 'type(c2)=%s, c2(3)=%s' % (type(c2), c2(3))

執行結果如下:

id(incrementBy)=139730510519782
type(c1)=<type &#39;function&#39;>, c1(3)=5
id(c1)=139730510519782
type(c2)=<type &#39;function&#39;>, c2(3)=5

上述範例中,startAt_v1和startAt_v2都實現了閉包,其中:v1借助嵌套定義函數實現;vv2則借助lambda表達式/匿名函數來實作。
我們以v1為例對閉包做說明:
1) 函數startAt_v1接受1個參數,返回1個函數對象,而這個函數對象的行為由嵌套定義的函數incrementBy實現。
2) 對函數incrementBy來說,變數x就是所謂的non-local變數(因為x既非該函數定義的局部變量,又非普通意義上的全域變數),incrementBy實作具體的函數行為並傳回。
3) main入口的c1接收到的返回值是個函數對象,從id(incrementBy) == id(c1)可斷定,c1"指向"的對象與函數名incrementBy"指向"的其實是同一個函數對象。
4) 受益於Python對閉包的支持,與普通函數的物件相比,c1指向的物件可以存取不在其函數作用域內的non-local變量,而這個變數是由incrementBy的外層包裝函數startAt_v1的入參提供的,於是,相當於c1指向的函數物件對其外層包裝函數的入參具有"記憶"功能,透過調用外層包裝函數創建閉包時,不同的入參被內層函數作為引用環境維護起來。
5) 當呼叫c1(3)時,傳入的參數與引用環境維護的外層包裝函數的參數一起運算得到最終結果。
以上步驟分析說明了一個閉包從創建到執行的基本原理,理解這個case後,閉包的概念也應該清晰了。

3. 裝飾器
python支援裝飾器(decorator)語法。裝飾器的概念對於初學者來說比較晦澀,因為它涉及到函數式程式設計的幾個概念(如匿名函數、閉包),這也是本文先介紹匿名函數和閉包的原因。

我们引用这篇文章对装饰器的定义:
A decorator is a function that takes a function object as an argument, and returns a function object as a return value.
从这个定义可知,装饰器本质上只是一个函数,它借助闭包的语法去修改一个函数(又称被装饰函数)的行为,即decorator其实是个闭包函数,该函数以被装饰函数名(这个函数名其实是一个函数对象的引用)作为入参,在闭包内修改被装饰函数的行为后,返回一个新的函数对象。
特别说明:decorator并非必须以函数形式出现,它可以是任何可被调用的对象,例如它也可以class形式出现,参见这篇文章给出的例子。
在定义好函数装饰器的前提下,当外部调用这个被装饰函数时,decorator的语法糖会由Python解释器解释为先执行装饰器函数,然后在装饰器返回的新函数对象上继续执行其余语句。
来个实例分析一下:

#!/bin/env python
#-*- encoding: utf-8 -*-
 
def wrapper(fn):
 def inner(n, m):
  n += 1
  print &#39;in inner: fn=%s, n=%s, m=%s&#39; % (fn.__name__, n, m)
  return fn(n, m) + 6 // 这里有return且返回值为int对象
 return inner
 
@wrapper
def foo(n, m):
 print &#39;in foo: n=%s, m=%s&#39; % (n, m)
 return n * m
 
print foo(2, 3)

上面的示例中,foo通过@wrapper语法糖声明它的装饰器是wrapper,在wrapper中,定义了嵌套的inner函数(该函数的参数列表必须与被装饰函数foo的参数列表保持一致),装饰器wrapper修改foo的行为后,返回inner(注意:由于inner的返回值是个int对象,故wrpper最终返回的也是个int对象)。
调用foo(2, 3)时,Python解释器先调用wrapper对foo做行为改写,然后返回int对象,不难推测,上述代码的执行结果如下:

in inner: fn=foo, n=3, m=3
in foo: n=3, m=3
foo(2, 3)=15

更多Python的几个高级语法概念浅析(lambda表达式闭包装饰器)相关文章请关注PHP中文网!

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