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
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlexFang0904

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值