视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Python装饰器使用实例:验证参数合法性
2020-11-27 14:40:58 责编:小采
文档


python是不带静态检查的动态语言,有时候需要在调用函数时保证参数合法。检查参数合法性是一个显著的切面场景,各个函数都可能有这个需求。但另一方面,参数合法性是不是应该由调用方来保证比较好也是一个需要结合实际才能回答的问题,总之双方约定好,不要都不检查或者都检查就可以了。下面这个模块用于在函数上使用装饰器进行参数的合法性验证。

你可以直接执行这个模块进行测试,如果完全没有输出则表示通过。你也可以找到几个以_test开头的函数,所有的测试用例都包含在这几个函数中。使用方法参见模块文档和测试用例。

# -*- coding: UTF-8 -*-
 
'''
@summary: 验证器
该模块提供了一个装饰器用于验证参数是否合法,使用方法为:
 
from validator import validParam, nullOk, multiType
 
@validParam(i=int)
def foo(i):
 return i+1
 
编写验证器:
 
1. 仅验证类型:
@validParam(type, ...)
例如:
检查第一个位置的参数是否为int类型:
@validParam(int)
检查名为x的参数是否为int类型:
@validParam(x=int)
 
验证多个参数:
@validParam(int, int)
指定参数名验证:
@validParam(int, s=str)
 
针对*和**参数编写的验证器将验证这些参数实际包含的每个元素:
@validParam(varargs=int)
def foo(*varargs): pass
 
@validParam(kws=int)
def foo7(s, **kws): pass
 
2. 带有条件的验证:
@validParam((type, condition), ...)
其中,condition是一个表达式字符串,使用x引用待验证的对象;
根据bool(表达式的值)判断是否通过验证,若计算表达式时抛出异常,视为失败。
例如:
验证一个10到20之间的整数:
@validParam(i=(int, '1020'), nullOk(str, '/^d+$/')))
 
5. 如果有更复杂的验证需求,还可以编写一个函数作为验证函数传入。
这个函数接收待验证的对象作为参数,根据bool(返回值)判断是否通过验证,抛出异常视为失败。
例如:
def validFunction(x):
 return isinstance(x, int) and x>0
@validParam(i=validFunction)
def foo(i): pass
 
这个验证函数等价于:
@validParam(i=(int, 'x>0'))
def foo(i): pass
 
 
@author: HUXI
@since: 2011-3-22
@change: 
'''
 
import inspect
import re
 
class ValidateException(Exception): pass
 
 
def validParam(*varargs, **keywords):
 '''验证参数的装饰器。'''
 
 varargs = map(_toStardardCondition, varargs)
 keywords = dict((k, _toStardardCondition(keywords[k]))
 for k in keywords)
 
 def generator(func):
 args, varargname, kwname = inspect.getargspec(func)[:3]
 dctValidator = _getcallargs(args, varargname, kwname,
 varargs, keywords)
 
 def wrapper(*callvarargs, **callkeywords):
 dctCallArgs = _getcallargs(args, varargname, kwname,
 callvarargs, callkeywords)
 
 k, item = None, None
 try:
 for k in dctValidator:
 if k == varargname:
 for item in dctCallArgs[k]:
 assert dctValidator[k](item)
 elif k == kwname:
 for item in dctCallArgs[k].values():
 assert dctValidator[k](item)
 else:
 item = dctCallArgs[k]
 assert dctValidator[k](item)
 except:
 raise ValidateException,
 ('%s() parameter validation fails, param: %s, value: %s(%s)'
 % (func.func_name, k, item, item.__class__.__name__))
 
 return func(*callvarargs, **callkeywords)
 
 wrapper = _wrapps(wrapper, func)
 return wrapper
 
 return generator
 
 
def _toStardardCondition(condition):
 '''将各种格式的检查条件转换为检查函数'''
 
 if inspect.isclass(condition):
 return lambda x: isinstance(x, condition)
 
 if isinstance(condition, (tuple, list)):
 cls, condition = condition[:2]
 if condition is None:
 return _toStardardCondition(cls)
 
 if cls in (str, unicode) and condition[0] == condition[-1] == '/':
 return lambda x: (isinstance(x, cls)
 and re.match(condition[1:-1], x) is not None)
 
 return lambda x: isinstance(x, cls) and eval(condition)
 
 return condition
 
 
