在加強原函數的功能的同時,不修改原函數的定義,這種在代碼運行期間動態增長功能的方式,就稱爲裝飾器(Decorator)。裝飾器,本質是一個返回函數的高階函數。在瞭解裝飾器以前,也簡單介紹下返回函數的相關內容。python
以前,咱們講太高階函數,map()
,filter()
函數等高階函數,可以接受函數做爲參數,而函數一樣也能夠做爲結果值返回。微信
先定義一個簡單的函數:閉包
>>> def func(): ... print("return function")
上述代碼只是普通的一個函數。如果,不須要當即輸出,能夠返回函數,而不是直接輸出:app
>>> def return_func(): ... def func(): ... print("return function") ... return func
當調用 return_func()
時,返回的不是輸出結果,而是函數:函數
>>> f = return_func() >>> f <function return_func.<locals>.func at 0x000002C03AF091E0>
當調用 f
時,才輸出內容:日誌
>>> f() return function
這是返回函數的簡單應用。code
閉包,是指在一個函數內部定義了另外一個函數,而內部函數引用了外部函數的參數和局部變量,當返回內部函數時,相關參數和變量存儲在返回的函數中。orm
下面代碼實現一個閉包的操做:get
>>> def lazy_sum(*args): ... def sum(): ... num = 0 ... for x in args: ... num += x ... return num ... return sum ...
這個例子中,內部函數調用了外部函數的參數 args
,當調用 lazy_sum
時,返回的函數存儲着相關參數和變量。只有當再次調用返回函數,纔會得出運算結果。io
>>> f = lazy_sum(1,2,3,4,5) >>> f <function lazy_sum.<locals>.sum at 0x000002C03AF31488> >>> f() 15
這裏須要注意,每次調用外部函數,返回的函數都是新的函數,即便傳入的參數都相同:
>>> f1 = lazy_sum(1,2,3,4,5) >>> f2 = lazy_sum(1,2,3,4,5) >>> f1 is f2 False >>> f1 == f2 False
該例子中,f1()
和 f2()
的結果互不影響。
還有個須要注意的地方,返回函數不是馬上執行,而是調用了 f()
才執行。嘗試用另一個例子說明這種狀況,示例以下:
>>> def count(): ... lst = [] ... for i in range(1, 4): ... def func(): ... return i * i ... lst.append(func) ... return func ... >>> f1, f2, f3 = count()
在這個例子中,每次循環,都建立一個新的函數,將建立的 3 個函數返回。
這裏的結果,可能會猜想調用 f1(), f2(), f3()
結果分別是 1, 4, 9
,但實際結果卻都是 9
:
>>> f1() 9 >>> f2() 9 >>> f3() 9
這裏是由於返回的函數引用了變量 i
,可是沒有馬上執行。等 3 個函數都返回時,引用的變量 i
的值已經所有變成了 3
,因此最終結果是 9
。
因此,返回閉包時,返回函數不要引用循環變量,或者後續會發生變化的值。
前面已經說明,裝飾器,本質上是一個返回函數的高階函數。嘗試用例子說明,
>>> def log(func): ... def warpper(*args, **kw): ... print('call {}():'.format(func.__name__) ... return func(*args, **kw) ... return wrapper
上面的 log
是一個裝飾器,接受函數做爲參數,返回函數。藉助 Python 的 @
語法,把裝飾器置於函數的定義處:
@log def func(): print("function name")
調用 func()
函數,會運行 func()
自己函數,還會在運行 func()
前,打印一行日誌:
>>> func() call func(): function name
在這裏,將 @log
放到 func()
函數的定義處,至關於下列語句:
func = log(func)
因爲 log()
是裝飾器,返回一個函數。可是,原來的 func()
還存在,只是如今同名的 func()
指向了新的函數,因此調用的 func()
時,執行的將是 wrapper()
函數。在 wrapper()
函數內,首先先打印日誌,而後再調用原始函數。
這些就是裝飾器的一些內容,至於更深刻的部分,後續會繼續更新介紹。
以上就是本篇的主要內容
題外話: 近期網上散佈不少的謠言,致使不少人沒法正確識別真假。下面的連接,是騰訊新聞的一個平臺【較真】,實時給你們闢謠以及科普,能夠幫助你們分清真相。儘可能作到不傳謠不信謠。
歡迎關注微信公衆號《書所集錄》