检查函数接受或返回的参数,在特定上下文中执行时可能有用。XML-RPC协议是一种轻量级的远程过程调用(Remote Procedure Call)协议,通过HTTP使用XML对调用进行编码。自定义装饰器可以提供这种类型的签名,并确保输入和输出的类型,简单来讲就是创建一个用于检查输入参数和输出参数类型的装饰器
首先展示完整的代码
rpc_info = {}
def xmlrpc(in_=(), out=(type(None),)):
# in_:输入数据的类型, out输出数据的类型
def _xmlrpc(function):
# 注册签名,本质上就是构建了一个字典,以函数名为key,输入参数和输出参数的类型为value
func_name = function.__name__
rpc_info[func_name] = (in_, out)
def _check_types(elements, types):
"""用来检查类型的子函数。"""
if len(elements) != len(types):
# 检查参数数量与要验证的参数数量
raise TypeError('argument count is wrong')
typed = enumerate(zip(elements, types))
for index, couple in typed:
# 获取每一个参数和其应该对应的数据类型
arg, of_the_right_type = couple
if isinstance(arg, of_the_right_type):
continue
raise TypeError(
'arg #%d should be %s' % (index,
of_the_right_type))
# 包装过的函数
def __xmlrpc(*args): # 没有允许的关键词
# 检查输入的内容
checkable_args = args[1:] # 去掉self
_check_types(checkable_args, in_)
# 运行函数
res = function(*args)
# 检查输出的内容
if not type(res) in (tuple, list):
checkable_res = (res,)
else:
checkable_res = res
_check_types(checkable_res, out)
# 函数及其类型检查成功
return res
return __xmlrpc
return _xmlrpc
rpc_info是一个字典,用于修饰器中的注册功能(传入的函数名与输入输出类型的键值对)。
在xmlrpc中,in_传入的是输入参数的类型,out传入的是输出参数的类型。
在_xmlrpc首先完成传入的函数名与输入输出类型的键值对,在_xmlrpc中构建了_check_types和__xmlrpc函数,其中_check_types函数用于检查参数的类型,__xmlrpc函数先调用_check_types检查输入参数的类型,随后调用function获取输出结果。随后再调用_check_types检查输出类型。
_check_types输入参数有elements和types,elements对应的是函数的参数,types对应的是参数应当对应的类型。首先要判断elements和types的数量是否一致,当数量一致时逐个判断参数是否满足要求的类型(isinstance用来判断类型是否相同)。
在__xmlrpc中,我们着重来看下面这一段代码
if not type(res) in (tuple, list):
checkable_res = (res,)
else:
checkable_res = res
这段代码是用来判断res是否为元组或者列表,因为我们在_check_types采用的是逐个遍历可迭代对象(元组或者列表)的方式,但是function的返回值不一定是可迭代的对象,所以这里我们需要强行把他变为可迭代的对象。
下面我们使用一个例子来展现效果
class RPCView:
@xmlrpc((int, int)) # two int -> None
def meth1(self, int1, int2):
print('received %d and %d' % (int1, int2))
@xmlrpc((str,), (int,)) # string -> int
def meth2(self, phrase):
print('received %s' % phrase)
return 12
在RPCView类中我们构建了两个类方法,meth1和meth2,用于判断他们传入参数和输出参数的类型
如果我们打印一下rpc_info,可以看到注册后的信息
print(rpc_info)
运行结果为:
{'meth1': ((<class 'int'>, <class 'int'>), (<class 'NoneType'>,)), 'meth2': ((<class 'str'>,), (<class 'int'>,))}
即函数名与传入和传出参数的键值对
我们可以将类进行实例化并测试
my = RPCView()
my.meth1(1, 2)
my.meth2(2)
在调用meth1时会输出received 1 and 2,但是调用meth2时由于我们输入的并不是字符串,所以经修饰器检查不符合,会报错。