先來看一段簡單的代碼
def add():
a = 2
b = 5
def func(c):
return a*c+b
return func
在函數func裏面用到了外面的參數a, b(稱爲函數的環境變量)。像這樣,函數與其環境變量就構成了閉包。簡單地理解呢,就是嵌套函數。
通常地,函數被調用之後其命名空間隨着函數的結束而結束,但是在閉包中不然,外部函數的命名空間和內部的結合到了一起。
當我們調用外層函數時,返回內層函數,特別注意!!返回的是一個函數。
續上面的代碼
a = add()
print a
# <function func at 0xb70c9f7c>
a(5) # output: 15
可見調用add後,返回func函數,我們讓a指向了這個新函數,直接print的話並沒有結果,唯有我們再次調用的時候才計算。
下面我們通過一個例子來了解一下閉包使用時的注意事項
def count():
fs = []
for i in range(1, 4):
def f():
return i * i
fs.append(f)
return fs
f1, f2, f3 = count()
print f1, f2, f3 # 9 9 9
調用count()的時候便返回三個函數,都放在fs的列表裏,最後由f1,f2,f3分別指向。但是爲什麼我們打印出來的時候不是:1,4,9而是9,9,9呢。
這就是我們使用閉包時要注意的地方了!!返回來的是函數而不是計算結果,而其中的i值仍然在自增,當我們打印的時候,i已經是3了,我們調用f1,f2,f3的時候才真正進行了運算,這時運算的數據是3。所以,當我們使用閉包的時候要注意內部函數不要使用循環變量。
如果一定要用的話,也不是沒有辦法,再加一個函數“記住”變量值就好了。像這樣
def count():
fs = []
for i in range(1, 4):
def f(j):
def g():
return j * j
return g
fs.append(f(i))
return fs
f1, f2, f3 = count()
這時候,f1,f2,f3對應的是被返回來的f(1), f(2), f(3)
好了,瞭解閉包後我們就可以涉及一下裝飾器了。所謂裝飾器就是實現一些小功能(比如在函數操作前後輸出打印點什麼),又能不讓代碼嵌入其中。通常我們用@語法把裝飾器,就是實現小功能的函數放在另一個函數的上一行。
例1
def log(func):
def wrapper(*args, **kw):
print 'call %s()' % func.func_name
return func(*args, **kw)
return wrapper
@log # now* = log(now)
def now():
now.func_name = "now"
print "2016-7-17"
now() # 調用的是now*
#call now
#2016-7-17
“調用的是now*”的意思是這時的now指向的是由log()函數返回來的wrapper()函數。下面的例子寫一個三層循環,可以打印附加文字。
例2
def log(text):
def decorator(func):
def wrapper(*arg, **kw):
print '%s %s()' %(text, func.func_name)
return func(*arg, **kw)
return wrapper
return decorator
@log('execute')
def time():
time.func_name = "time"
print "time for now is 2016.7.17"
time()
#execute time
#2016.7.17
如果再加一點修改,可以把上面兩個例子:例1和例2整合
calllable()判斷是否可調用。顯然,對象是函數的話返回true,字符串的話返回false
def log2(text): # text有可能是文本,也可能是函數
if callable(text): # if callable(text)=true then text 是函數
def wrapper(*args, **kw):
print 'begin call: ' + text.func_name
text(*args, **kw)
print 'end call'
return wrapper
else: # if false, text是文本,接下來是三層循環
def decorator(func):
def wrapper(*args, **kw):
print "begin call: " + text
func(*args, **kw)
print 'end call: '
return wrapper
return decorator
最後我們來看兩個稍複雜的例子加深印象和理解:
下面例子中,我定義了全局變量base,但是com方法中又定義了,最後wrapper函數使用的會是重新定義了的這個base(=7)。當定義了一個函數時,函數內的變量構成該函數的名字空間,前面提到的:在閉包裏面,內部函數的命名空間與外部函數結合到了一起,這也是一種將命名空間靜態化的方法。
base = 2
def com(func):
base = 7
# print "enter com"
def wrapper(value):
if value > base:
print "wrapper " + str(base) # wrapper 7
return func(value)
else:
return func(base)
return wrapper
@com
def sub_ten_sqart(value):
return math.sqrt(value - 7)
print sub_ten_sqart(11) # 2.0
print sub_ten_sqart(5) # 0.0
你覺得下面這段代碼的輸出會是怎樣的呢?
def demo_1(func):
print "enter demo_1"
def wrapper(a, b):
print "enter demo_1's wrapper"
func(a, b)
return wrapper
def demo_2(func):
print "enter demo_2"
def wrapper(a, b):
print "enter demo_2's wrapper"
func(a, b)
return wrapper
@demo_1
@demo_2
def add_func(a, b):
print "the result is %d" %(a+b)
add_func(1, 2)
輸出是
"enter demo_2"
"enter demo_1"
"enter demo_1's wrapper"
"enter demo_2's wrapper"
"the result is 3"
你對了嗎~
當被兩個裝飾器修飾時,從最裏層開始往外應用。
也就是說,過程相當於是這樣的:
add_func = demo_2(add_func)
打印輸出”enter demo_1”並且此時add_func變成了 wrapper(add_func)函數,但是該wrapper裏面還未執行。
緊接着:add_func = demo_1(add_func)=demo_1(wrappeer(add_func))
打印”enter demo_2”,傳遞給demo_1裏面的wrapper()的add_func其實是來自demo_2的wrapper,所以在demo_1的wrapper裏面,先打印”enter demo_1’s wrapper”,最後才執行了demo_2中的wrapper。
所以,對於Python的裝飾器,要記住它被返回的是一個函數,是裝飾器裏面定義的另一個函數!