Python裝飾器筆記

一.函數(shù)裝飾器

1.從Python內(nèi)層函數(shù)說起

首先我們來探討一下這篇文章所講的內(nèi)容Inner Functions - What Are They Good For?中文版

使用內(nèi)層函數(shù)的三個好處

  • 封裝
  • 貫徹DRY原則
  • 閉包和工廠函數(shù)

1.封裝

def outer(num1):
    def inner_increment(num1):  # hidden from outer code
        return num1 + 1
    num2 = inner_increment(num1)
    print(num1, num2)
 
inner_increment(10) #不能正確運行
# outer(10) #可以正常運行

這樣把內(nèi)層函數(shù)從全局作用域隱藏起來言沐,不能直接調(diào)用邓嘹。

使用這種設(shè)計模式的一個主要優(yōu)勢在于:在外部函數(shù)中對全部參數(shù)執(zhí)行了檢查,你可以在內(nèi)部函數(shù)中跳過全部的檢查過程险胰。

2.貫徹DRY原則

比如汹押,你可能寫了一個函數(shù)用來處理文件,并且你希望它既可以接受一個打開文件對象或是一個文件名:

def process(file_name):
    def do_stuff(file_process):
        for line in file_process:
            print(line)
    if isinstance(file_name, str):
        with open(file_name, 'r') as f:
            do_stuff(f)
    else:
        do_stuff(file_name)

3.閉包和工廠函數(shù)

閉包無非是使內(nèi)層函數(shù)在調(diào)用時記住它當(dāng)前環(huán)境的狀態(tài)起便。初學(xué)者經(jīng)常認(rèn)為閉包就是內(nèi)層函數(shù)棚贾,而且實際上它是由內(nèi)層函數(shù)導(dǎo)致的窖维。閉包在棧上“封閉”了局部變量,使其在棧創(chuàng)建執(zhí)行結(jié)束后仍然存在妙痹。

def generate_power(number):
   
# define the inner function ...
    def nth_power(power):
        return number ** power
    
# ... which is returned by the factory function
    return nth_power
>>raise_two = generate_power(2)

>>print(raise_two(7))
    
128

外層函數(shù)接受一個參數(shù)number=2铸史,然后生成一個nth_power()函數(shù),該函數(shù)只接受一個單一的參數(shù)power怯伊,其中包含number=2

返回的函數(shù)被賦值給變量raise_two琳轿,我們可以通過raise_two來調(diào)用函數(shù)并傳遞變量。

換句話說耿芹,閉包函數(shù)“初始化”了nth_power()函數(shù)并將其返回≌复郏現(xiàn)在無論你何時調(diào)用這個新返回的函數(shù),它都會去查看其私有的快照吧秕,也就是包含number=2的那一個媚送。

2.裝飾器

「裝飾器」是一個很著名的設(shè)計模式,經(jīng)常被用于有切面需求的場景寇甸,較為經(jīng)典的有插入日志塘偎、性能測試、事務(wù)處理

裝飾器其實就是一個工廠函數(shù)拿霉,它接受一個函數(shù)為參數(shù)吟秩,然后返回一個新函數(shù),其閉包中包含了原函數(shù)

1.簡單裝飾器

def deco(func):
    def wrapper():
        print "start"
        func() #調(diào)用函數(shù)
        print "end"
    return wrapper

@deco
def myfun():
    print "run"

myfun()

由于裝飾器函數(shù)返回的是原函數(shù)的閉包wrapper绽淘,實際上被裝飾后的函數(shù)就是wrapper涵防,其運行方式就和wrapper一樣。

相當(dāng)于
myfun=deco(myfun)

2.裝飾一個需要傳遞參數(shù)的函數(shù)

def deco(func):
    def wrapper(param):
        print "start"
        func(param)
        print "end"
    return wrapper


@deco
def myfun(param):
    print "run with param %s"%(param)


myfun("something")

這種情況下沪铭,仍然返回wrapper壮池,但是這個wrapper可以接受一個參數(shù),因此這樣的裝飾器只能作用于接受一個參數(shù)的函數(shù)

3.裝飾任意參數(shù)的函數(shù)

def deco(func):
    def warpper(*args,**kw):
        print "start"
        func(*args,**kw)
        print "end"
    return warpper


@deco
def myfun1(param1):
    print "run with param %s"%(param1)

@deco
def myfun2(param1,param2):
    print "run with param %s and %s"%(param1,param2)

myfun1("something")
myfun2("something","otherthing")

# start
# run with param something
# end
# start
# run with param something and otherthing
# end

兩個函數(shù)可以被同樣一個裝飾器所裝飾

4.帶參數(shù)的裝飾器