def nullOk(cls, condition=None):
 '''这个函数指定的检查条件可以接受None值'''
 
 return lambda x: x is None or _toStardardCondition((cls, condition))(x)
 
 
def multiType(*conditions):
 '''这个函数指定的检查条件只需要有一个通过'''
 
 lstValidator = map(_toStardardCondition, conditions)
 def validate(x):
 for v in lstValidator:
 if v(x):
 return True
 return validate
 
 
def _getcallargs(args, varargname, kwname, varargs, keywords):
 '''获取调用时的各参数名-值的字典'''
 
 dctArgs = {}
 varargs = tuple(varargs)
 keywords = dict(keywords)
 
 argcount = len(args)
 varcount = len(varargs)
 callvarargs = None
 
 if argcount <= varcount:
 for n, argname in enumerate(args):
 dctArgs[argname] = varargs[n]
 
 callvarargs = varargs[-(varcount-argcount):]
 
 else:
 for n, var in enumerate(varargs):
 dctArgs[args[n]] = var
 
 for argname in args[-(argcount-varcount):]:
 if argname in keywords:
 dctArgs[argname] = keywords.pop(argname)
 
 callvarargs = ()
 
 if varargname is not None:
 dctArgs[varargname] = callvarargs
 
 if kwname is not None:
 dctArgs[kwname] = keywords
 
 dctArgs.update(keywords)
 return dctArgs
 
 
def _wrapps(wrapper, wrapped):
 '''复制元数据'''
 
 for attr in ('__module__', '__name__', '__doc__'):
 setattr(wrapper, attr, getattr(wrapped, attr))
 for attr in ('__dict__',):
 getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
 
 return wrapper
 
 
#===============================================================================
# 测试
#===============================================================================
 
 
def _unittest(func, *cases):
 for case in cases:
 _functest(func, *case)
 
 
def _functest(func, isCkPass, *args, **kws):
 if isCkPass:
 func(*args, **kws)
 else:
 try:
 func(*args, **kws)
 assert False
 except ValidateException:
 pass
 
def _test1_simple():
 #检查第一个位置的参数是否为int类型:
 @validParam(int)
 def foo1(i): pass
 _unittest(foo1, 
 (True, 1), 
 (False, 's'), 
 (False, None))
 
 #检查名为x的参数是否为int类型:
 @validParam(x=int)
 def foo2(s, x): pass
 _unittest(foo2, 
 (True, 1, 2), 
 (False, 's', 's'))
 
 #验证多个参数:
 @validParam(int, int)
 def foo3(s, x): pass
 _unittest(foo3, 
 (True, 1, 2), 
 (False, 's', 2))
 
 #指定参数名验证:
 @validParam(int, s=str)
 def foo4(i, s): pass
 _unittest(foo4, 
 (True, 1, 'a'), 
 (False, 's', 1))
 
 #针对*和**参数编写的验证器将验证这些参数包含的每个元素:
 @validParam(varargs=int)
 def foo5(*varargs): pass
 _unittest(foo5,
 (True, 1, 2, 3, 4, 5),
 (False, 'a', 1))
 
 @validParam(kws=int)
 def foo6(**kws): pass
 _functest(foo6, True, a=1, b=2)
 _functest(foo6, False, a='a', b=2)
 
 @validParam(kws=int)
 def foo7(s, **kws): pass
 _functest(foo7, True, s='a', a=1, b=2)
 
 
def _test2_condition():
 #验证一个10到20之间的整数:
 @validParam(i=(int, '1020'), nullOk(str, '/^d+$/')))
 def foo2(s): pass
 _unittest(foo2, 
 (False, 1),
 (False, 'a'),
 (True, None),
 (False, 1.1),
 (True, 21),
 (True, '21'))
 
def _main():
 d = globals()
 from types import FunctionType
 print
 for f in d:
 if f.startswith('_test'):
 f = d[f]
 if isinstance(f, FunctionType):
 f()
 
if __name__ == '__main__':
 _main()

下载本文
显示全文
专题