You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lrpyopsr/Source_code_learning.md

5.7 KiB

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)
  2. 选择当前component作为当前被处理的对象根据参数的类型
    • 是class则实例化被使用的class
    • 是routine则调用该routine
    • 是sequence使用紧接的参数访问sequence
    • 否则,尝试用紧接的属性访问该对象
    • 否则如果是callable的示例调用
  3. 返回上一个步骤重复直到所有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)