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中关键字is与==的区别简述
Jul 31 Python
基于python编写的微博应用
Oct 17 Python
用pickle存储Python的原生对象方法
Apr 28 Python
Python读取文件内容的三种常用方式及效率比较
Oct 07 Python
Python实现PS图像明亮度调整效果示例
Jan 23 Python
使用Python AIML搭建聊天机器人的方法示例
Jul 09 Python
Python替换月份为英文缩写的实现方法
Jul 15 Python
python selenium登录豆瓣网过程解析
Aug 10 Python
python中类的输出或类的实例输出为这种形式的原因
Aug 12 Python
解决Pytorch 训练与测试时爆显存(out of memory)的问题
Aug 20 Python
Python pandas.DataFrame 找出有空值的行
Sep 09 Python
将keras的h5模型转换为tensorflow的pb模型操作
May 25 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
PHP中$_SERVER的详细参数与说明
2008/07/29 PHP
PHP合并静态文件详解
2014/11/14 PHP
WordPress主题制作之模板文件的引入方法
2015/12/28 PHP
Laravel 微信小程序后端搭建步骤详解
2019/11/26 PHP
免费空间广告万能消除代码
2006/09/04 Javascript
javascript instanceof 与typeof使用说明
2010/01/11 Javascript
使用基于jquery的gamequery插件做JS乒乓球游戏
2011/07/31 Javascript
文字溢出实现溢出的部分再放入一个新生成的div中具体代码
2013/05/17 Javascript
js登录弹出层特效
2014/03/07 Javascript
jquery实现的一个简单进度条效果实例
2014/05/12 Javascript
JavaScript异步回调的Promise模式封装实例
2014/06/07 Javascript
jQuery学习总结之jQuery事件
2014/06/30 Javascript
javascript 动态创建表格
2015/01/08 Javascript
你一定会收藏的Nodejs代码片段
2016/02/04 NodeJs
JavaScript记录光标在编辑器中位置的实现方法
2016/04/22 Javascript
javascript正则表达式之分组概念与用法实例
2016/06/16 Javascript
移动端js触摸事件详解
2016/09/18 Javascript
JS实现的数字格式化功能示例
2017/02/10 Javascript
JavaScript验证知识整理
2017/03/24 Javascript
nodejs利用ajax实现网页无刷新上传图片实例代码
2017/06/06 NodeJs
jQuery实现动态添加节点与遍历节点功能示例
2017/11/09 jQuery
vue自定义指令实现方法详解
2019/02/11 Javascript
使用vuepress搭建静态博客的示例代码
2019/02/14 Javascript
vue分页插件的使用方法
2019/12/25 Javascript
python复制与引用用法分析
2015/04/08 Python
利用Python实现图书超期提醒
2016/08/02 Python
浅谈python中的实例方法、类方法和静态方法
2017/02/17 Python
Python3.0中普通方法、类方法和静态方法的比较
2019/05/03 Python
python实现本地批量ping多个IP的方法示例
2019/08/07 Python
物理系毕业生自荐信
2013/11/01 职场文书
电厂厂长岗位职责
2014/01/02 职场文书
小学生迎国庆演讲稿
2014/09/05 职场文书
自荐信格式范文
2015/03/04 职场文书
使用pycharm运行flask应用程序的详细教程
2021/06/07 Python
python微信智能AI机器人实现多种支付方式
2022/04/12 Python