Python装饰器使用实例:验证参数合法性


Posted in Python onJune 24, 2015

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, '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()

Python 相关文章推荐
python实现在每个独立进程中运行一个函数的方法
Apr 23 Python
Python中使用platform模块获取系统信息的用法教程
Jul 08 Python
python使用matplotlib绘制折线图教程
Feb 08 Python
用python编写第一个IDA插件的实例
May 29 Python
python分块读取大数据,避免内存不足的方法
Dec 10 Python
Python设计模式之策略模式实例详解
Jan 21 Python
浅谈Python中的可迭代对象、迭代器、For循环工作机制、生成器
Mar 11 Python
教你一步步利用python实现贪吃蛇游戏
Jun 27 Python
python3 常见解密加密算法实例分析【base64、MD5等】
Dec 19 Python
python程序输出无内容的解决方式
Apr 09 Python
python模拟点击玩游戏的实例讲解
Nov 26 Python
使用python向MongoDB插入时间字段的操作
May 18 Python
Python线程详解
Jun 24 #Python
Python函数式编程指南(四):生成器详解
Jun 24 #Python
Python函数式编程指南(三):迭代器详解
Jun 24 #Python
Python函数式编程指南(二):从函数开始
Jun 24 #Python
Python函数式编程指南(一):函数式编程概述
Jun 24 #Python
web.py在SAE中的Session问题解决方法(使用mysql存储)
Jun 24 #Python
Python实现LRU算法的2种方法
Jun 24 #Python
You might like
模拟OICQ的实现思路和核心程序(三)
2006/10/09 PHP
PHP循环结构实例讲解
2014/02/10 PHP
smarty内置函数capture用法分析
2015/01/22 PHP
php身份证号码检查类实例
2015/06/18 PHP
常见的四种POST 提交数据方式(小总结)
2015/10/08 PHP
PHP中PDO连接数据库中各种DNS设置方法小结
2016/05/13 PHP
PHP 闭包详解及实例代码
2016/09/28 PHP
thinkPHP js文件中U方法不被解析问题的解决方法
2016/12/05 PHP
phpstudy2020搭建站点的实现示例
2020/10/30 PHP
把textarea中字符串里含有的回车换行替换成&amp;lt;br&amp;gt;的javascript代码
2007/04/20 Javascript
javascript实现上传图片前的预览(TX的面试题)
2007/08/20 Javascript
用cookies实现的可记忆的样式切换效果代码下载
2007/12/24 Javascript
页面中js执行顺序
2009/11/09 Javascript
javascript生成随机颜色示例代码
2014/05/05 Javascript
js简单实现交换Li的值
2014/05/22 Javascript
jQuery使用after()方法在元素后面添加多项内容的方法
2015/03/26 Javascript
jquery实现兼容IE8的异步上传文件
2015/06/15 Javascript
jQuery无刷新上传之uploadify3.1简单使用
2016/06/18 Javascript
微信小程序  action-sheet详解及实例代码
2016/11/09 Javascript
jquery事件与绑定事件
2017/03/16 Javascript
JS使用ActiveXObject实现用户提交表单时屏蔽敏感词功能
2017/06/20 Javascript
angular ng-model 无法获取值的处理方法
2018/10/02 Javascript
jquery操作checkbox的常用方法总结【附测试源码下载】
2019/06/10 jQuery
深入了解JavaScript 的 WebAssembly
2019/06/15 Javascript
js实现中文实时时钟
2020/01/15 Javascript
JS如何生成随机验证码
2020/03/02 Javascript
关于Python面向对象编程的知识点总结
2017/02/14 Python
疯狂上涨的Python 开发者应从2.x还是3.x着手?
2017/11/16 Python
使用Python通过win32 COM打开Excel并添加Sheet的方法
2018/05/02 Python
简单了解python 生成器 列表推导式 生成器表达式
2019/08/22 Python
Python高级编程之继承问题详解(super与mro)
2019/11/19 Python
Python logging日志库空间不足问题解决
2020/09/14 Python
HTML5 Canvas自定义圆角矩形与虚线示例代码
2013/08/02 HTML / CSS
高性能装备提升营地:Kammok
2019/02/27 全球购物
《宿建德江》教学反思
2014/04/23 职场文书
个人销售励志奋斗口号
2019/12/05 职场文书