裝飾器接受一個函數(shù)作為參數(shù)杀怠,這個毋庸置疑椰憋。但是有時候我們需要裝飾器接受另外的參數(shù)。此時需要再加一層函數(shù)赔退,實際上是定義了一個生成裝飾器的工廠函數(shù)橙依,調(diào)用它,搭配需要的參數(shù)硕旗,來返回合適的裝飾器窗骑。

def log(text):
    def deco(func):
        def wrapper(*args,**kw):
            print text
            func(*args,**kw)
            print text + " again"
        return wrapper
    return deco


@log("hello")
def myfun(message):
    print message


myfun("world")

# hello
# world
# hello again

這里分兩步

  • log=log("hello"),把返回的deco函數(shù)賦值給log漆枚,此時log相當(dāng)于其包含text=“hello”的閉包
  • myfun=log(myfun)创译,相當(dāng)于把myfun傳入了deco函數(shù),并且返回wrapper墙基,并賦值給myfun软族,此時myfun相當(dāng)于其裝飾后的閉包辛藻。

整體來看是myfun=log("hello")(myfun)

5.裝飾器帶參數(shù)

# -*- coding:gbk -*-  
'''''示例8: 裝飾器帶類參數(shù)'''  
  
class locker:  
    def __init__(self):  
        print("locker.__init__() should be not called.")  
         
    @staticmethod  
    def acquire():  
        print("locker.acquire() called.(這是靜態(tài)方法)")  
         
    @staticmethod  
    def release():  
        print("  locker.release() called.(不需要對象實例)")  
  
def deco(cls):  
    '''''cls 必須實現(xiàn)acquire和release靜態(tài)方法'''  
    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()  

關(guān)于wrapper的返回值

上面的代碼中,我們的wrapper函數(shù)都沒有返回值互订,而是在wrapper中直接調(diào)用了func函數(shù)吱肌,這么做的目的是要在函數(shù)運行前后打印一些字符串。而func函數(shù)本事也只是打印字符串而已仰禽。

但是這么做有時會違背func函數(shù)的初衷氮墨,比如func函數(shù)確實是需要返回值的,那么其裝飾后的函數(shù)wrapper也應(yīng)該把值返回吐葵。

我們看這樣一段函數(shù):

def deco(func):
    def warpper(*args,**kw):
        print "start"
        func(*args,**kw)#直接調(diào)用规揪,無返回值
        print "end"
    return warpper

@deco
def myfun(param):
    return 2+param
    
sum=myfun(2) #期望紀(jì)錄返回值并打印
print sum

結(jié)果,并沒有返回值

>>
start
end
None

因此我們需要wrapper把函數(shù)結(jié)果返回:

def deco(func):
    def warpper(*args,**kw):
        print "start"
        result=func(*args,**kw)#紀(jì)錄結(jié)果
        print "end"
        return result #返回
    return warpper
@deco
def myfun(param):
    return 2**param


sum=myfun(2) #這里其實是sum=result
print sum

當(dāng)然温峭,如果不是為了在func前后打印字符串猛铅,也可以把func直接返回

一個實際例子:統(tǒng)計函數(shù)執(zhí)行時間

from time import time,sleep
def timer(func):
    def warpper(*args,**kw):
        tic=time()
        result=func(*args,**kw)
        toc=time()
        print "%f seconds has passed"%(toc-tic)
        return result
    return warpper

@timer
def myfun():
    sleep(2)
    return "end"

print myfun()

# 2.005432 seconds has passed
# end

關(guān)于裝飾器裝飾過程中函數(shù)名稱的變化

當(dāng)裝飾器裝飾函數(shù)并返回wrapper后,原本myfun的__name__就改變了

from time import time,sleep

def timer(func):
    def warpper(*args,**kw):
        tic=time()
        result=func(*args,**kw)
        toc=time()
        print func.__name__
        print "%f seconds has passed"%(toc-tic)
        return result
    return warpper

@timer
def myfun():
    sleep(2)
    return "end"

myfun()
print myfun.__name__ #wrapper

# myfun
# 2.003399 seconds has passed
# warpper

這樣對于一些依賴函數(shù)名的功能就會失效凤藏,而且也不太符合邏輯奸忽,畢竟wrapper對于我們只是一個中間產(chǎn)物

from time import time,sleep
import functools

def timer(func):
    @functools.wraps(func)
    def warpper(*args,**kw):
        tic=time()
        result=func(*args,**kw)
        toc=time()
        print func.__name__
        print "%f seconds has passed"%(toc-tic)
        return result
    return warpper


@timer
def myfun():
    sleep(2)
    return "end"

myfun()
print myfun.__name__ #wrapper

# myfun
# 2.003737 seconds has passed
# myfun

