1. 程式人生 > >廖大python實戰專案第五天

廖大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響應的Headerreturn 則將HTTP響應的body 返回。在這裡面,如何接受、解析HTTP請求和傳送HTTP響應不是我們關注的重點,我們該做的是決定用什麼內容去響應HTTP請求,而其餘部分交給WSGI介面去實現。這裡已經出現了第一層的抽象,讓我們擺脫煩雜的底層邏輯。

然而單單實現這個還是不夠的,對比WebApp的處理邏輯,這裡還是有些低階。一個url對應一個HTTP請求,而HTTP請求可分為GET,POST,PUT,DELETE等等,我們自己用一個一個的函式實現不大現實。所以我們需要對WSGI介面

做進一步的抽象,也就是所謂的Web框架

回到廖大的這一天的實戰內容,是要對aiohttp封裝一個更高層次的web框架。為了看懂aiohttp實現HTTP響應的邏輯(整體和同步一樣但是還是有一些差別影響理解),我們需要先把之前的非同步IO那四篇都回顧一下,在這裡,aiohttp官方文件還有Server Usage可以幫助我們更好地理解request,Response,Application這三個方法的功能。

涉及到模組渲染裝飾器我一開始有些不清楚,補一下對應的章節就明白了。


__call__()方法

廖大提到一個方法__call()__,只要一個類定義了這個方法,就可以將它的例項視為函式。這裡先上一篇文章

Python的特殊函式 __call()__算是講得很清楚了。python中函式是一個物件,與普通類的例項不同的是,函式是callable;但只要給一個類添上__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__()

參考:
Python官方文件
__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實戰專案第五天