距離我上一次寫文章到現在已經頗有一段時間了,我想差不多也該在部落格裡開始新的系列了。
本文是我稱為「這不是魔法」系列的第一篇,我準備在裡面展示一些熱門開源包提供的友好API是如何通過它們各自語言的原始語法構造的。
本文我們先來說說Flask,深入探討Flask如何實現在函式上方寫“@app.route()”就能在因特網上輸出函式的執行結果。
下面是Flask主頁給我們的第一個例子,我們現在就由它入手,深入理解“@app.route()”是如何工作的。
1 2 3 4 5 |
app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" |
@app.route和其它裝飾器
要想明白“@app.route()”的工作原理,我們首先需要看一看Python中的裝飾器(就是以“@”開頭的那玩意,下面接著函式定義)。
究竟什麼是裝飾器?沒啥特別的。裝飾器只是一種接受函式(就是那個你用“@”符號裝飾的函式)的函式,並返回一個新的函式。
當你裝飾一個函式,意味著你告訴Python呼叫的是那個由你的裝飾器返回的新函式,而不僅僅是直接返回原函式體的執行結果。
還不是很明白?這裡是一個簡單的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# This is our decorator def simple_decorator(f): # This is the new function we're going to return # This function will be used in place of our original definition def wrapper(): print "Entering Function" f() print "Exited Function" return wrapper @simple_decorator def hello(): print "Hello World" hello() |
執行上述程式碼會輸出以下結果:
Entering Function
Hello World
Exited Function
很好!
現在我們有點明白怎樣建立我們自己的“@app.route()”裝飾器了,但你可能會注意到有一個不同點,就是我們的simple_decorator不可以接受任何引數, 但“@app.route()”卻可以。
那麼我們怎樣才能給我們的裝飾器傳引數?要實現這個我們只需建立一個“decorator_factory”函式,我們呼叫這個函式,返回適用於我們函式的裝飾器。現在看看如果實現它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def decorator_factory(enter_message, exit_message): # We're going to return this decorator def simple_decorator(f): def wrapper(): print enter_message f() print exit_message return wrapper return simple_decorator @decorator_factory("Start", "End") def hello(): print "Hello World" hello() |
給我們的輸出是:
請注意在我們寫@decorator_factory(“Start”, “End”)時,我們實際呼叫的是decorator_factory函式,實際返回的裝飾器已經被用上了,程式碼很整潔,對吧?
Start
Hello World
End
把“app”放進“app.route”
現在我們掌握了裝飾器怎樣工作的全部前置知識 ,可以重新實現Flask API的這個部分了,那麼把我們的目光轉移到“app”在我們Flask應用中的重要地位上面來。
在開始解釋Flask物件裡面發生了什麼之前,我們先建立我們自己的Python類NotFlask。
1 2 3 4 |
class NotFlask(): pass app = NotFlask() |
這不是個很有趣的類,不過有一樣值得注意,就是這個類的方法也可以被用作裝飾器,所以讓我們把這個類寫得更有趣一點,加一個稱作 route的方法,它是一個簡單的裝飾器工廠。
1 2 3 4 5 6 7 8 9 10 11 12 |
class NotFlask(): def route(self, route_str): def decorator(f): return f return decorator app = NotFlask() @app.route("/") def hello(): return "Hello World!" |
這個裝飾器和我們之前建立的那些最大的不同,在於我們不想修改被我們裝飾的函式的行為,我們只是想獲得它的引用。
所以,最後一步是我們打算去利用一個特性,就是用裝飾器函式的副產品去儲存一個提供給我們的路徑之間的連結,裝飾器函式應該與它關聯起來。
為了實現這個,我們給我們的NotFlask物件加一個“routes”字典,當我們的“decorator”函式被呼叫,路徑將被插入新字典中函式對應的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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!" |
現在我們就要完成了!可如果沒法訪問內部的檢視函式,儲存路徑的字典又有什麼用?讓我們加入一個方法serve(path),當給定的路徑存在時執行一個函式並給們我結果,當路徑尚未註冊時則丟擲一個異常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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) if view_function: return view_function() else: raise ValueError('Route "{}"" has not been registered'.format(path)) app = NotFlask() @app.route("/") def hello(): return "Hello World!" |
在這個系列我們只關注重現那些熱門庫提供的友好API,所以鉤掛“serve”方法實現一個HTTP伺服器其實有一點超出本文的範圍,當然結果是確定的,執行下述片段:
1 2 3 4 5 6 7 |
app = NotFlask() @app.route("/") def hello(): return "Hello World!" print app.serve("/") |
我們會看到:
Hello World!
我們已經完成了一個的Flask網頁上第一個例子的非常簡單的重現,讓我們寫一些快速測試檢測我們簡單重現的Flask的“@app.route()”是否正確。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class TestNotFlask(unittest.TestCase): def setUp(self): self.app = NotFlask() def test_valid_route(self): @self.app.route('/') def index(): return 'Hello World' self.assertEqual(self.app.serve('/'), 'Hello World') def test_invalid_route(self): with self.assertRaises(ValueError): self.app.serve('/invalid') |
吸口氣。
完全正確!所以,僅僅是一個簡單的包含一個字典的裝飾器, 就重現了Flask的“app.route()”裝飾器的基本的行為。
在本系列的下一篇,也是Flask的app.route()的最後一篇,將通過解析下面這個例子來解釋動態URL模式是如何工作。
1 2 3 4 5 |
; html-script: false ]app = Flask(__name__) @app.route("/hello/<username>") def hello_user(username): return "Hello {} !".format(username) |
待續!