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发送邮件的实例代码(支持html、图片、附件)
Mar 04 Python
在Python的web框架中中编写日志列表的教程
Apr 30 Python
Python简单读取json文件功能示例
Nov 30 Python
Python连接Mssql基础教程之Python库pymssql
Sep 16 Python
Python面向对象程序设计构造函数和析构函数用法分析
Apr 12 Python
Python爬虫学习之获取指定网页源码
Jul 30 Python
详解解决Python memory error的问题(四种解决方案)
Aug 08 Python
Django之PopUp的具体实现方法
Aug 31 Python
python实现图片插入文字
Nov 26 Python
Python写捕鱼达人的游戏实现
Mar 31 Python
pycharm中如何自定义设置通过“ctrl+滚轮”进行放大和缩小实现方法
Sep 16 Python
如何在Python中创建二叉树
Mar 30 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效率,提高php性能的一些方法
2011/03/24 PHP
WordPress的主题编写中获取头部模板和底部模板
2015/12/28 PHP
PHP处理数组和XML之间的互相转换
2016/06/02 PHP
PHP使用phpunit进行单元测试示例
2019/09/23 PHP
TP5框架使用QueryList采集框架爬小说操作示例
2020/03/26 PHP
javascript json 新手入门文档
2009/12/03 Javascript
javascript 通用简单的table选项卡实现
2010/05/07 Javascript
基于Jquery的温度计动画效果
2010/06/18 Javascript
contains和compareDocumentPosition 方法来确定是否HTML节点间的关系
2011/09/13 Javascript
JavaScript 代码压缩工具小结
2012/02/27 Javascript
JQuery触发事件例如click
2013/09/11 Javascript
JavaScript支持的最大递归调用次数分析
2014/06/24 Javascript
JavaScript中使用Callback控制流程介绍
2015/03/16 Javascript
基于JavaScript获取鼠标位置的各种方法
2015/12/16 Javascript
jQuery ajax请求返回list数据动态生成input标签,并把list数据赋值到input标签
2016/03/29 Javascript
JavaScript中的Object对象学习教程
2016/05/20 Javascript
JavaScript作用域示例详解
2016/07/07 Javascript
详解Vue中组件传值的多重实现方式
2019/08/16 Javascript
sharp.js安装过程中遇到的问题总结
2020/04/02 Javascript
通过实例了解Nodejs模块系统及require机制
2020/07/16 NodeJs
[01:04:02]DOTA2-DPC中国联赛 正赛 Elephant vs IG BO3 第二场 1月24日
2021/03/11 DOTA
Python使用pickle模块存储数据报错解决示例代码
2018/01/26 Python
pandas修改DataFrame列名的方法
2018/04/08 Python
浅谈python中np.array的shape( ,)与( ,1)的区别
2018/06/04 Python
python2 与 pyhton3的输入语句写法小结
2018/09/10 Python
在Pytorch中使用样本权重(sample_weight)的正确方法
2019/08/17 Python
python对常见数据类型的遍历解析
2019/08/27 Python
如何配置关联Python 解释器 Anaconda的教程(图解)
2020/04/30 Python
internal修饰符起什么作用
2013/12/16 面试题
公务员职业生涯规划书范文  
2014/01/19 职场文书
医德医风演讲稿
2014/05/20 职场文书
导游欢送词
2015/01/31 职场文书
狂人日记读书笔记
2015/06/30 职场文书
假期读书倡议书3篇
2019/08/19 职场文书
2019年家电促销广告语集锦
2019/10/21 职场文书
python全面解析接口返回数据
2022/02/12 Python