一.函數(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é)果贬蛙。