【Python随笔】如何提取python函数的接口定义信息

在某些python框架底层的开发需求中,需要通过一些类似反射的手段,提取函数接口的信息,从而对一系列函数接口进行管理。本篇文章就来粗浅谈一下,如何提取python函数的接口定义信息。

function对象的信息

首先我们拿一个函数来试试手:

1
2
3
4
5
def myprinter(s: str, /, e: str = '\n', *, prefix: Any = '') -> Tuple[bool, str]:
"""hello world"""
msg = str(prefix) + s + e
print(msg, end='')
return True, msg

这个函数myprinter采用了新的语法糖/以及*/的左边表示强制位置参数(positional-only arguments),*的右边表示强制关键字参数(keyword-only arguments),而两者中间的参数在使用时以位置参数或者是关键字参数的形式表达都可以。

要提取这个函数的信息,我们首先要知道函数本身也是一种对象。因此,可以用到dir函数,遍历这个对象的属性,从而提取所有属性的值。

通过dir导出函数对象的属性与值,打印的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{'__annotations__': {'e': <class 'str'>,
'prefix': typing.Any,
'return': typing.Tuple[bool, str],
's': <class 'str'>},
'__call__': <method-wrapper '__call__' of function object at 0x000001C78C54F040>,
'__class__': <class 'function'>,
'__closure__': None,
'__code__': <code object myprinter at 0x000001C78CC823A0, file "H:\Projects\Python\playground\datastruct\functionobj.py", line 11>,
'__defaults__': ('\n',),
'__delattr__': <method-wrapper '__delattr__' of function object at 0x000001C78C54F040>,
'__dict__': {},
'__dir__': [函数对象的属性列表,其实就打印的这些,不再重复列举了],
'__doc__': 'hello world',
'__eq__': <method-wrapper '__eq__' of function object at 0x000001C78C54F040>,
'__format__': <built-in method __format__ of function object at 0x000001C78C54F040>,
'__ge__': <method-wrapper '__ge__' of function object at 0x000001C78C54F040>,
'__get__': <method-wrapper '__get__' of function object at 0x000001C78C54F040>,
'__getattribute__': <method-wrapper '__getattribute__' of function object at 0x000001C78C54F040>,
'__globals__': {函数对应的全局环境情况,不一一列举了},
'__gt__': <method-wrapper '__gt__' of function object at 0x000001C78C54F040>,
'__hash__': 122285281028,
'__init__': None,
'__init_subclass__': None,
'__kwdefaults__': {'prefix': ''},
'__le__': <method-wrapper '__le__' of function object at 0x000001C78C54F040>,
'__lt__': <method-wrapper '__lt__' of function object at 0x000001C78C54F040>,
'__module__': '__main__',
'__name__': 'myprinter',
'__ne__': <method-wrapper '__ne__' of function object at 0x000001C78C54F040>,
'__new__': <built-in method __new__ of type object at 0x00007FFF57678C50>,
'__qualname__': 'myprinter',
'__reduce__': <built-in method __reduce__ of function object at 0x000001C78C54F040>,
'__reduce_ex__': <built-in method __reduce_ex__ of function object at 0x000001C78C54F040>,
'__repr__': '<function myprinter at 0x000001C78C54F040>',
'__setattr__': <method-wrapper '__setattr__' of function object at 0x000001C78C54F040>,
'__sizeof__': 120,
'__str__': '<function myprinter at 0x000001C78C54F040>',
'__subclasshook__': NotImplemented}

我们可以看到里面的一些关键属性:

  • __name__:函数名字。
  • __qualname__:函数的合法名称
    • 比如在class A中定义一个函数f,那么A.f.__name__f,而A.f.__qualname__A.f
  • __doc__:函数注释文本
  • __annotations__:函数参数注释信息
    • 一般开发时候注释信息都是参数名以及对应的数据类型,因此有了这个就可以对函数的入参进行合法校验。当然,明显也利好一些IoC机制的底层开发的需求。
    • 返回值对应的数据类型,可以在return这个key下找
  • __defaults__:位置参数/非强制关键字参数的默认值的tuple
  • __kwdefaults__:强制关键字参数的默认值的dict
  • __module__:函数所属的模块
  • __code__:函数所包含的代码段对象

除了通过dir的方式获取这些关键属性之外,通过inspect库的getmembers函数也可以实现类似的效果。对于inspect库的用法,可以参考官方文档

参数数据结构类型信息

在上述的__annotations__属性中,我们能够提取到标注的参数数据结构类型。对于strint这种一般型数据就直接标注对应的类型;而对于一些泛型则需要标注为typing,比如说typing.Dicttyping.Any等等。

针对typing的用法,可以参考官方文档。不过本文计划进一步探索typing对象的相关信息。

