1 背景

今天在 Github 的时间线上看到了一个有意思的 Python 库,名字叫做 "IceCream"。这个库用来取代 print() 函数,实现更加可控且优雅的调试输出。IceCream 的接口形式非常简单,只要直接将函数的调用形式传入即可。如下面的代码:

1
2
3
4
5
6
from icecream import ic

def foo(i):
return i + 333

ic(foo(123))

会产生下面的输出

1
ic| foo(123): 456

这个是怎么实现的呢?我查了一下代码发现 ic 函数(实际上一个实现了 __call__ 的类的实例)主要使用了 inspect 这个库。这是一个 Python 的标准库而我从来没有用过。

IceCream 的 Logo

按照官方库的说法,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 模块的使用还是比较简单和直接的。