前言
在之前我寫過一篇博客,講述了python的閉包和裝飾器,python的裝飾器一直是面試熱點,也是python很重要的特性之一,不過我認為閉包是裝飾器的基礎,比裝飾器擁有更寬廣的概念和作用,所以如果面試官問我關於裝飾器的問題,我都會從閉包的角度去分析裝飾器,關於這個大塊面試官問了我如下幾個問題:什么是裝飾器?什么是閉包?閉包和裝飾器的作用?閉包和裝飾器的使用場景?下面從這幾個問題出發,在前一篇博客的基礎之上更加深刻的探討關於閉包和裝飾器的知識
正文
關於閉包的概念,我從網上找了如下幾種說法:
- This technique of using the values of outside parameters within a dynamic function is called closures
- a closure is simply a function with free variables, where the bindings for all such variables are known in advance
- a closure is a function (object) that remembers its creation environment (enclosing scope)
- This technique by which some data gets attached to the code is called closure in Python
- A closure occurs when a function has access to a local variable from an enclosing scope that has finished its execution
上面的話語基本涵蓋了閉包的特性,關於這些語句的理解,我還是先列舉一個非常簡單的例子:
# coding="utf-8"
def fun(msg):
def nested_fun():
print msg
return nested_fun
new_fun = fun("i like python")
new_fun()
上面的代碼首先定義了一個函數fun,在這個函數之中再定義了一個嵌套函數,之后返回了這個函數,在這個函數之外,重新將fun函數包括msg參數賦值給了new_fun,之后執行new_fun()函數,運行這段代碼,可以發現在console中打印出了i love python這句話。上面的代碼有兩個注意點:1.函數也是一種對象,可以作為參數返回和傳遞;2.new_fun=fun("i like python")這句代碼其實是將new_fun指向了fun函數的返回值nested_fun函數,在執行new_fun函數的時候其實在執行了nested_fun函數,在這個函數中打印出的msg對象其實來自於上層函數,並不是自己的本地變量,為什么還會打印出來呢?所以這就是閉包,根據這個例子可以很好的體會上面列出的幾種說法。
在python中閉包所滿足的條件為:
- 必須擁有嵌套函數
- 嵌套函數調用定義在閉包函數里面的值
- 閉包函數必須返回嵌套函數
裝飾器和閉包可以實現一定的封裝功能,將函數或者變量封裝起來,同時利用他們可以減少代碼冗余,減少一些重復工作量。下面舉一個例子,如果現在我們想實現一個簡單的乘法函數,比如times2(n)函數就是給n*2,times5(n)實現給n乘以5,這個功能可以使用默認值很容易的實現,代碼如下:
def times2(n, x=2):
return n * x
print times2(3)
但是這有個明顯的缺點,每個times()函數都需要進行定義細節,現在可以用閉包來實現,代碼如下:
def multiplier(n):
def multi(x):
return x * n
return multi
times2 = multiplier(2)
times5 = multiplier(5)
print times2(4)
print times5(4)
這段代碼的美妙大家可以好好體會一下。
說完了閉包,再說一說裝飾器,裝飾器就是用函數或者類去封裝(裝飾)另一個對象,裝飾器支持和被封裝的對象相同的接口,所以被裝飾的對象甚至感覺不到自己被裝飾了,在python中,裝飾器更多的是一個函數將另一個函數作為參數,他會利用閉包來實現整個裝飾功能,和上面舉得閉包的例子不同的是,裝飾器利用的是閉包可以捕獲函數。
裝飾器的常用場景如下:
- 注入參數
- 記錄函數行為
- 預處理/后處理
- 修改調用時的上下文
下面簡單的列舉一個記錄函數行為的裝飾器的使用:
def logging(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print func.__name__, args, kwargs
return res
return wrapper
@logging
def fib(n):
if n == 1 or n == 0:
return 1
return fib(n-1) + fib(n-2)
print fib(10)
上面的裝飾器代碼實現了打印被裝飾的函數的執行情況,大家可以自己運行。
總結
語言的美會得到認可和傳播,比如lamdba函數,了解了語言的特性之后,可以極大的提高工作效率,增加代碼的美感,提高代碼的健壯性。