# Python Fire可以做什么? 这是一个通过暴露复杂系统内部接口,利用管道和CLI来方便测试的工具。 它可以自动扫描module, object, class, function并自动构建管道 Expose functionality to user by archive them in a function / module / object / class / dict # Python fire 源码分析: ## \_\_main__.py 主要负责import被使用的module,或者file,将其导入Fire.fire()函数。 ## trace.py 此module具有用于跟踪fire执行状况的类。fire的执行被分割为数个步骤,它们取值属于如下集合: (INITIAL_COMPONENT,INSTANTIATED_CLASS,CALLED_ROUTINE,CALLED_CALLABLE,ACCESSED_PROPERTY,COMPLETION_SCRIPT,INTERACTIVE_MODE ) FireTrace由一系列FireTraceElement对象组成,内部用list表示。在需要时可以由GetLastHealthyElement().component返回当前被操作的元素。 表示一个fire执行时的操作。一项行动可能是实例化类、调用例程或访问属性。 每个操作fire._CallAndUpdateTrace()都使用参数并产生一个新component。最后一部分由Fire序列化为stdout,并由Fire方法返回。如果出现Fire使用错误,例如提供的参数不足,无法调用 一个函数,则该Error将在trace中被capture并且final component将被设定为None。 ## inspect.py/inspectutils.py 提供inspection操作的功能支持。也就是对callable对象参数的解析。 主要的核心函数如下: ``` def Py3GetFullArgSpec(fn): ``` 内置getfullargspec的替代方法。 其实现使用库的_signature_from_callable() ``` sig = inspect._signature_from_callable( # pylint: disable=protected-access fn, skip_bound_arg=True, follow_wrapper_chains=True, sigcls=inspect.Signature) ``` ## decorators.py 这些decorators为python fire提供函数metadata。 这里的fn是指被调用的函数,是fire将准备好的参数传入的终端。 SetParseFn和SetParseFns允许设置被Fire用于解析命令行参数的函数。这些函数将被用于客户端代码。 ### SetParseFns(*positional, **named) 设置fire的fns,以便在调用被修饰的fn时用于解析参数。 返回一个decorator,当应用于函数时,它会将元数据添加到函数告诉Fire如何将字符串命令行参数转换为正确的用于调用函数的Python参数。 解析函数应该接受单个字符串参数并向在调用修饰函数时在其位置使用。 ## core.py ### _Fire(component, args, parsed_flag_args, context, name=None) 这是python fire的核心部分,完成fire程序的核心逻辑。几种参数含义如下 * component: 是fire构建CLI的初始元素,可以是module, class, routine, object... * args: 待component使用的参数序列 * parsed_flag_args: 启动fire的flag,定制化一些fire行为 * context: 调用fire时的local和global变量 * name: 可选的名字,在interactive模式使用 fire的一般执行过程如下: 1. 检查并处理flag(--xxx) 1. 选择当前component作为当前被处理的对象,根据参数的类型 * 是class,则实例化被使用的class * 是routine,则调用该routine * 是sequence,使用紧接的参数访问sequence * 否则,尝试用紧接的属性访问该对象 * 否则,如果是callable的示例,调用 1. 返回上一个步骤重复,直到所有args被消耗 当不再能够继续执行时,fire将结果序列化后输出。以上就是对fire主要功能的简单描述。其比较具体的实现如下。 我们可以注意到其比较核心的功能需要用到代码分析技术,而fire的实现如下: ``` is_callable = inspect.isclass(component) or inspect.isroutine(component) is_callable_object = callable(component) and not is_callable is_sequence = isinstance(component, (list, tuple)) is_map = isinstance(component, dict) or inspectutils.IsNamedTuple(component) ``` 其中主要使用到inspect库的各种isXXX函数。去到这个库的源文件查看,可以发现如下十分方便的函数:ismodule(), isclass(), ismethod(), isfunction(), isgeneratorfunction(), isgenerator(), istraceback(), isframe(), iscode(), isbuiltin(), isroutine()。 再检视inspect.py文件,可以发现大部分的isXXX()的实现用到了python builtins的库函数:isinstance(),issubclass(),getattr(),hasattr()... 有了python提供的这些函数,就可以实现变量的动态查找和识别,然后根据这些变量的不同类别,进行不同的操作。 当检测到component是class, routine, callable object时,调用_CallAndUpdateTrace() ### _CallAndUpdateTrace()实现 重要部分: ``` # 返回filename, line number filename, lineno = inspectutils.GetFileAndLine(component) # 生成当前component的metadata metadata = decorators.GetMetadata(component) # fn是在这个阶段工作中被调用的Callable对象 fn = component.__call__ if treatment == 'callable' else component # 生成fn的parse function parse = _MakeParseFn(fn, metadata) # 把被调用fn的参数解析出来,把args列表变为 # (varargs, kwargs), remaining_args的形式 (varargs, kwargs), consumed_args, remaining_args, capacity = parse(args) ``` metadata:用以描述属性的数据,在fire中使用一个<属性,值>的字典存储。 它的生成主要依靠 **getattr()**。 源代码如下: ``` def GetMetadata(fn): # ... metadata = getattr(fn, FIRE_METADATA, default) # ... ``` 最后,也是最关键的调用部分,非常直接。我们把已经处理好的(varargs, kwargs)传入: ``` if inspectutils.IsCoroutineFunction(fn): loop = asyncio.get_event_loop() component = loop.run_until_complete(fn(*varargs, **kwargs)) else: component = fn(*varargs, **kwargs) ```