我们用一段代码就能粗略对typing的各类对象信息看个明白:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
print('typing.Dict:')
dtype = typing.Dict[str, typing.Union[
typing.List[int],
typing.Tuple[bool, str],
typing.Callable[[float, int], typing.NoReturn]
]]
pprint.pprint(dtype.__dict__)
print('typing.Union:')
utype = dtype.__dict__['__args__'][1]
pprint.pprint(utype.__dict__)
print('typing.List:')
ltype = utype.__dict__['__args__'][0]
pprint.pprint(ltype.__dict__)
print('typing.Tuple:')
ttype = utype.__dict__['__args__'][1]
pprint.pprint(ttype.__dict__)
print('typing.Callable:')
ftype = utype.__dict__['__args__'][2]
pprint.pprint(ftype.__dict__)
print('typing.NoReturn:')
norettype = ftype.__dict__['__args__'][-1]
print(norettype._name)
print(isinstance(norettype, typing._SpecialForm))

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
typing.Dict:
{'__args__': (<class 'str'>,
typing.Union[typing.List[int], typing.Tuple[bool, str], typing.Callable[[float, int], typing.NoReturn]]),
'__origin__': <class 'dict'>,
'__parameters__': (),
'__slots__': None,
'_inst': False,
'_name': 'Dict'}
typing.Union:
{'__args__': (typing.List[int],
typing.Tuple[bool, str],
typing.Callable[[float, int], typing.NoReturn]),
'__module__': 'typing',
'__origin__': typing.Union,
'__parameters__': (),
'__slots__': None,
'_inst': True,
'_name': None}
typing.List:
{'__args__': (<class 'int'>,),
'__origin__': <class 'list'>,
'__parameters__': (),
'__slots__': None,
'_inst': False,
'_name': 'List'}
typing.Tuple:
{'__args__': (<class 'bool'>, <class 'str'>),
'__origin__': <class 'tuple'>,
'__parameters__': (),
'__slots__': None,
'_inst': False,
'_name': 'Tuple'}
typing.Callable:
{'__args__': (<class 'float'>, <class 'int'>, typing.NoReturn),
'__origin__': <class 'collections.abc.Callable'>,
'__parameters__': (),
'__slots__': None,
'_inst': True,
'_name': 'Callable'}
typing.NoReturn:
NoReturn
True

对于一些容器类型的对象,通过__dict__['__args__']就能够获得里面的数据类型标注,从而对容器对象合法性进行检查。对于一些隶属于typing._SpecialForm的对象,比如AnyNoReturn等,可以通过_name属性来判断具体是哪个。

函数体对象的信息

code对象(函数的函数体)的信息,从上面的属性导出情况可知,我们可以通过__code__来获取函数的函数体对象。

通过dir或者inspect等方式,可以导出code对象的属性:

1
2
codeobj = myprinter.__code__
pprint.pprint(objdict(codeobj))

打印结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
{'__class__': <class 'code'>,
'__delattr__': <method-wrapper '__delattr__' of code object at 0x0000012FB44A2500>,
'__dir__': [忽略],
'__doc__': 'code(argcount, posonlyargcount, kwonlyargcount, nlocals, '
'stacksize,\n'
' flags, codestring, constants, names, varnames, filename, '
'name,\n'
' firstlineno, lnotab[, freevars[, cellvars]])\n'
'\n'
'Create a code object. Not for the faint of heart.',
'__eq__': <method-wrapper '__eq__' of code object at 0x0000012FB44A2500>,
'__format__': <built-in method __format__ of code object at 0x0000012FB44A2500>,
'__ge__': <method-wrapper '__ge__' of code object at 0x0000012FB44A2500>,
'__getattribute__': <method-wrapper '__getattribute__' of code object at 0x0000012FB44A2500>,
'__gt__': <method-wrapper '__gt__' of code object at 0x0000012FB44A2500>,
'__hash__': -7836529936335626281,
'__init__': None,
'__init_subclass__': None,
'__le__': <method-wrapper '__le__' of code object at 0x0000012FB44A2500>,
'__lt__': <method-wrapper '__lt__' of code object at 0x0000012FB44A2500>,
'__ne__': <method-wrapper '__ne__' of code object at 0x0000012FB44A2500>,
'__new__': <built-in method __new__ of type object at 0x00007FFF5766F870>,
'__reduce__': <built-in method __reduce__ of code object at 0x0000012FB44A2500>,
'__reduce_ex__': <built-in method __reduce_ex__ of code object at 0x0000012FB44A2500>,
'__repr__': '<code object myprinter at 0x0000012FB44A2500, file '
'"H:\\Projects\\Python\\playground\\datastruct\\functionobj.py", '
'line 12>',
'__setattr__': <method-wrapper '__setattr__' of code object at 0x0000012FB44A2500>,
'__sizeof__': 176,
'__str__': '<code object myprinter at 0x0000012FB44A2500, file '
'"H:\\Projects\\Python\\playground\\datastruct\\functionobj.py", '
'line 12>',
'__subclasshook__': NotImplemented,
'co_argcount': 2,
'co_cellvars': (),
'co_code': b't\x00|\x02\x83\x01|\x00\x17\x00|\x01\x17\x00}\x03t\x01|\x03'
b'd\x01d\x02\x8d\x02\x01\x00d\x03|\x03f\x02S\x00',
'co_consts': ('hello world', '', ('end',), True),
'co_filename': 'H:\\Projects\\Python\\playground\\datastruct\\functionobj.py',
'co_firstlineno': 12,
'co_flags': 67,
'co_freevars': (),
'co_kwonlyargcount': 1,
'co_lnotab': b'\x00\x02\x10\x01\x0c\x01',
'co_name': 'myprinter',
'co_names': ('str', 'print'),
'co_nlocals': 4,
'co_posonlyargcount': 1,
'co_stacksize': 4,
'co_varnames': ('s', 'e', 'prefix', 'msg'),
'replace': <code object myprinter at 0x0000012FB44CAA80, file "H:\Projects\Python\playground\datastruct\functionobj.py", line 12>}

其中一些关键属性如下(同样在inspect的文档中能查到):

  • co_argcount:参数个数(不包括强制关键字参数,以及带星号的参数)
  • co_filename: 所属文件
  • co_firstlineno:第一行行号
  • co_kwonlyargcount:强制关键字参数个数
  • co_name:函数体的名字
  • co_varnames:参数与变量的名字的tuple
版权声明
本文为博客HiKariのTechLab原创文章,转载请标明出处,谢谢~~~