使用装饰器进行函数类型检查

动态类型的特性使得Python函数在被调用时,其参数类型不易被知晓。或者,为了动态支持多类型,实际参数的类型由调用者提供。如下:

def add(x, y):
    return x + y

print(add(2, 3)) # 5
print(add('Hello', ' World')) # Hello World

上面的例子可以看出,函数参数并没有指定类型,使得该函数支持多种类型,这也正是Python语言的特殊之处。

但有时候,我们想限制函数的参数类型。这时很多人会想到类型提示(Type Hinting),即类型注解。如下:

def add(x:str, y:str) -> str:
    return x + y

然而,类型提示仅仅作为编程规约。在实际调用中无法强制类型约束,也不会有任何报错,如下:

print(add(2, 3)) # 5

若要强制类型检查,只能在编程中进行类型检查,然后进行异常提示。代码如下:

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

        # 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).arguments
            # Enforce type assertions across supplied arguments
            for name, value in bound_values.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

@typeassert(int, int)
def add(x, y):
    return x + y

@typeassert(int, z=int)
def spam(x, y, z=42):
    print(x, y, z)

>>> spam(1, 2, 3)
12 3
>>> spam(1, 'hello', 3)
1 hello 3
>>> spam(1, 'hello', 'world') T
raceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "contract.py", line 33, in wrapper
TypeError: Argument z must be <class 'int'>
>>>

编写此装饰器的一个棘手的部分是,它涉及检查并使用要包装的函数的参数签名。 此处选择的工具应该是inspect.signature()函数。 简而言之,它允许从可调用对象中提取签名信息。
如下:

>>> from inspect import signature
>>> def spam(x, y, z=42):
...     pass
...
>>> sig = signature(spam)
>>> print(sig)
(x, y, z=42)
>>> sig.parameters
mappingproxy({'x': <Parameter "x">,
              'y': <Parameter "y">,
              'z': <Parameter "z=42">})
>>> sig.parameters['z'].name
'z'
>>> sig.parameters['z'].default
42
>>> sig.parameters['z'].kind
<_ParameterKind.POSITIONAL_OR_KEYWORD: 1>
>>>

在装饰器的第一部分,使用signature对象的bind_partial()方法对提供的类型与参数名称进行部分绑定。

>>> bound_types = sig.bind_partial(int,z=int)
>>> bound_types
<BoundArguments (x=<class 'int'>, z=<class 'int'>)>
>>> 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)])
>>>

原文地址:https://www.cnblogs.com/jeffrey-yang/p/12257512.html

时间: 2024-08-29 14:25:52

使用装饰器进行函数类型检查的相关文章

关于Python装饰器内层函数为什么要return目标函数的一些个人见解

https://blog.csdn.net/try_test_python/article/details/80802199 前几天在学装饰器的时候,关于装饰器内层函数调用目标函数时是否return目标函数的调用产生了一点迷惑,事实是当被装饰的目标函数有返回值的时候,装饰器内层函数也必须返回该目标函数的调用. 我们都知道不带括号的函数名指向是函数代码所在的内存地址,加上括号之后就变成了一个执行命令,那么这个'func( )'到底有什么意义呢? 上面这张图可以大概看出点东西,单独的函数名是 fun

diango中让装了装饰器的函数的名字不是inner,而是原来的名字

让装了装饰器的函数的名字不是inner,而是原来的名字 from functools import wraps def wrapper(func): @wraps(func) # 复制了原来函数的名字和注释 def inner(request,*arg,**kwargs): # 之前 ret = func(request,*arg,**kwargs) # 之后 return ret return inner @wrapper # f1 = wrapper(f1) def f1(request):

装饰器工厂函数

""" 需求:参数传入0 希望时间用整数显示,参数传入1 用浮点数显示 """ import time def get_run_time(flag): """装饰器工厂函数""" def get_time(func): """装饰器函数:对函数运行时间进行统计""" print('in get_time') def inner(

python笔记--3--函数、生成器、装饰器、函数嵌套定义、函数柯里化

函数 函数定义语法: def 函数名([参数列表]): '''注释''' 函数体 函数形参不需要声明其类型,也不需要指定函数返回值类型 即使该函数不需要接收任何参数,也必须保留一对空的圆括号 括号后面的冒号必不可少 函数体相对于def关键字必须保持一定的空格缩进 Python允许嵌套定义函数 在定义函数时,开头部分的注释并不是必需的,但是如果为函数的定义加上这段注释的话,可以为用户提供友好的提示和使用帮助. Python是一种高级动态编程语言,变量类型是随时可以改变的.Python中的函数和自定

装饰器(函数)

闭包 1.作用域L_E_G_B(局部.内嵌.全局...): 1 x=10#全局 2 3 def f(): 4 a=5 #嵌套作用域 5 def inner(): 6 count = 7 #局部变量 7 print a 8 return 1 从内往外寻找 a . 2.高阶函数 a.函数名可以作为参数输入 b.函数名可以作为返回值 3.闭包 1 def outer(): 2 x=10 3 def inner(): #条件1 inner是内部函数 4 print(x) #条件2 外部环境的一个变量 5

Python学习(十)—— 装饰器和函数闭包

装饰器 装饰器:本质就是函数,功能是为其他函数添加附加功能 原则: 1.不修改被修饰函数的源代码 2.不修改被修饰函数的调用方式 统计程序运行的时间(不使用装饰器): 这种方法修改了源代码,不能用于已经上线的程序 1 import time 2 def calc(l): 3 res = 0 4 start_time = time.time() 5 for i in l: 6 res += i 7 time.sleep(0.01) 8 stop_time = time.time() 9 print

装饰器与函数的多层嵌套

# coding: utf-8 def login(func): print("the first level") def inner1(*args): print("the second level") def inner2(*args): print("the third level") def inner3(*args): print("the forth level") func(*args) func(*args)

python-day04 内置函数和函数装饰器

python内置函数 1.数学相关 abs(x) 取x绝对值 divmode(x,y) 取x除以y的商和余数,常用做分页,返回商和余数组成一个元组 pow(x,y[,z]) 取x的y次方 ,等同于x ** y,如果给出z值,该函数就计算x的y次幂值被z取模的值 round(x,[,n]) 四舍五入取x的值,n表示取小数点几位 min(X) 取X中最小的值 max(X) 取X中最大值 练习举例: >>> abs(-10) #取-10的绝对值 10 >>> abs(10)

python基础篇【第四篇】内置函数、装饰器:

一.内置函数: 对于一些python中常用的函数,python自身就已经定义了一些函数用于调用,我们就叫它内置函数!如以下都是python中内置的函数! 一些常用的内置函数具体用法: 一些常用的内置函数具体用法: 1.abs():取绝对值 1 n=abs(-2) 2 print(n) 3 4 结果: 5 2 2.all();传入可迭代的对象中,都为真才为真; Fasle: 0,None,"",[],{} ,()都为假True: 1,-1,True,有值返回真. n=all([1,2,3