導(dǎo)入模塊import functools,并且用@functools.wraps(func)裝飾wrapper即可

3.Flask中的@app.route()裝飾器

Things which aren't magic - Flask and @app.route - Part 1

Things which aren't magic - Flask and @app.route - Part 2

class NotFlask():
    def route(self, route_str):
        def decorator(f):
            return f
 
        return decorator
 
app = NotFlask()
 
@app.route("/")
def hello():
    return "Hello World!"

route是NotFlask類的一個方法揖庄,并且其實際上是一個裝飾器工廠栗菜,這里我們并沒有裝飾我們的函數(shù),裝飾器僅僅返回了函數(shù)的引用而沒有裝飾它蹄梢。

class NotFlask():
    def __init__(self):
        self.routes = {}
 
    def route(self, route_str):
        def decorator(f):
            self.routes[route_str] = f
            return f
 
        return decorator
 
app = NotFlask()
 
@app.route("/")
def hello():
    return "Hello World!"

現(xiàn)在給裝飾器初始化一個字典疙筹,在我們傳入?yún)?shù)生產(chǎn)裝飾器route的時候,把函數(shù)存入字典響應(yīng)位置禁炒,key為url字符串而咆,value為相應(yīng)函數(shù)。

不過此時幕袱,我們并不能訪問這個內(nèi)部的視圖函數(shù)暴备,我們需要一個方法來獲取相應(yīng)的視圖函數(shù)。

class NotFlask():
    def __init__(self):
        self.routes = {}
 
    def route(self, route_str):
        def decorator(f):
            self.routes[route_str] = f
            return f
 
        return decorator
 
    def serve(self, path):
        view_function = self.routes.get(path)#獲取相應(yīng)函數(shù)
        if view_function:
            return view_function()#返回函數(shù)
        else:
            raise ValueError('Route "{}"" has not been registered'.format(path))
 
app = NotFlask()
 
@app.route("/")
def hello():
    return "Hello World!"

然后我們可以這樣凹蜂,通過url字符串來訪問相應(yīng)的視圖函數(shù)

app = NotFlask()
 
@app.route("/")
def hello():
    return "Hello World!"
 
print app.serve("/")

#>>Hello World!

小結(jié)

Flask路由裝飾器的主要功能馍驯,就是綁定url到相應(yīng)的函數(shù)。
(如何訪問視圖函數(shù)其實是HTTP服務(wù)器的一部分)


當(dāng)然玛痊,目前的url綁定還太死板,我們需要url能夠加入可變參數(shù)

下面我們要實現(xiàn)從url中識別出參數(shù)

app = Flask(__name__)
 
@app.route("/hello/<username>")
def hello_user(username):
    return "Hello {}!".format(username)

首先我們要利用命名捕獲組狂打,從url中識別參數(shù)

route_regex = re.compile(r'^/hello/(?P<username>.+)$')
match = route_regex.match("/hello/ains")
 
print match.groupdict()

當(dāng)然擂煞,我們需要一個方法來把輸入的url轉(zhuǎn)化為相應(yīng)的正則表達(dá)式

def build_route_pattern(route):
    route_regex = re.sub(r'(<\w+>)', r'(?P\1.+)', route)
    return re.compile("^{}$".format(route_regex))
 
print build_route_pattern('/hello/<username>')
class NotFlask():
    def __init__(self):
        self.routes = []

    # Here's our build_route_pattern we made earlier
    @staticmethod
    def build_route_pattern(route):
        route_regex = re.sub(r'(<\w+>)', r'(?P\1.+)', route)
        return re.compile("^{}$".format(route_regex))

    def route(self, route_str):
        def decorator(f):
            # Instead of inserting into a dictionary,
            # We'll append the tuple to our route list
            route_pattern = self.build_route_pattern(route_str)
            self.routes.append((route_pattern, f))

            return f

        return decorator

與之前的代碼不同,字典被移除了趴乡,取而代之的是一個列表对省,然后我們把生成的正則表達(dá)式和相應(yīng)的函數(shù)作為元組放到列表里蝗拿。

同樣,我們需要一個方法來返回視圖函數(shù)蒿涎,當(dāng)然哀托,還有捕獲匹配組的字典,我們需要它來傳遞正確的參數(shù)

def get_route_match(path):
    for route_pattern, view_function in self.routes:
        m = route_pattern.match(path)
        if m:
           return m.groupdict(), view_function
 
    return None

最終結(jié)果:

