def has_request_arg(fn):这个函数抛出错误的那段代码看的我好纠结啊
Topic sourcedef`foo(a, *b, c, **d):
pass
a == POSITIONAL_OR_KEYWORD # a是用位置或参数名都可赋值的
b == VAR_POSITIONAL # b是可变长列表
c == KEYWORD_ONLY # c只能通过参数名的方式赋值
d == VAR_KEYWORD # d是可变长字典
POSITIONAL_ONLY 这类型在官方说明是不会出现在普通函数的,一般是内置函数什么的才会有,可能是self,cls或者是更底层的东西
廖老师意思应该是: request应该作为POSITIONAL_OR_KEYWORD类型的参数,就是最普通的Python函数的参数类型。
如果request作为VAR_POSITIONAL类型,那么赋值给他request对象是会报错的。
如果request作为KEYWORD_ONLY类型,那么赋值给request是可以,不会报错,但是这个参数赋值没有什么意义,因为会被request对象覆盖,如果http请求里面不予赋值也会报错。
如果request作为VAR_KEYWORD类型,那么传过去的参数request就在参数字典中,需要再次解析,也没有意义。
为什么request要作为最后一个参数,我的理解是业务约定,不是代码层面的问题,因为最后调用的函数参数肯定是从request对象中取出的。 如果调用的函数需要传入request,那么它可以完全不需要传入其他参数。 之所以传入其他参数一个作用是通过url装饰器更加直观的对应,另外一个就是KEYWORD_ONLY参数作为必填参数,这都是业务上的约定了。
遇到逻辑泥团必须考虑重构!先让我们理一下一个路由函数的所有参数都是怎么来的,只有清晰理解这一点才有可能聊得下去,先来看这样一个路由函数:
@get('/api/{table}')
async def api_model(table, page=1, request):
pass
先不必管参数是属于什么类型的,先来聊聊这些参数是可能是从哪里来的。读过源码的就会知道路由函数的参数主要有三个来源。但我们还是从第一步说起吧,首先先用inspect.signature(self._func).parameters
的函数获取路由api_model
的参数表,不难看出是有[table, page, request]
三个参数,其中page
是有默认值的。接下来要获取参数了,有三个来源:
- 网页中的
GET
和POST
方法(获取/?page=10
还有json
或form
的数据。) request.match_info
(获取@get('/api/{table}')
装饰器里面的参数)def __call__(self, request)
(获取request
参数)
顺序能变吗?不能!你若读过我的代码,就会发现我重构之后的代码和廖老师在获取参数的顺序都是一致的,只不过我简化了逻辑泥团,将事先检验改成事后检验而已。
最容易改变,或者说最容易被用户操纵的就是GET
方法的参数,万一你已经获取了table
和request
的参数,用户只要网址后面加入/?table=users&request=XXX
就可以轻易替换你原本的参数,所以,网页的参数必须最先加入参数字典的,在这个部分必须小心处理,只有在路由函数所要求的参数才能加到参数字典(比如api_model
的,只有[table, page, request]
这三个参数才接受,其他的都会被忽略的)。
request.match_info
的参数能不能改变,我也不知道,把table
换成page
的参数直接就服务器错误,找不到此网页
但是最重要的是request
是最后一个传入的!如果此前GET
或POST
传入了request
参数,那最后一定是被覆盖成正确的request
参数!所以request
绝对没有任何可能是VAR
类型的。如果你不小心在路由函数把request
写成*request
或者**request
是一定会报错的,request
可以是KEYWORD_ONLY
也可以是POSITIONAL_OR_KEYWORD
类型的,这两种类型都完全可以接受的。因为最后都是用await self._func(**kw)
来调用,**kw
是解压字典的意思,也就说是所有参数都是用KEYWORD
来传参的,KEYWORD_ONLY
和POSITIONAL_OR_KEYWORD
都无所谓,唯一考量只是接口规范性,什么参数类型实质上并不是特别重要。
我上面的回复表达的应该很清楚,因为廖老师是在aiohttp上层封装的一个框架,参数是KEYWORD_ONLY和POSITIONAL_OR_KEYWORD都无所谓。 这个没错,通过dict[key]传参是可以避免参数错误的问题。 代码里的逻辑这个就是廖老师框架这层的“约定”了,这个“约定”的好处是所有定义的handle都很清晰,参数位置是确定的,这个不是代码的对错,而是良好的设计。
你真的确定request
一定得是在最后程序才不会报错吗?
我想你是被我的言论误导了,只有我说是“最后”,代码只是在说“尽可能往后”。空口无凭,我们来看代码。
for name, param in params.items():
if name == 'request':
found = True
continue
if found and (param.kind != inspect.Parameter.VAR_POSITIONAL and param.kind != inspect.Parameter.KEYWORD_ONLY and param.kind != inspect.Parameter.VAR_KEYWORD):
raise ValueError('request parameter must be the last named parameter in function: %s%s' % (fn.__name__, str(sig)))
if found and (param.kind != VAR_POSITIONAL and param.kind != KEYWORD_ONLY and param.kind != VAR_KEYWORD)
重点是这句,当找到'request'参数后,如果后面的参数不是VAR_POSITIONAL,也不是KEYWORD_ONLY,还不是VAR_KEYWORD的时候就报错,绕不绕?我看到都纠结死了。换一种写法
if found and (param.kind not in (VAR_POSITIONAL, KEYWORD_ONLY, VAR_KEYWORD))
再换一种写法:
if found and (param.kind in (POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD))
翻译成中文的意思就是:如果找到request
参数的话,后面还有POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD)这两类参数就报错,否则是不会报错的!
def foo(request, a) # 这种是会报错
def foo(a, request, *b) # 这种不报错
def foo(a, *b, request) # 这种也不报错
def foo(a, *b, request, c) # 这种还是不报错
def foo(a, *b, c, request, **d) # 这种还是不报错
def foo(a, *b, c, **d, request) # 这种系统会报错,VAR_KEYWORD只能是最后一个参数
以上的例子你可以随便拿去测试,看看我是否所言非虚。
这些例子足以说明request
并非像你想象的那么固定,而是可变的,接下来看看再好的替代方案,如果想要request
位置确定,很简单,如果有request
,让它永远处在第一位好了!代码也可以非常简洁:
def has_request_arg(fn):
sig = inspect.signature(fn)
params = sig.parameters
if 'request' in params:
if 'request' != list(params.keys())[0]:
raise ValueError('request must be the first parameter in function: %s%s' % (fn.__name__, str(sig)))
return True
return False
但我还是觉得没有太大的必要指明request
的位置,在这项目中,RequestHandler
的代码虽然是存在很多问题的,但瑕不掩瑜,我还是在这里学会搭建我的博客的,欢迎来踩,交流讨论什么都行,有很多东西都是不辩不明的。
def foo(a, *request):pass
def foo(a, **request):pass
于是我丧心病狂再测试两个应该报错的例子,然而还是没有报错... 参考可以,切莫盲从,知其然,更要知所以然。
这个是否报错是要配合url参数的,如果你要说url里面不传参数,但是function里面传request,或者*request,那就当我前面的都没说吧。。。。
我说这个是框架的设计和约定,并不是代码非要这么写。
- 1
- 2
辛未六月羊
查了下python官方文档中关于inspect.Parameter的部分,里面有5种参数类型,和廖老师教程中有点不一样。 分别是POSITIONAL_ONLY、VAR_POSITIONAL、KEYWORD_ONLY、VAR_KEYWORD、POSITIONAL_OR_KEYWORD 分别对应廖老师教程中的位置参数、可变参数、命名关键字参数、关键字参数,最后一个是位置参数或命名关键字参数 这样算来其实也就只有四种参数类型,廖老师教程中的默认参数其实是从位置参数中衍生出来的,命名关键字参数也可以是默认参数
()内的这段代码的意思应该是当参数类型既不是可变参数,又不是命名关键字参数,又不是关键字参数的时候,()内为True 也就是说只要是位置参数就判断为True,然后抛出错误,那为什么不直接写
抛出的错误提示request parameter must be the last named parameter in function看的我更加纠结了 如果named parameter解释为有名字的参数,那所有参数都有名字,最后一个有名字的参数就是最后一个参数,也就是说request参数后面不能再有别的参数,只要有就报错,这样的话条件判断语句写成这样就好了
如果named parameter解释为命名关键字参数,那就是说request参数必须是最后命名关键字参数,而且必须是最后一个。如果是这样的话,找到request参数后应该先判断下参数类型,然后再决定是否设置found为True 如果找到request参数,那后面的参数只可能是关键字参数,如果下一个参数类型还是命名关键字参数,那就需要报错。这样的话条件判断语句就应该是