前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python 对传参进行参数检查的装饰器

python 对传参进行参数检查的装饰器

作者头像
用户5760343
发布2019-12-12 16:26:08
8740
发布2019-12-12 16:26:08
举报
文章被收录于专栏:sktj

代码语言:javascript
复制
from inspect import signature
 from functools import wraps
def typeassert(*ty_args, **ty_kwargs):
 def decorate(func):
 # If in optimized mode, disable type checking
 if not debug:
 return func
代码语言:javascript
复制
    # Map function argument names to supplied types
    sig = signature(func)
    bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments

    @wraps(func)
    def wrapper(*args, **kwargs):
        bound_values = sig.bind(*args, **kwargs)
        # Enforce type assertions across supplied arguments
        for name, value in bound_values.arguments.items():
            if name in bound_types:
                if not isinstance(value, bound_types[name]):
                    raise TypeError(
                        'Argument {} must be {}'.format(name, bound_types[name])
                        )
        return func(*args, **kwargs)
    return wrapper
return decorate

调用方法

代码语言:javascript
复制
   @typeassert(int, z=int)
 ... def spam(x, y, z=42):
 ...     print(x, y, z)
 ...
 spam(1, 2, 3)
 1 2 3
 spam(1, 'hello', 3)
 1 hello 3
 spam(1, 'hello', 'world')
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "contract.py", line 33, in wrapper
 TypeError: Argument z must be <class 'int'>
 
 
 

装饰器说明

首先,装饰器只会在函数定义时被调用一次。 有时候你去掉装饰器的功能,那么你只需要简单的返回被装饰函数即可。 下面的代码中,如果全局变量 debug 被设置成了False(当你使用-O或-OO参数的优化模式执行程序时), 那么就直接返回未修改过的函数本身:

代码语言:javascript
复制
def decorate(func):
 # If in optimized mode, disable type checking
 if not debug:
 return func
 其次,这里还对被包装函数的参数签名进行了检查,我们使用了 inspect.signature() 函数。 简单来讲,它运行你提取一个可调用对象的参数签名信息。例如:
代码语言:javascript
复制
   from inspect import signature
 def spam(x, y, z=42):
 ...     pass
 ...
 sig = signature(spam)
 print(sig)
 (x, y, z=42)
 sig.parameters
 mappingproxy(OrderedDict([('x', <Parameter at 0x10077a050 'x'>),
 ('y', <Parameter at 0x10077a158 'y'>), ('z', <Parameter at 0x10077a1b0 'z'>)]))
 sig.parameters['z'].name
 'z'
 sig.parameters['z'].default
 42
 sig.parameters['z'].kind
 <_ParameterKind: 'POSITIONAL_OR_KEYWORD'>
 
 
 

装饰器的开始部分,我们使用了 bind_partial() 方法来执行从指定类型到名称的部分绑定。 下面是例子演示:

代码语言:javascript
复制
   bound_types = sig.bind_partial(int,z=int)
 bound_types
 <inspect.BoundArguments object at 0x10069bb50>
 bound_types.arguments
 OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])
 
 
 

在这个部分绑定中,你可以注意到缺失的参数被忽略了(比如并没有对y进行绑定)。 不过最重要的是创建了一个有序字典 bound_types.arguments 。 这个字典会将参数名以函数签名中相同顺序映射到指定的类型值上面去。 在我们的装饰器例子中,这个映射包含了我们要强制指定的类型断言。

在装饰器创建的实际包装函数中使用到了 sig.bind() 方法。 bind() 跟 bind_partial() 类似,但是它不允许忽略任何参数。因此有了下面的结果:

代码语言:javascript
复制
   bound_values = sig.bind(1, 2, 3)
 bound_values.arguments
 OrderedDict([('x', 1), ('y', 2), ('z', 3)])
 
 
 

使用这个映射我们可以很轻松的实现我们的强制类型检查:

代码语言:javascript
复制
   for name, value in bound_values.arguments.items():
 ...     if name in bound_types.arguments:
 ...         if not isinstance(value, bound_types.arguments[name]):
 ...             raise TypeError()
 ...
 
 
 

不过这个方案还有点小瑕疵,它对于有默认值的参数并不适用。 比如下面的代码可以正常工作,尽管items的类型是错误的:

