手上一个基于uwsgi开发的后台服务,接收GET请求,使用QUERY_STRING作为参数。
最开始的时候,路由功能使用的是if else的结构,大致如下
path = env["PATH_INFO"] param = parse_query_string(env["QUERY_STRING"]) if path == "foo1/bar1": fooBar1(param) elif path == "foo2/bar2": fooBar2(param)
为了方便管理并且美化代码,调整为使用路由字典的形式
funcion = {"foo/bar":fooBar} functions[path](params) def fooBar(params): param1 = params.get("p")
经过一段时间的使用后发现,在这种形式下每个函数的参数unpack与必要参数判断都需要独立进行,产生了很多重复代码
并且,在参考了flask等框架的装饰器形式之后,对路由部分进行了重构
def route(path="", required=None): def route_func(func): def params_check_func(params): args = inspect.getargspec(func) for required_param in (var_required if required else []): if required_param not in params: raise ParamsError("%s is required" % required_param) return func(**params) global functions functions[path] = params_check_func return params_check_func return route_func @route("foo/bar", ["p"]) def fooBar(p, *args, **kwargs): return do_sth(p)
其中因为参数利用了Python的可变变量功能,直接从QUERY_STRING解析获得,为了避免输入参数中附加了不必要的参数,所以使用*args, **kwargs的结构将多余的参数存储并忽略掉
以避免多余的参数使函数因参数数量不符造成报错
每个函数都增加*args, **kwargs的参数显得多余,同时发现,每个函数的必须参数在函数定义的参数列表中已经可以体现,
具体思路是:
使用 inspect.getargspec(func)获得参数的具体参数列表
则其中的args.defaults即为有默认值参数的默认值,使用len(args.defaults)获取有默认值参数的个数
那么args.args[:-len(args.defaults)]就是必须参数的列表,因为在定义中,有默认值的参数必须在无默认值参数的后面
进一步,如果没有args.varargs和not args.keywords即可变参数,则将所有多余的参数过滤
同时经过统一格式化的参数名,也可以直接映射到相应的路径
那么在改进之后的代码则如下
def route(path="", required=None): def route_func(func): def params_check_func(params): args = inspect.getargspec(func) var_required = required if not var_required: var_required = [arg for arg in args.args[:-len(args.defaults)]] for required_param in (var_required if var_required else []): if required_param not in params: raise ParamsError("%s is required" % required_param) if not args.varargs and not args.keywords: for param_key in params.keys(): if param_key not in args.args: params.pop(param_key) return func(**params) global functions var_path = path if not var_path: var_path = "/%s/" % func.func_name.replace("__", "/") functions[var_path] = params_check_func return params_check_func return route_func @route() def foo__bar(p1, p2=1): return do_sth(p1, p2)