class NotFlask():
    def __init__(self):
        self.routes = []

    @staticmethod
    def build_route_pattern(route):
        route_regex = re.sub(r'(<\w+>)', r'(?P\1.+)', route)
        return re.compile("^{}$".format(route_regex))

    def route(self, route_str):
        def decorator(f):
            route_pattern = self.build_route_pattern(route_str)
            self.routes.append((route_pattern, f))

            return f

        return decorator

    def get_route_match(self, path):
        for route_pattern, view_function in self.routes:
            m = route_pattern.match(path)
            if m:
                return m.groupdict(), view_function

        return None

    def serve(self, path):
        #查找和path匹配的視圖函數(shù)以及捕獲組字典
        route_match = self.get_route_match(path)
        if route_match:
            kwargs, view_function = route_match
            return view_function(**kwargs)#捕獲組字典作為函數(shù)參數(shù)
        else:
            raise ValueError('Route "{}"" has not been registered'.format(path))

使用方法:

app = NotFlask()
 
@app.route("/hello/<username>")
def hello_user(username):
    return "Hello {}!".format(username)
 
print app.serve("/hello/ains")
>>Hello ains!

小結(jié)

裝飾階段:

  • 裝飾器工廠route接受url字符串劳秋,生成一個合適的裝飾器
  • 裝飾器裝飾視圖函數(shù)仓手,生成url字符串對應(yīng)的正則表達(dá)式模板,連同視圖函數(shù)組成元組玻淑,存放在列表中嗽冒。然后把函數(shù)返回。

調(diào)用階段:

  • app.serve并傳入url的時候补履,首先在列表中查找添坊,依次進(jìn)行匹配,是否有符合該模式的路徑和視圖函數(shù)
  • 有則返回相應(yīng)獲取捕獲組字典和視圖函數(shù)
  • 將字典作為參數(shù)箫锤,返回該視圖函數(shù)的運行結(jié)果贬蛙。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谚攒,隨后出現(xiàn)的幾起案子速客,更是在濱河造成了極大的恐慌,老刑警劉巖五鲫,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溺职,死亡現(xiàn)場離奇詭異,居然都是意外死亡位喂,警方通過查閱死者的電腦和手機(jī)浪耘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塑崖,“玉大人七冲,你說我怎么就攤上這事」嫫牛” “怎么了澜躺?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抒蚜。 經(jīng)常有香客問我掘鄙,道長,這世上最難降的妖魔是什么嗡髓? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任操漠,我火速辦了婚禮,結(jié)果婚禮上饿这,老公的妹妹穿的比我還像新娘浊伙。我一直安慰自己撞秋,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布嚣鄙。 她就那樣靜靜地躺著吻贿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哑子。 梳的紋絲不亂的頭發(fā)上舅列,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機(jī)與錄音赵抢,去河邊找鬼剧蹂。 笑死,一個胖子當(dāng)著我的面吹牛烦却,可吹牛的內(nèi)容都是我干的宠叼。 我是一名探鬼主播其爵,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼冒冬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了摩渺?” 一聲冷哼從身側(cè)響起简烤,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摇幻,沒想到半個月后横侦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡绰姻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年枉侧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狂芋。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡榨馁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出帜矾,到底是詐尸還是另有隱情翼虫,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布屡萤,位于F島的核電站珍剑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏灭衷。R本人自食惡果不足惜次慢,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翔曲。 院中可真熱鬧迫像,春花似錦、人聲如沸瞳遍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掠械。三九已至由缆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間猾蒂,已是汗流浹背均唉。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留肚菠,地道東北人舔箭。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像蚊逢,于是被迫代替她去往敵國和親层扶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 裝飾器的主要作用:代碼復(fù)用烙荷、裝飾函數(shù)! 簡單裝飾器 現(xiàn)在有三個函數(shù) 每個函數(shù)都有自己的功能! 這時我想讓這些函數(shù)在...
    學(xué)編程的Dreamer閱讀 677評論 0 8
  • 每個人都有的內(nèi)褲主要功能是用來遮羞镜会,但是到了冬天它沒法為我們防風(fēng)御寒,咋辦终抽?我們想到的一個辦法就是把內(nèi)褲改造一下戳表,...
    chen_000閱讀 1,360評論 0 3
  • 要點: 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個“式” 模塊:如何使用模塊 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍钪绨椤傩浴?..
    victorsungo閱讀 1,468評論 0 6
  • 作者:周珈名 我愛唱歌匾旭,我知道歌聲沒有終點。但我不管我長多大有多遠(yuǎn)亩码,但我一直都會學(xué)習(xí)音樂季率,因為我要成為歌手!
    夢見騎術(shù)閱讀 263評論 1 1
  • 01 在以前的文章《詩詞大會與武亦姝描沟,有沒有勾起你回憶里的美好與孤獨》這一篇中飒泻,曾大致講過: 小時候,沒有手機(jī)吏廉,沒...
    觀觀之洲閱讀 409評論 0 0