代码语言:javascript
复制
   @typeassert(int, list)
 ... def bar(x, items=None):
 ...     if items is None:
  sig = signature(spam)
 >>> print(sig)
 (x, y, z=42)
 >>> sig.parameters
 mappingproxy(OrderedDict([('x', <Parameter at 0x10077a050 'x'>),
 ('y', <Parameter at 0x10077a158 'y'>), ('z', <Parameter at 0x10077a1b0 'z'>)]))
 >>> sig.parameters['z'].name
 'z'
 >>> sig.parameters['z'].default
 42
 >>> sig.parameters['z'].kind
 <_ParameterKind: 'POSITIONAL_OR_KEYWORD'>
 >>>
 装饰器的开始部分,我们使用了 bind_partial() 方法来执行从指定类型到名称的部分绑定。 下面是例子演示:
 
 >>> bound_types = sig.bind_partial(int,z=int)
 >>> bound_types
 <inspect.BoundArguments object at 0x10069bb50>
 >>> bound_types.arguments
 OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])
 >>>
 在这个部分绑定中,你可以注意到缺失的参数被忽略了(比如并没有对y进行绑定)。 不过最重要的是创建了一个有序字典 bound_types.arguments 。 这个字典会将参数名以函数签名中相同顺序映射到指定的类型值上面去。 在我们的装饰器例子中,这个映射包含了我们要强制指定的类型断言。
 
 在装饰器创建的实际包装函数中使用到了 sig.bind() 方法。 bind() 跟 bind_partial() 类似,但是它不允许忽略任何参数。因此有了下面的结果:
 
 >>> bound_values = sig.bind(1, 2, 3)
 >>> bound_values.arguments
 OrderedDict([('x', 1), ('y', 2), ('z', 3)])
 >>>
 使用这个映射我们可以很轻松的实现我们的强制类型检查:
 
 >>> for name, value in bound_values.arguments.items():
 ...     if name in bound_types.arguments:
 ...         if not isinstance(value, bound_types.arguments[name]):
 ...             raise TypeError()
 ...
 >>>
 不过这个方案还有点小瑕疵,它对于有默认值的参数并不适用。 比如下面的代码可以正常工作,尽管items的类型是错误的:
 
 >>> @typeassert(int, list)
 ... def bar(x, items=None):
 ...     if items is None:
 ...         items = []
 ...     items.append(x)
 ...     return items
 bar(2)
 [2]
 bar(2,3)
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "contract.py", line 33, in wrapper
 TypeError: Argument items must be <class 'list'>
 bar(4, [1, 2, 3])
 sig.parameters['z'].kind
 <_ParameterKind: 'POSITIONAL_OR_KEYWORD'>
 >>>
 装饰器的开始部分,我们使用了 bind_partial() 方法来执行从指定类型到名称的部分绑定。 下面是例子演示:
 
 >>> bound_types = sig.bind_partial(int,z=int)
 >>> bound_types
 <inspect.BoundArguments object at 0x10069bb50>
 >>> bound_types.arguments
 OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])
 >>>
 在这个部分绑定中,你可以注意到缺失的参数被忽略了(比如并没有对y进行绑定)。 不过最重要的是创建了一个有序字典 bound_types.arguments 。 这个字典会将参数名以函数签名中相同顺序映射到指定的类型值上面去。 在我们的装饰器例子中,这个映射包含了我们要强制指定的类型断言。
 
 在装饰器创建的实际包装函数中使用到了 sig.bind() 方法。 bind() 跟 bind_partial() 类似,但是它不允许忽略任何参数。因此有了下面的结果:
 
 >>> bound_values = sig.bind(1, 2, 3)
 >>> bound_values.arguments
 OrderedDict([('x', 1), ('y', 2), ('z', 3)])
 >>>
 使用这个映射我们可以很轻松的实现我们的强制类型检查:
 
 >>> for name, value in bound_values.arguments.items():
 ...     if name in bound_types.arguments:
 ...         if not isinstance(value, bound_types.arguments[name]):
 ...             raise TypeError()
 ...
 >>>
 不过这个方案还有点小瑕疵,它对于有默认值的参数并不适用。 比如下面的代码可以正常工作,尽管items的类型是错误的:
 
 >>> @typeassert(int, list)
 ... def bar(x, items=None):
 ...     if items is None:
 ...         items = []
 ...     items.append(x)
 ...     return items
 >>> bar(2)
 [2]
 >>> bar(2,3)
 Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "contract.py", line 33, in wrapper
 TypeError: Argument items must be <class 'list'>
 >>> bar(4, [1, 2, 3])
 [1, 2, 3, 4]
 
 
 
 

最后一点是关于适用装饰器参数和函数注解之间的争论。 例如,为什么不像下面这样写一个装饰器来查找函数中的注解呢?

代码语言:javascript
复制
@typeassert
 def spam(x:int, y, z:int = 42):
 print(x,y,z)
 一个可能的原因是如果使用了函数参数注解,那么就被限制了。 如果注解被用来做类型检查就不能做其他事情了。而且 @typeassert 不能再用于使用注解做其他事情的函数了。 而使用上面的装饰器参数灵活性大多了,也更加通用。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 调用方法
  • 装饰器说明
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档