python检查输入参数个数_python 使用装饰器做函数参数自动检查

❝ 编写自定义python函数后,一般有一个参数检查过程,检查输入的参数是否是定义的类型,参数检查可以避免一些明显错误,也可以提高代码调试时的效率。本文用装饰器加注释表达式的方式,对函数做参数检查。

1 装饰器

在python中,常常看到@符号,后面的函数就是装饰器,比如在定义一个类的时候,会用到@property,作用是将一个方法转换为类的属性,也是一个装饰器。装饰器可以理解为对函数外加一个行为,这个行为是对你的项目代码是比较有意义的通用行为,常见的行为有打印日子,打印函数计算时长,也比如本次我们要做的参数检查等。对函数加装饰器,不仅可以增加函数功能,也能简化代码,提高可读性。

直接举个例子来说明,我们定义一个计时器,计算函数的运行时长:

import time

def timer(func):

"""

用于计时的装饰器函数

:param func: 被装饰函数

:return: 闭包函数,封装了自定义行为与被装饰函数的调用

"""

def wrapper(*args, **kwargs):

"""

闭包函数

:param args: 被装饰函数的位置参数

:param kwargs: 被装饰函数的关键字参数

:return: int,被装饰函数的计算结果

"""

t1 = time.time() # 开始时间

r = func(*args, **kwargs) # 被装饰函数调用

t2 = time.time() # 结束时间

cost = t2 - t1 # 计算时长

print('function cost{} second'.format(cost))

return r

return wrapper

我们定义的函数timer是一个装饰器函数,这个函数比较特殊,它能把函数作为参数传入,这里是我们的被装饰函数,然后返回的对象也为一个函数,称之为闭包函数,在闭包函数中,需要做两件事,一件事是定义行为(打印函数计算时长),另一件事是调用函数计算。

装饰器函数写好后,可以直接使用@符号进行使用,我们定义一个需要被装饰的函数add,作用就是对两个数相加,使用装饰器时在函数前一行加上@timer即可。

@timer

def add(x, y):

time.sleep(2)

return x + y

@符号是装饰器的语法糖,可以理解为装饰器的快捷键,如果不用@符号,直接用装饰器函数调用的形式也是可以的,只不过语法糖更简洁。@符号的作用就是在函数实际调用前,执行以下语句:

add = timer(add)

最后,运行函数add,得到如下效果:

print(add(1, 2))

function cost2.000091075897217 second

3

可见,函数不仅运算出结果,也打印出了计算时长。此后,该装饰器可以用在任何你认为需要打印函数计算时长的地方,可是说是很方便了。

2 注释表达式

注释表达式是python的一种特性,使可以在函数定义的时候,顺便定义输入参数的类型,提高函数可读性。用:符号加上参数类型实现。

def anno(x: str, y: dict, z: int = 123):

print("x type:{},y type:{},z type{}".format(type(x), type(y), type(z)))

anno('123', {'a': 1}, 2)

x type:,y type:,z type

3 函数参数检查

有了前面两部分知识,可以完成函数参数检查任务了,大致思路是在函数定义的时候使用注释表达式定义参数类型,然后编写一个参数检查装饰器,来检查输入的参数是否与定义的类型一致,不一致则自定义报错。

import collections

import functools

import inspect

def para_check(func):

"""

函数参数检查装饰器,需要配合函数注解表达式(Function Annotations)使用

"""

msg = 'Argument {argument} must be {expected!r},but got {got!r},value {value!r}'

# 获取函数定义的参数

sig = inspect.signature(func)

parameters = sig.parameters # 参数有序字典

arg_keys = tuple(parameters.keys()) # 参数名称

@functools.wraps(func)

def wrapper(*args, **kwargs):

CheckItem = collections.namedtuple('CheckItem', ('anno', 'arg_name', 'value'))

check_list = []

# collect args *args 传入的参数以及对应的函数参数注解

for i, value in enumerate(args):

arg_name = arg_keys[i]

anno = parameters[arg_name].annotation

check_list.append(CheckItem(anno, arg_name, value))

# collect kwargs **kwargs 传入的参数以及对应的函数参数注解

for arg_name, value in kwargs.items():

anno = parameters[arg_name].annotation

check_list.append(CheckItem(anno, arg_name, value))

# check type

for item in check_list:

if not isinstance(item.value, item.anno):

error = msg.format(expected=item.anno, argument=item.arg_name,

got=type(item.value), value=item.value)

raise TypeError(error)

return func(*args, **kwargs)

return wrapper

定义一个被检查函数:

@para_check

def test(x: int, y: int):

return x + y

当输入参数类型错误时,会打印出自定义的输出内容,由此可以很方便定位错误,分析错误原因。

test('1',2)

TypeError: Argument x must be ,but got ,value '1'

此后,可以在任何需要做参数检查的函数前,加上@para_check装饰器,这样可以避免在每个函数中都写一大段参数检查的重复代码,参数检查装饰器,简化代码,降低冗余度。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值