Python裝飾器的一點解讀
版權申明:本文為部落客窗戶(Colin Cai)原創,歡迎轉帖。如要轉貼,必須註明原文網址 //www.cnblogs.com/Colin-Cai/p/12977127.html 作者:窗戶 QQ/微信:6679072 E-mail:[email protected]
理論上,函數是一等公民(first class function)的語言都可以使用函數式編程,從而利用運算元(高階函數)來做裝飾器。
裝飾器一般是這樣一個運算元,它接受一個函數作為參數,返回另外一個函數。裝飾器,顧名思義,就是把一個函數「裝飾」一下,得到另外一個函數。為何要裝飾一下呢?目的一般是可能設計上需要對函數做一些改裝,比如原函數輸出結果需要再加工加工,或者原函數的輸入參數傳入不一樣,或者兩者兼有之,等等。
迭代是編程中常用的手段,它的計算方式表現為狀態的不斷變換,且狀態的變換具有唯一性。
比如我們使用Scheme來表示迭代。
;stat代表當前狀態,next代表狀態改變函數,final?代表判斷狀態是否終止 (define (iterate-orgin stat next final?) (if (final? stat) stat (iterate-orgin (next stat) next final?))) ;將next函數和final?函數封成一個函數 (define (iterate stat f-stat) (iterate-orgin stat (f-stat 'next) (f-stat 'final))) ;最終我們需要的迭代函數 (define (it f-stat) (lambda (stat) (iterate stat f-stat)))
以上構造出一個運算元it,就是用來「裝飾」迭代的函數。
我們構造一個對list求和的迭代:
可以每次把list的前面兩個相加,比如對(1 2 3 4 5)求和,經過以下狀態:
(1 2 3 4 5)
(3 3 4 5)
(6 4 5)
(10 5)
(15)
15
得到最後結果15。
程式碼可以如下:
(define (make-sum-func sym) (if (eq? sym 'next);next函數 (lambda (lst) (if (pair? lst) (if (null? (cdr lst)) (car lst) (cons (+ (car lst) (cadr lst)) (cddr lst))) lst)) (if (eq? sym 'final?);final?函數 (lambda (lst) (not (pair? lst))) '())))
然後測試一下((it make-sum-func) ‘(1 2 3 4 5)),得到最後結果15。
上面兩個函數寫在一起,我們還可以再分離一下。
;定義一個打包函數 (define (wrap-next-final next final?) (lambda (sym) (if (eq? sym 'next) next (if (eq? sym 'final?) final? '())))) ;下面next和final?兩個函數可以分開寫 (define make-sum-next (lambda (lst) (if (pair? lst) (if (null? (cdr lst)) (car lst) (cons (+ (car lst) (cadr lst)) (cddr lst))) lst))) (define make-sum-final? (lambda (lst) (not (pair? lst)))) ;於是函數就可以下面這樣表示 (define make-sum-func (wrap-next-final make-sum-next make-sum-final?))
總而言之,裝飾器就是這樣一類運算元。
Python也是這樣,只是Python提供了@這樣的語法,實際上是個語法糖,與其說是簡寫,倒是更像是個語法提醒這是一個裝飾器。
我們這次希望來顯示一下mysym,還是用求和。
先寫一個簡單的mysum函數用於求和:
def mysum(*args): ret = 0 for i in args: ret += i return ret
做一個運算元,來擴充它的輸入參數:
這裡需要用來判斷一個對象是否是可迭代對象,
from collections import Iterable
然後,如果判斷對象x是否是可迭代對象,只需要:
isinstance(x, Iterable)
運算元如下:
from collections import Iterable def args_expan(f): def f2(*args): lst = [] for i in args: if isinstance(i, Iterable): lst.append(f(*i)) else: lst.append(i) return f(*lst) return f2
然後在mysum前添加裝飾器標誌
@args_expan def mysum(*args): ret = 0 for i in args: ret += i return ret
測試如下:
print(mysum(1,2,3,4,5)) print(mysum((1,2,3,4,5))) print(mysum([1,2,3,4,5])) print(mysum(range(1,6))) print(mysum(map(lambda x:x+1, range(5)))) print(mysum(filter(lambda x:x<6, range(10)))) #構造了一個生成器 def gen_range(a, b): while a < b: yield a a += 1 print(mysum(\ filter(lambda x:x<6, range(10)), \ 6, \ [7,8], \ (9, 10), \ map(lambda x:x+11, range(10)), \ gen_range(21,101)))
運行得到:
15
15
15
15
15
15
5050
終於看到,函數功能已被擴充。
這個裝飾器還可以裝飾別的函數,比如乘積、統計等。
從而,裝飾器就是這樣一個運算元,一般用來改造函數的輸入或輸出,避免重複寫程式碼。