1 背景
今天在 Github 的时间线上看到了一个有意思的 Python 库,名字叫做 "IceCream"。这个库用来取代 print()
函数,实现更加可控且优雅的调试输出。IceCream 的接口形式非常简单,只要直接将函数的调用形式传入即可。如下面的代码:
1 | from icecream import ic |
会产生下面的输出
1 | ic| foo(123): 456 |
这个是怎么实现的呢?我查了一下代码发现 ic
函数(实际上一个实现了 __call__
的类的实例)主要使用了 inspect
这个库。这是一个 Python 的标准库而我从来没有用过。
按照官方库的说法,inspect
模块提供了用来检查 Live Objects 的信息的能力。这里说的 Live Objects 可以包括模块,类,方法,函数,tracebacks, frame objects 以及 code objects。例如,你可以使用 inspect
模块来获取一个类的内容,提取一个方法的源代码,提取一个函数的参数格式,或者从一个 traceback 中提取详细的信息。
inspect
方法主要提供了 4 中不同类型的“服务”,包括:类型检查(Type Checking),源码获取(Getting Source Code),检查类与函数(Inspect Classes and Functions)以及获取解释器的栈(Examing the interpreter stack)。
我们来详细介绍这四个部分。
2 类型与成员
getmembers()
函数可以获取一个对象(如类或者模块)的成员。其返回的形式是一个元祖 (name, value)
的列表。函数的参数形式是:
1 | inspect.getmembers(object[, predicate]) |
其中除了待检查对象,调用者还可以传入一个 predicate
对象用来筛选这个函数返回的成员。inspect
模块提供了一系列以 is
开头的函数用来充当这个 predicate
。这里列举一些例子:
inspect.ismodule(object)
inspect.isclass(object)
inspect.ismethod(object)
- ...
在 Python 中,不同类型的对象具有的特殊成员可以看下面这个表:
Type | Attribute | Description |
---|---|---|
module | __doc__ |
documentation string |
__file__ |
filename (missing for built-in modules) | |
class | __doc__ |
documentation string |
__name__ |
name with which this class was defined | |
__qualname__ |
qualified name | |
__module__ |
name of module in which this class was defined | |
method | __doc__ |
documentation string |
__name__ |
name with which this method was defined | |
__qualname__ |
qualified name | |
__func__ |
function object containing implementation of method | |
__self__ |
instance to which this method is bound, or None | |
__module__ |
name of module in which this method was defined | |
function | __doc__ |
documentation string |
__name__ |
name with which this function was defined | |
__qualname__ |
qualified name | |
__code__ |
code object containing compiled function bytecode | |
__defaults__ |
tuple of any default values for positional or keyword parameters | |
__kwdefaults__ |
mapping of any default values for keyword-only parameters | |
__globals__ |
global namespace in which this function was defined | |
__annotations__ |
mapping of parameters names to annotations; "return" key is reserved for return annotations. | |
__module__ |
name of module in which this function was defined | |
traceback | tb_frame |
frame object at this level |
tb_lasti |
index of last attempted instruction in bytecode | |
tb_lineno |
current line number in Python source code | |
tb_next |
next inner traceback object (called by this level) | |
frame | f_back |
next outer frame object (this frame’s caller) |
f_builtins |
builtins namespace seen by this frame | |
f_code |
code object being executed in this frame | |
f_globals |
global namespace seen by this frame | |
f_lasti |
index of last attempted instruction in bytecode | |
f_lineno |
current line number in Python source code | |
f_locals |
local namespace seen by this frame | |
f_trace |
tracing function for this frame, or None | |
code | co_argcount |
number of arguments (not including keyword only arguments, * or ** args) |
co_code |
string of raw compiled bytecode | |
co_cellvars |
tuple of names of cell variables (referenced by containing scopes) | |
co_consts |
tuple of constants used in the bytecode | |
co_filename |
name of file in which this code object was created | |
co_firstlineno |
number of first line in Python source code | |
co_flags |
bitmap of CO_* flags, read more here | |
co_lnotab |
encoded mapping of line numbers to bytecode indices | |
co_freevars |
tuple of names of free variables (referenced via a function’s closure) | |
co_posonlyargcount |
number of positional only arguments | |
co_kwonlyargcount |
number of keyword only arguments (not including ** arg) | |
co_name |
name with which this code object was defined | |
co_names |
tuple of names of local variables | |
co_nlocals |
number of local variables | |
co_stacksize |
virtual machine stack space required | |
co_varnames |
tuple of names of arguments and localvariables | |
generator | __name__ |
name |
__qualname__ |
qualified name | |
gi_frame |
frame | |
gi_running |
is the generator running? | |
gi_code |
code | |
gi_yieldfrom |
object being iterated by yield from, or None | |
coroutine | __name__ |
name |
__qualname__ |
qualified name | |
cr_await |
object being awaited on, or None | |
cr_frame |
frame | |
cr_running |
is the coroutine running? | |
cr_code |
code | |
cr_origin |
where coroutine was created, or None. See sys.set_coroutine_origin_tracking_depth() | |
builtin | __doc__ |
documentation string |
__name__ |
original name of this function or method | |
__qualname__ |
qualified name | |
__self__ |
instance to which a method is bound, or None |
3 提取源码
inspect
可以获取对象的源代码信息,这部分相关的支持函数包括:
inspect.getdoc(object)
: 获取一个对象的文本字符串形式的介绍。如果目标对象的文本字符串是空的,这个函数会随着继承树上溯;inspect.getcommnets(object)
:获取一个对象前方最近的一行注释字符(对于类、函数或者方法),如果目标对象是一个模块,则会获取其所在文件的第一行字符串;inspect.getfile
: 获取对象被定义的文件名称(可能是文本类型文件,也可能是二进制文件);
剩下还有很多 API 函数,使用的时候查询官方文档即可。
4 获取函数和类的信息
inspect
模块提供了获取类和函数的信息的接口,这些接口可以获取类的继承树,函数的形参列表等。例如:
inspect.getclasstree(classes, unique=False)
: 可以以嵌套列表(nested lists)的形式返回类的继承树;inpsect.getfullargspec(func)
: 可以以具名元组(named tuples)的形式获取一个函数的参数列表。
5 获取解释器堆栈
这是本文开头提到的
IceCream
主要使用到的功能。
这类接口会以“帧记录”(frame records)的形式返回栈信息。每个帧记录是一个具名元组 FrameInfo(frame, filename, lineno, function, code_context, index)
。
这里也有一堆 API,我这篇文章也不是要充当官方文档,所以还是去官网看 API 具体形式吧~
发现这篇文章稍显多余了,其实 inspect
模块的使用还是比较简单和直接的。