廖大python實戰專案第五天
PS: 決定還是堅持寫部落格記錄一下比較好。
今天的實戰內容是編寫web框架,如果之前的知識不熟悉的話確實看不大懂。在這裡奉上自己的理解以及幫助理解的相關資料和文件。
Web框架
首先我們要知道web框架是什麼東西,它到底要怎麼實現。這一點廖大在web開發的WSGI介面、使用web框架這兩篇文章裡已經說過了。摘要一些略作說明:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h1>Hello, web!</h1>']
上面這段程式碼實現了響應HTTP請求,其中start_response
傳送了HTTP響應的Header
,return
則將HTTP響應的body
返回。在這裡面,如何接受、解析HTTP請求和傳送HTTP響應不是我們關注的重點,我們該做的是決定用什麼內容去響應HTTP請求,而其餘部分交給WSGI介面去實現。這裡已經出現了第一層的抽象,讓我們擺脫煩雜的底層邏輯。
然而單單實現這個還是不夠的,對比WebApp的處理邏輯,這裡還是有些低階。一個url對應一個HTTP請求,而HTTP請求可分為GET
,POST
,PUT
,DELETE
等等,我們自己用一個一個的函式實現不大現實。所以我們需要對WSGI介面
回到廖大的這一天的實戰內容,是要對aiohttp
封裝一個更高層次的web框架。為了看懂aiohttp
實現HTTP響應的邏輯(整體和同步一樣但是還是有一些差別影響理解),我們需要先把之前的非同步IO那四篇都回顧一下,在這裡,aiohttp
官方文件還有Server Usage可以幫助我們更好地理解request
,Response
,Application
這三個方法的功能。
涉及到模組渲染和裝飾器我一開始有些不清楚,補一下對應的章節就明白了。
__call__()方法
廖大提到一個方法__call()__
,只要一個類定義了這個方法,就可以將它的例項視為函式。這裡先上一篇文章__call()__
方法,就可以實現像函式那樣的呼叫。
getattr()方法
這個方法在前兩天的實戰中已經見過,但還是補一下資料內容。文章:Python的hasattr(), getattr(), setattr()函式使用方法詳解。
getattr()是用於獲取物件的屬性或方法,如果存在就打印出來,如果不存在就打印出預設值。需要注意的是,如果是返回的物件的方法,返回的是方法的記憶體地址,如果需要執行這個方法,可以在後面新增一對括號。
inspect模組
參考:
Python文件--inspect模組介紹
中文版inspect模組介紹
在這段例項中:
def add_route(app, fn):
method = getattr(fn, '__method__', None)
path = getattr(fn, '__route__', None)
if path is None or method is None:
raise ValueError('@get or @post not defined in %s.' % str(fn))
if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):
fn = asyncio.coroutine(fn)
logging.info('add route %s %s => %s(%s)' % (method, path, fn.__name__, ', '.join(inspect.signature(fn).parameters.keys())))
app.router.add_route(method, path, RequestHandler(app, fn))
廖大用到了之前沒見過的inspect
模組。其中inspect.isgeneratorfunction()
好理解,從字面上可以知道是判斷是否為generaor函式。具體的實現暫時不去管它。
後面那個inspect.signature(fn).parameters.keys()
有點費解。其中inspect.signature()
的功能根據官方文件講是Return a Signature object for the given callable,即返回傳入的可呼叫函式的所有引數;而之後的paramters
似乎是用於索引引數。這是官方的兩個示例:
關於signature
:
>>> from inspect import signature
>>> def foo(a, *, b:int, **kwargs):
... pass
>>> sig = signature(foo)
>>> str(sig)
'(a, *, b:int, **kwargs)'
>>> str(sig.parameters['b'])
'b:int'
>>> sig.parameters['b'].annotation
<class 'int'>
關於parameter
:
>>> def foo(a, b, *, c, d=10):
... pass
>>> sig = signature(foo)
>>> for param in sig.parameters.values():
... if (param.kind == param.KEYWORD_ONLY and
... param.default is param.empty):
# KEYWORD_ONLY: 關鍵字引數
... print('Parameter:', param)
Parameter: c
所以,廖大的那段程式碼的意思是將函式的所有引數(不包括預設值)用逗號隔開,加入到那個括號裡去。
rfind()方法
從右向左查詢,返回字串首次出現的位置;如果沒有匹配項則返回-1。
find()
與之類似,只不過是從左向右查詢。
__import__()
__import__
內建函式是用於動態載入模組的。這個不難理解,關鍵是理解廖大的示例:
def add_routes(app, module_name):
n = module_name.rfind('.') # 找到模組名的最後一個'.'位置
if n == (-1): # 如果模組名為“XX”這種形式
mod = __import__(module_name, globals(), locals())
else: # 如果模組名為"XX.XX"這種形式
name = module_name[n+1:] # 後半部分即子函式
mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name) # 載入模組,獲取模組的子函式並返回
for attr in dir(mod): # 對於子函式裡的所有屬性和方法
if attr.startswith('_'): # 如果attr為”__XX"這種形式就忽略
continue
fn = getattr(mod, attr)
if callable(fn): # 如果可呼叫
method = getattr(fn, '__method__', None)
path = getattr(fn, '__route__', None)
if method and path:
add_route(app, fn)
攔截器
攔截器廖大python實戰專案第五天