我跟別人說我精通python,別人問我wrapper是啥,我說不知道,尼瑪,原來wrapper就是裝飾器,熟的不得了啊,英語真是我的克星啊。
閉包 closure
在認識裝飾器之前先認識下閉包
閉包,顧名思義就是把什么東西封閉在保內,什么東西呢?變量和函數。
在一個函數里裝了另一個函數,里面那個函數稱為內部函數,外面那個函數稱為外部函數,
在內部函數里,對在外部作用域(非全局作用域)里的變量進行引用,這個內部函數就稱為閉包
定義在外部函數內但被內部函數引用或調用的變量稱為自由變量,所以閉包又被稱為引用了自由變量的函數。
內部函數和自由變量同時存在構建一個閉包實體,閉包的作用就是使閉包實體可以脫離創建它的環境運行,就是變量和函數脫離了創建它的環境依然存在,而且可執行,這點跟面向對象有些類似。
閉包的語法是一個函數A內創建了一個函數B,並且函數A返回了函數B,函數B就是閉包,函數A內的變量叫自由變量,包括A的參數
示例代碼
def counter(start_at=0): print('act') count=[start_at] # 自由變量 def incr(): # 內部函數 count[0]+=1 return count[0] return incr # 返回一個函數對象 count=counter(5) # act print 111 # 111 print count() # 6 # count 和 incr 脫離了創建它的環境,依然可以運行 print count() # 7 count2=counter(100) # act print 222 # 222 print count2() # 101 print count() # 8
閉包的作用
1. 閉包實際上是對數據或者數據操作的封裝
2. 閉包可以實現一些通用的功能,它是裝飾器的基礎。
裝飾器
裝飾器本質上是個函數,一個用來包裝函數的函數,返回被包裝的函數對象。
被包裝的函數需要作為裝飾器函數的參數。
裝飾器以語法糖@開頭,形式如下
@decorator(dec_opt_args) def func2Bdecorated(func_opt_args):
並且可以有多個裝飾器
@deco2 @deco1 def func(): pass 等價於 func=deco2(deco1(func))
普通方式實現類似裝飾器的功能,以幫助理解
def deco(func): print("before myfunc() called.") func() print(" after myfunc() called.") return func def myfunc(): print(" myfunc() called.") myfunc = deco(myfunc) # before myfunc() called. # myfunc() called. # after myfunc() called. myfunc() # myfunc() called. myfunc() # myfunc() called.
使用語法糖@裝飾函數,注意這並不是裝飾器
def deco(func): print("before myfunc() called.") func() print("after myfunc() called.") return func @deco def myfunc(): print("myfunc() called.") myfunc() # before myfunc() called. # myfunc() called. # after myfunc() called. # myfunc() called. print("*"*5) myfunc() # myfunc() called.
可以看到第一次執行時@起了作用,第二次執行時@沒起作用。為什么呢,往下看
真正的裝飾器來了
無參裝飾器
def deco(func): print(33) def _deco(): print("before myfunc() called.") func() print(" after myfunc() called.") return _deco @deco def myfunc(): print(" myfunc() called.") return 'ok' myfunc() # 33 # before myfunc() called. # myfunc() called. # after myfunc() called. myfunc() # before myfunc() called. # myfunc() called. # after myfunc() called.
可以看到每次執行@都起了作用,但是33只被打印了一次,又是為什么呢?
明明是執行了兩次一模一樣的操作,結果卻不同,看來有必要深入理解一下裝飾器了。
深入理解裝飾器
之前講到閉包類似於面向對象,而裝飾器基於閉包,也應該類似於面向對象咯,或許吧,類似嘛,我又沒說是,所以應該沒錯,
為什么扯這么多,因為我要用class來解釋上面的問題。
對上面的無參裝飾器分析一
## 上述過程類似這樣 def myfunc(): print(" myfunc() called.") return 'ok' bb = deco(myfunc) # 3 bb() # before myfunc() called. # myfunc() called. # after myfunc() called. bb() # before myfunc() called. # myfunc() called. # after myfunc() called.
把裝飾器轉成普通函數,就明了了:
裝飾器內的操作在創建裝飾器實例時已經運行,這可以理解為class的實例化,如果在實例化時有print操作,在實例調用時不會再有
對上面的無參裝飾器分析二
def deco(func): a = 3 def _deco(): print("before myfunc() called.") func() print(a) print(" after myfunc() called.") return _deco @deco def myfunc(): print(" myfunc() called.") return 'ok' myfunc() # before myfunc() called. # myfunc() called. # 3 # after myfunc() called. myfunc() # before myfunc() called. # myfunc() called. # 3 # after myfunc() called.
裝飾器傳遞的是自由變量和閉包,可以理解為class的實例屬性和方法,在實例調用時,屬性一直存在。
myfunc()第一次運行時相當於初始化了裝飾器,后面只是調用實例,雖然它沒有生成實例對象,在這點上不同於class。
總結
裝飾器函數真的類似於面向對象
裝飾器在第一次運行時相當於實例化class,實例化時可以有操作和屬性,操作不被傳遞,屬性被傳遞
裝飾器不需要創建實例對象,運行即相當於實例化class
裝飾器傳遞的是自由變量和屬性,裝飾器函數內的操作不被傳遞
裝飾器的各種語法
有參裝飾器
def deco(func): def _deco(a, b): print("before myfunc() called.") ret = func(a, b) print(" after myfunc() called. result: %s" % ret) return ret return _deco @deco def myfunc(a, b): print(" myfunc(%s,%s) called." % (a, b)) return a + b myfunc(1, 2) # before myfunc() called. # myfunc(1,2) called. # after myfunc() called. result: 3 myfunc(3, 4) # before myfunc() called. # myfunc(3,4) called. # after myfunc() called. result: 7
裝飾器帶參數
外面加一層
def deco(arg): def _deco(func): def __deco(): print("before %s called [%s]." % (func.__name__, arg)) func() print(" after %s called [%s]." % (func.__name__, arg)) return __deco return _deco @deco("mymodule") def myfunc(): print(" myfunc() called.") @deco("module2") def myfunc2(): print(" myfunc2() called.") myfunc() myfunc2()
裝飾器帶類參數
裝飾器的參數是個類,也可以是實例,或者其他
class locker: def __init__(self): print("locker.__init__() should be not called.") @staticmethod def acquire(): print("locker.acquire() called.(這是靜態方法)") @staticmethod def release(): print(" locker.release() called.(不需要對象實例)") def deco(cls): '''cls 必須實現acquire和release靜態方法''' def _deco(func): def __deco(): print("before %s called [%s]." % (func.__name__, cls)) cls.acquire() try: return func() finally: cls.release() return __deco return _deco @deco(locker) def myfunc(): print(" myfunc() called.") myfunc() myfunc()
被裝飾的函數屬性發生了變化
def deco(func): def myfunc(x): return func(x) return myfunc @deco def test1(x): return x+1 print(test1(4)) # 5 print(test1.__name__) # myfunc 名字並非真實名字
名字並非真正函數的名字,而是裝飾器函數里被裝飾的函數的名字
保留被裝飾的函數的屬性
import time import functools def timeit(func): @functools.wraps(func) # 此句就是用來保留被裝飾的函數的屬性的 ,其余跟普通裝飾器一樣 def wrapper(): start = time.clock() func() end =time.clock() print 'used:', end - start return wrapper @timeit def foo(): print 'in foo()' foo() # used: 5.13182184074e-06 print foo.__name__ # foo
name屬性被保留
參考資料:
http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html