分类目录:《系统学习Python》总目录
之前的文章中的版本说明了我们需要使用的基础知识,但是它非常局限一一它只支持验证按照位置传递的参数,而不支持验证关键字参数(实际上,它假设不会有那种使得参数位置数不正确的关键字参数传递)。此外,对于在一个给定调用中可能忽略的默认参数,它什么也没有做。如果所有的参数都按照位置传递并且不是默认的,这没有问题,但在一个通用工具中,这仅是少数时候的理想状态。Python支持灵活得多的参数传递模式,接下来我们将对此进行讲解。
对示例的如下变种做得更好。通过把被包装函数的期待参数与调用时实际传入的参数匹配,它就能够同时支持验证位置参数和关键字参数,并且它会跳过对调用中忽略的默认参数的测试。简而言之,要验证的参数通过装饰器的关键字参数说明,装饰器随后遍历*pargs
位置参数元组和**kargs
关键字字典以进行验证:
trace = True
def rangetest(**argchecks):
def onDecorator(func):
if not __debug__:
return func
else:
code = func.__code__
allargs = code.co_varnames[:code.co_argcount]
funcname = func.__name__
def onCall(*pargs, **kargs):
expected = list(allargs)
positionals = expected[:len(pargs)]
for (argname, (low, high)) in argchecks.items():
if argname in kargs:
if kargs[argname] < low or kargs[argname] > high:
errmsg = '{0} argument "{1}" not in {2}-{3}'.format(funcname, argname, low, high)
raise TypeError(errmsg)
elif argname in positionals:
position = positionals.index(argname)
if pargs[position] < low or pargs[position] > high:
errmsg = '{0} argument "{1}" not in {2}-{3}'.format(funcname, argname, low, high)
raise TypeError(errmsg)
else:
if trace:
print('Argument {0} idefault'.format(argname))
return func(*pargs, **kargs)
return onCall
return onDecorator
如下的测试脚本展示了如何使用这个装饰器一一一需要验证的参数由装饰器的关键字参数给定,并且在实际的调用中,我们可以按照名称或位置传递参数,或者即便需要验证,可以用默认参数的方式来忽略:
@rangetest(age = (0, 120))
def personinfo(name, age):
print('%s is %s years old.' % (name, age))
@rangetest(M=(1, 12), D=(1, 31), Y=(0, 2099))
def birthday(M, D, Y):
print('birthday = {0}/{1}/{2}'.format(M, D, Y))
class Person:
def __init__(self, name, job, pay):
self.job = job
self.pay = pay
@rangetest(percent=(0, 1))
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
输入
personinfo('Bob', 40)
输出:
Bob is 40 years old.
输入
personinfo(age = 40, name = 'Bob')
输出:
Bob is 40 years old.
输入
personinfo('Bob', age = 40)
输出:
Bob is 40 years old.
输入:
personinfo('Bob', 150)
输出:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [56], in <cell line: 1>()
----> 1 personinfo('Bob', 150)
Input In [48], in rangetest.<locals>.onDecorator.<locals>.onCall(*pargs, **kargs)
24 if pargs[position] < low or pargs[position] > high:
25 errmsg = '{0} argument "{1}" not in {2}-{3}'.format(funcname, argname, low, high)
---> 26 raise TypeError(errmsg)
28 else:
29 if trace:
TypeError: personinfo argument "age" not in 0-120
输入:
birthday(5, D=1, Y=2000)
输出:
birthday = 5/1/2000
输入:
bob = Person('Bob', 'dev', 100000)
bob.giveRaise(0.1)
bob.giveRaise(percent=0.1)
bob.giveRaise(1.5)
输出:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [58], in <cell line: 4>()
2 bob.giveRaise(0.1)
3 bob.giveRaise(percent=0.1)
----> 4 bob.giveRaise(1.5)
Input In [48], in rangetest.<locals>.onDecorator.<locals>.onCall(*pargs, **kargs)
24 if pargs[position] < low or pargs[position] > high:
25 errmsg = '{0} argument "{1}" not in {2}-{3}'.format(funcname, argname, low, high)
---> 26 raise TypeError(errmsg)
28 else:
29 if trace:
TypeError: giveRaise argument "percent" not in 0-1
当这段脚本运行的时候,超出范围的参数会像前面一样引发异常,但参数可以按照名称或位置传递,并且省略的默认参数不会验证。这段代码在python2.X和Python3.X都可以运行。跟踪输出自行体验并进一步测试;它像前面一样工作,但是其应用范围已经拓展了很多。除非.-0
命令行参数传递给了Python来禁用装饰器的逻辑,否则取消某个方法测试行的注释的时候,一旦出现验证错误我们像前面一样得到一个异常。
参考文献:
[1] Mark Lutz. Python学习手册[M]. 机械工业出版社, 2018.
文章评论