0
点赞
收藏
分享

微信扫一扫

Python 装饰器实例:调用参数合法性检查


# -*- 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, '10<x<20'))
验证一个长度小于20的字符串:
@validParam(s=(str, 'len(x)<20'))
验证一个年龄小于20的学生:
@validParam(stu=(Student, 'x.age<20'))

另外,如果类型是字符串,condition还可以使用斜杠开头和结尾表示正则表达式匹配。
验证一个由数字组成的字符串:
@validParam(s=(str, '/^\d*$/'))

3. 以上验证方式默认为当值是None时验证失败。如果None是合法的参数,可以使用nullOk()。
nullOk()接受一个验证条件作为参数。
例如:
@validParam(i=nullOk(int))
@validParam(i=nullOk((int, '10<x<20')))
也可以简写为:
@validParam(i=nullOk(int, '10<x<20'))

4. 如果参数有多个合法的类型,可以使用multiType()。
multiType()可接受多个参数,每个参数都是一个验证条件。
例如:
@validParam(s=multiType(int, str))
@validParam(s=multiType((int, 'x>20'), 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 , '10<x<20' ))
def foo1(x, i): pass
_unittest(foo1,
( True , 1 , 11 ),
( False , 1 , 'a' ),
( False , 1 , 1 ))

#验证一个长度小于20的字符串:
@validParam (s = ( str , 'len(x)<20' ))
def foo2(a, s): pass
_unittest(foo2,
( True , 1 , 'a' ),
( False , 1 , 1 ),
( False , 1 , 'a' * 20 ))

#验证一个年龄小于20的学生:
class Student( object ):
def __init__( self , age): self .age = age

@validParam (stu = (Student, 'x.age<20' ))
def foo3(stu): pass
_unittest(foo3,
( True , Student( 18 )),
( False , 1 ),
( False , Student( 20 )))

#验证一个由数字组成的字符串:
@validParam (s = ( str , r '/^\d*$/' ))
def foo4(s): pass
_unittest(foo4,
( True , '1234' ),
( False , 1 ),
( False , 'a1234' ))


def _test3_nullok():
@validParam (i = nullOk( int ))
def foo1(i): pass
_unittest(foo1,
( True , 1 ),
( False , 'a' ),
( True , None ))

@validParam (i = nullOk( int , '10<x<20' ))
def foo2(i): pass
_unittest(foo2,
( True , 11 ),
( False , 'a' ),
( True , None ),
( False , 1 ))


def _test4_multitype():
@validParam (s = multiType( int , str ))
def foo1(s): pass
_unittest(foo1,
( True , 1 ),
( True , 'a' ),
( False , None ),
( False , 1.1 ))

@validParam (s = multiType(( int , 'x>20' ), 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()

举报

相关推荐

0 条评论