zoukankan      html  css  js  c++  java
  • 如何用python的装饰器定义一个像C++一样的强类型函数

        Python作为一个动态的脚本语言,其函数在定义时是不需要指出参数的类型,也不需要指出函数是否有返回值。本文将介绍如何使用python的装饰器来定义一个像C++那样的强类型函数。接下去,先介绍python3中关于函数的定义。
    0. python3中的函数定义
    举个例子来说吧,比如如下的函数定义:
     
    1 def fun(a:int, b=1, *c, d, e=2, **f) -> str:
    2      pass
    这里主要是说几点与python2中不同的点。
    1)分号后面表示参数的annotation,这个annotation可以是任意的python对象,不一定要type类型的,比如 a:"This is parameter a!" 这样也是可以的。
    2)在可变位置参数列表c和可变关键字参数字典f中间的参数叫做 指定关键字参数(keyword only parameter),这个关键字的意思是你在函数调用时只能用关键字形式调用。比如调用下面的这个例子,调用fun(1,2)是会报trace的,因为没有指定keyword-only参数,如果调用fun(1,c=2)则不会出错。注意,如果要定义keyword-only参数,那么必须在其前面加一个可变的位置参数。
    3)-> 后面的是函数的返回值,属于函数返回值的annotation。
    4)虽然函数可以指定参数的annotation,但python对参数的类型还是没有限制的,比如上面的参数a不是说一定要是int类型,比如上面的函数不一定要返回一个str对象。比如下面的例子,fun的参数a可以是字符串,函数也不一定要返回值。
    接下去,本文介绍利用inspect.getfullargspec()来定义一个强类型的函数。
    1. inspect.getfullargspec()
     
    getfullargspec函数返回一个七元组:
    args:确定的参数名字列表,这个参数是定义在函数最开始的位置,这里又包括两种类型的参数,有默认参数的和没有默认参数的,并且有默认参数的必须要跟在没有默认参数的后头。
    varargs:可变位置参数的名字,这种参数的位置紧接着args后面。
    varkw:可变关键字参数,这种参数的位置在最后面。
    defaults:确定参数的默认值元组,这个元组的第一个值是最后一个默认参数,也就是于args的排列顺序是相反的,原因嘛,仔细想想应该也就清楚了。
    kwonlyargs:指定关键字参数名字,这种类型的参数其实跟args参数一样,只不过这种类型的参数必须跟在可变位置参数后面,可变关键字参数前面。另外,这种参数也分两种,有默认值和无默认值,但是与args参数不同的是有默认值和无默认值的位置没有强制的顺序,因为他们总是要指定关键字的。
    kwonlydefaults:指定关键字参数的默认值字典,注意这里与defaults不同的是,defaults是元组,这里是字典,原因当然是因为指定关键字参数的默认值参数顺序不定。
    annotations:这个就是参数的annotation字典,包括返回值以及每个参数分号后面的对象。
    有了上面这个工具,我们就可以定义一个强类型的函数了。
    2. 使用python3的函数annotation来定义强类型的函数装饰器
    首先,我们必须得保证参数的annotation以及返回值必须是类型,也就是必须是Type的instance。
    这里就是取出函数的annotations,然后验证是不是type的instance。现在我们可以确保所有的annotation都是有效的。接下去就是验证参数的类型是否是annotations里面所指定的type,最简单的想法当然就是看看参数名字是否在annotations中,如果在annotations中的话在看看参数值是不是annotations中指定的type。
    看起来似乎非常OK,但我们来看一下这些例子:
    >>> @typesafe
    ... def example(*args:int, **kwargs:str):
    ...      pass
    ...
    >>> example(spam='eggs')
    >>> example(kwargs='spam')
    >>> example(args='spam')
    Traceback (most recent call last):
    ...
    TypeError: Wrong type for args: expected int, got str.
    我们来分析一下出错的原因。首先这个函数的本意应该是希望所有的可变位置参数都是int型,所有的可变关键字参数都是str型。但是example函数的annotations应该是{‘args’: <class ‘int’>, ‘karges’: <class ‘str’>}字典,当取出args参数时发现这个参数名字在annotations中,就断定这个参数的值是int类型,从而导致出错。另外,如果我们调用example(spam=1)也不会出错,这于函数定义时的本意也是不符合的。综上考虑,我们还必须考虑args和kwargs参数所对应的annotation。另外,对于默认参数的类型和返回值也需要进行验证。

     1 # _*_ coding: utf-8
     2 import functools
     3 import inspect
     4 from itertools import chain
     5 
     6 def typesafe(func):
     7     """
     8     Verify that the function is called with the right arguments types and that
     9     it returns a value of the right type, accordings to its annotations.
    10     """
    11     spec = inspect.getfullargspec(func)
    12     annotations = spec.annotations
    13 
    14     for name, annotation in annotations.items():
    15         if not isinstance(annotation, type):
    16             raise TypeError("The annotation for '%s' is not a type." % name)
    17 
    18     error = "Wrong type for %s: expected %s, got %s."
    19     # Deal with default parameters
    20     defaults = spec.defaults or ()
    21     defaults_zip = zip(spec.args[-len(defaults):], defaults)
    22     kwonlydefaults = spec.kwonlydefaults or {}
    23     for name, value in chain(defaults_zip, kwonlydefaults.items()):
    24         if name in annotations and not isinstance(value, annotations[name]):
    25             raise TypeError(error % ('default value of %s' % name,
    26                                      annotations[name].__name__,
    27                                      type(value).__name__))
    28 
    29     @functools.wraps(func)
    30     def wrapper(*args, **kwargs):
    31         # Populate a dictionary of explicit arguments passed positionally
    32         explicit_args = dict(zip(spec.args, args))
    33         keyword_args = kwargs.copy()
    34         # Add all explicit arguments passed by keyword
    35         for name in chain(spec.args, spec.kwonlyargs):
    36             if name in kwargs:
    37                 explicit_args[name] = keyword_args.pop(name)
    38 
    39         # Deal with explict arguments
    40         for name, arg in explicit_args.items():
    41             if name in annotations and not isinstance(arg, annotations[name]):
    42                 raise TypeError(error % (name,
    43                                          annotations[name].__name__,
    44                                          type(arg).__name__))
    45 
    46         # Deal with variable positional arguments
    47         if spec.varargs and spec.varargs in annotations:
    48             annotation = annotations[spec.varargs]
    49             for i, arg in enumerate(args[len(spec.args):]):
    50                 if not isinstance(arg, annotation):
    51                     raise TypeError(error % ('variable argument %s' % (i+1),
    52                                     annotation.__name__,
    53                                     type(arg).__name__))
    54 
    55         # Deal with variable keyword argument
    56         if spec.varkw and spec.varkw in annotations:
    57             annotation = annotations[spec.varkw]
    58             for name, arg in keyword_args.items():
    59                 if not isinstance(arg, annotation):
    60                     raise TypeError(error % (name,
    61                                              annotation.__name__,
    62                                              type(arg).__name__))
    63 
    64         # Deal with return value
    65         r = func(*args, **kwargs)
    66         if 'return' in annotations and not isinstance(r, annotations['return']):
    67             raise TypeError(error % ('the return value',
    68                                      annotations['return'].__name__,
    69                                      type(r).__name__))
    70         return r
    71 
    72     return wrapper

    对于上面的代码:

    19-27 行比较好理解,就是验证函数定义时,默认参数和annotation是否匹配,如果不匹配的就返回定义错误。
    32 行spec.args和args的长度不一定会一样长,如果args的长度比较长,说明多出来的是属于可变位置参数,在46行到52行处理;如果spec.args的位置比较长,说明没有可变位置参数,多出来的已经通过默认值指定了,已经在19-27行处理了。
    34-37 行主要是考虑到,1)有一些确定的参数在函数调用时也有可能使用参数名进行调用;2)指定关键字参数必须使用参数名进行调用。
    这样处理后,keyword_args中就只存储可变关键字参数了,而explicit_args里存储的是包括确定型参数以及指定关键字参数。
    39-44 行处理explicit_args里面的参数
    46-53 行处理可变位置参数
    55-62 行处理的是可变关键字参数
    64-69 行处理返回值
     
    当然这个函数对类型的要求太高了,而并没有像C++那样有默认的类型转换。以下是自己写的一个带有默认类型转换的代码,如有错误望指出。
      1 # _*_ coding: utf-8
      2 import functools
      3 import inspect
      4 from itertools import chain
      5 
      6 def precessArg(value, annotation):
      7     try:
      8         return annotation(value)
      9     except ValueError as e:
     10         print('value:', value)
     11         raise TypeError('Expected: %s, got: %s' % (annotation.__name__,
     12                                                    type(value).__name__))
     13 
     14 def typesafe(func):
     15     """
     16     Verify that the function is called with the right arguments types and that
     17     it returns a value of the right type, accordings to its annotations.
     18     """
     19     spec = inspect.getfullargspec(func)
     20     annotations = spec.annotations
     21 
     22     for name, annotation in annotations.items():
     23         if not isinstance(annotation, type):
     24             raise TypeError("The annotation for '%s' is not a type." % name)
     25 
     26     error = "Wrong type for %s: expected %s, got %s."
     27     # Deal with default parameters
     28     defaults = spec.defaults and list(spec.defaults) or []
     29     defaults_zip = zip(spec.args[-len(defaults):], defaults)
     30     i = 0
     31     for name, value in defaults_zip:
     32         if name in annotations:
     33             defaults[i] = precessArg(value, annotations[name])
     34         i += 1
     35     func.__defaults__ = tuple(defaults)
     36 
     37     kwonlydefaults = spec.kwonlydefaults or {}
     38     for name, value in kwonlydefaults.items():
     39         if name in annotations:
     40             kwonlydefaults[name] = precessArg(value, annotations[name])
     41     func.__kwdefaults__ = kwonlydefaults
     42 
     43     @functools.wraps(func)
     44     def wrapper(*args, **kwargs):
     45         keyword_args = kwargs.copy()
     46         new_args = args and list(args) or []
     47         new_kwargs = kwargs.copy()
     48         # Deal with explicit argument passed positionally
     49         i = 0
     50         for name, arg in zip(spec.args, args):
     51             if name in annotations:
     52                 new_args[i] = precessArg(arg, annotations[name])
     53             i += 1
     54 
     55         # Add all explicit arguments passed by keyword
     56         for name in chain(spec.args, spec.kwonlyargs):
     57             poped_name = None
     58             if name in kwargs:
     59                 poped_name = keyword_args.pop(name)
     60             if poped_name is not None and name in annotations:
     61                 new_kwargs[name] = precessArg(poped_name, annotations[name]) 
     62 
     63         # Deal with variable positional arguments
     64         if spec.varargs and spec.varargs in annotations:
     65             annotation = annotations[spec.varargs]
     66             for i, arg in enumerate(args[len(spec.args):]):
     67                 new_args[i] = precessArg(arg, annotation)
     68 
     69         # Deal with variable keyword argument
     70         if spec.varkw and spec.varkw in annotations:
     71             annotation = annotations[spec.varkw]
     72             for name, arg in keyword_args.items():
     73                 new_kwargs[name] = precessArg(arg, annotation)
     74 
     75         # Deal with return value
     76         r = func(*new_args, **new_kwargs)
     77         if 'return' in annotations:
     78             r = precessArg(r, annotations['return'])
     79         return r
     80 
     81     return wrapper
     82 
     83 
     84 if __name__ == '__main__':
     85     print("Begin test.")
     86     print("Test case 1:")
     87     try:
     88         @typesafe
     89         def testfun1(a:'This is a para.'):
     90             print('called OK!')
     91     except TypeError as e:
     92         print("TypeError: %s" % e)
     93 
     94     print("Test case 2:")
     95     try:
     96         @typesafe
     97         def testfun2(a:int,b:str = 'defaule'):
     98             print('called OK!')
     99         testfun2('str',1)
    100     except TypeError as e:
    101         print("TypeError: %s" % e)
    102 
    103     print("test case 3:")
    104     try:
    105         @typesafe
    106         def testfun3(a:int, b:int = 'str'):
    107             print('called OK')
    108     except TypeError as e:
    109         print('TypeError: %s' % e)
    110 
    111     print("Test case 4:")
    112     try:
    113         @typesafe
    114         def testfun4(a:int = '123', b:int = 1.2):
    115             print('called OK.')
    116             print(a, b)
    117         testfun4()
    118     except TypeError as e:
    119         print('TypeError: %s' % e)
    120 
    121     @typesafe
    122     def testfun5(a:int, b, c:int = 1, d = 2, *e:int, f:int, g, h:int = 3, i = 4, **j:int) -> str :
    123         print('called OK.')
    124         print(a, b, c, d, e, f, g, h, i, j)
    125         return 'OK'
    126 
    127     print("Test case 5:")
    128     try:
    129         testfun5(1.2, 'whatever', f = 2.3, g = 'whatever')
    130     except TypeError as e:
    131         print('TypeError: %s' % e)
    132 
    133     print("Test case 6:")
    134     try:
    135         testfun5(1.2, 'whatever', 2.2, 3.2, 'e1', f = '123', g = 'whatever')
    136     except TypeError as e:
    137         print('TypeError: %s' % e)
    138 
    139     print("Test case 7:")
    140     try:
    141         testfun5(1.2, 'whatever', 2.2, 3.2, 12, f = '123', g = 'whatever')
    142     except TypeError as e:
    143         print('TypeError: %s' % e)
    144 
    145     print("Test case 8:")
    146     try:
    147         testfun5(1.2, 'whatever', 2.2, 3.2, 12, f = '123', g = 'whatever', key1 = 'key1')
    148     except TypeError as e:
    149         print('TypeError: %s' % e)
    150 
    151     print("Test case 9:")
    152     try:
    153         testfun5(1.2, 'whatever', 2.2, 3.2, 12, f = '123', g = 'whatever', key1 = '111')
    154     except TypeError as e:
    155         print('TypeError: %s' % e)
    156 
    157     print('Test case 10:')
    158     @typesafe
    159     def testfun10(a) -> int:
    160         print('called OK.')
    161         return 'OK'
    162     try:
    163         testfun10(1)
    164     except TypeError as e:
    165         print('TypeError: %s' % e)
    166 
    167 
    168 
    169 
    170     
     
     
     
     
  • 相关阅读:
    ObjectC&&Swift 渐变色算法实现
    【iOS数据存储】iOS文件系统介绍
    1 、Quartz 2D绘图基础
    iOS 常用框架列表
    【Foundation Frame】Struct
    【Foundation Frame】NSMutableArray
    【Foundation Frame】NSDictionary/NSMutableDictionary
    【Foundation Frame】NSString
    【Foundation Frame】NSArray
    在vue项目中使用自己封装的ajax
  • 原文地址:https://www.cnblogs.com/boostable/p/python_annotations.html
Copyright © 2011-2022 走看看