PS: 决定还是坚持写博客记录一下比较好。
今天的实战内容是编写web框架,如果之前的知识不熟悉的话确实看不大懂。在这里奉上自己的理解以及帮助理解的相关资料和文档。
首先我们要知道web框架是什么东西,它到底要怎么实现。这一点廖大在web开发的WSGI接口、使用web框架这两篇文章里已经说过了。摘要一些略作说明:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'Hello, web!
']
上面这段代码实现了响应HTTP请求,其中start_response
发送了HTTP响应的Header
,return
则将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()__
,只要一个类定义了这个方法,就可以将它的实例视为函数。这里先上一篇文章Python的特殊函数 __call()__算是讲得很清楚了。python中函数是一个对象,与普通类的实例不同的是,函数是callable;但只要给一个类添上__call()__
方法,就可以实现像函数那样的调用。
这个方法在前两天的实战中已经见过,但还是补一下资料内容。文章:Python的hasattr(), getattr(), setattr()函数使用方法详解。
getattr()是用于获取对象的属性或方法,如果存在就打印出来,如果不存在就打印出默认值。需要注意的是,如果是返回的对象的方法,返回的是方法的内存地址,如果需要运行这个方法,可以在后面添加一对括号。
参考:
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
所以,廖大的那段代码的意思是将函数的所有参数(不包括默认值)用逗号隔开,加入到那个括号里去。
从右向左查询,返回字符串首次出现的位置;如果没有匹配项则返回-1。
find()
与之类似,只不过是从左向右查询。
参考:
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)
拦截器