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 相关文章推荐
paramiko模块安装和使用(远程登录服务器)
Jan 27 Python
python实现的一个火车票转让信息采集器
Jul 09 Python
Python入门篇之字符串
Oct 17 Python
python、java等哪一门编程语言适合人工智能?
Nov 13 Python
Python中Threading用法详解
Dec 27 Python
在NumPy中创建空数组/矩阵的方法
Jun 15 Python
使用python Fabric动态修改远程机器hosts的方法
Oct 26 Python
python实现二维数组的对角线遍历
Mar 02 Python
python Django框架实现web端分页呈现数据
Oct 31 Python
python列表推导和生成器表达式知识点总结
Jan 10 Python
解决python存数据库速度太慢的问题
Apr 23 Python
Python爬取英雄联盟MSI直播间弹幕并生成词云图
Jun 01 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中操作MySQL数据库的一些要注意的问题
2006/10/09 PHP
PHP时间格式控制符对照表分享
2013/07/23 PHP
ThinkPHP行为扩展Behavior应用实例详解
2014/07/22 PHP
关于php微信订阅号开发之token验证后自动发送消息给订阅号但是没有消息返回的问题
2015/12/21 PHP
PHP中的表达式简述
2016/05/29 PHP
Yii2基于Ajax自动获取表单数据的方法
2016/08/10 PHP
YII框架模块化处理操作示例
2019/04/26 PHP
php中用unset销毁变量并释放内存
2020/05/10 PHP
jQuery调用AJAX时Get和post公用的乱码解决方法实例说明
2013/06/04 Javascript
多个jquery.datatable共存,checkbox全选异常的快速解决方法
2013/12/10 Javascript
javascript的propertyIsEnumerable()方法使用介绍
2014/04/09 Javascript
jQuery控制TR显示隐藏的三种常用方法
2014/08/21 Javascript
javascript制作2048游戏
2015/03/30 Javascript
再谈javascript常见错误及解决方法
2016/09/16 Javascript
angularjs中回车键触发某一事件的方法
2017/04/24 Javascript
AngularJs实现聊天列表实时刷新功能
2017/06/15 Javascript
vue利用better-scroll实现轮播图与页面滚动详解
2017/10/20 Javascript
vue项目在安卓低版本机显示空白的原因分析(两种)
2018/09/04 Javascript
解决vue做详情页跳转的时候使用created方法 数据不会更新问题
2020/07/24 Javascript
Python生成器(Generator)详解
2015/04/13 Python
Python自动化测试Eclipse+Pydev 搭建开发环境
2016/08/15 Python
Python使用matplotlib绘制正弦和余弦曲线的方法示例
2018/01/06 Python
python将list转为matrix的方法
2018/12/12 Python
Python 字符串类型列表转换成真正列表类型过程解析
2019/08/26 Python
python+opencv3生成一个自定义纯色图教程
2020/02/19 Python
Python定时从Mysql提取数据存入Redis的实现
2020/05/03 Python
在Keras中实现保存和加载权重及模型结构
2020/06/15 Python
酒店工作职员求职简历的自我评价
2013/10/23 职场文书
工会优秀工作者事迹
2014/08/17 职场文书
争先创优公开承诺书
2014/08/30 职场文书
2015年保洁员工作总结
2015/05/04 职场文书
讲座新闻稿
2015/07/18 职场文书
2015年学校政教工作总结
2015/07/20 职场文书
2016五一手机促销广告语
2016/01/28 职场文书
2019最新校园运动会广播稿!
2019/06/28 职场文书
Li list-style-image 图片垂直居中实现方法
2023/05/21 HTML / CSS