Q's magic

Q's magic

September 20, 2020
Python其他文章
Python

前言 #

Q 是一个开源库,正如它介绍里所写的那样,它是用来“Quick and dirty debugging output for tired programmers.”,Q的用法主要有两种:

  1. import q之后, 使用q(value)来 print value 的值,或是使用q/value, 或者q|value,只要在想 print 的值前加“q/”或“q|”的前缀,即可以print值

  2. 在函数定义前加@q的修饰器,可以print 函数参数,返回值,运行时间等信息 本文用来展示q这个库是如何在300+行代码的篇幅下,完成这样的功能的。

import q之后为什么可以直接使用q()和@q #

想做到这件事的办法可以想到,如同其他语言一样q()需要被实现成一个全局的静态函数,在python中,则是在import时完成class q的实例化,那么另一个问题是,q 中 class 名是 Q ,那么调用应该是Q.q(),而不是q(),这部分的实现关键在于它将自己装载在了sys.module中,通过这条语句sys.modules['q'] = Q()并在class中实现__call__()函数,使得可以直接使用q()

q/value如何实现 #

q/value 修改一下格式就变为 q / value, 同时源码里也可以看到其中有__truediv__()函数,于是发现这里的实现在于 q 重载了/|运算,使得这个运算符在执行了q print的操作之后返回__truediv__()的第二个参数。

object.truediv(self, other) 调用这些方法来实现二进制算术运算 (+, -, *, @, /, //, %, divmod(), pow(), **, «, », &, ^, |)。例如,求表达式 x + y 的值,其中 x 是具有 add() 方法的类的一个实例,则会调用 x.add(y)。 python数据模型

这里可以看到 q/value 时会调用 q 的__truediv__()函数,这样在 q 重载了这个函数的时候,达到了重载操作符的效果。

@q如何实现 #

q被调用时调用了__call__()函数,这个函数主要做了有关的几件事:self.inspect.getframeinfo(self.sys._getframe(1), context=9)通过getframeinfo()来获取调用附近的代码文本块,打印被修饰函数的基本信息,然后扫描文本块返回self.trace(), python 定义修饰器需要返回一个以修饰函数 func 为参数的 wapper 函数,那么可以想到 trace 里会返回 wapper,那么在这里,则是返回了self.functools.update_wrapper(wrapper, func)

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) 更新一个 wrapper 函数以使其类似于 wrapped 函数。 可选参数为指明原函数的哪些属性要直接被赋值给 wrapper 函数的匹配属性的元组,并且这些 wrapper 函数的属性将使用原函数的对应属性来更新。 此函数的主要目的是在 decorator 函数中用来包装被装饰的函数并返回包装器。 如果包装器函数未被更新,则被返回函数的元数据将反映包装器定义而不是原始函数定义,这通常没有什么用处。 module-functools

trace 里仅仅是定义了wapper(),而在wapper()中执行了 func,然后返回了 func 的返回值,在这里修饰器得到了 func 的参数,(q.__call__(self, *args),这里被作为修饰器调用时,args[0] 是被修饰的函数,这个参数被传到了 trace 里)因此可以遍历参数并 print 它们的值,func 在wapper 中执行,这样也可以 print 返回值

在处理error时会有些麻烦,因为 func 是在 q 的wapper()中运行的,异常抛出时会抛到 wapper 的 栈帧,这里需要在 wapper 中获取 error 信息,这是通过 self.sys.exc_info()来做到的,通过 print error_trace_back.tb_next 可以正确显示异常。

reference #

  1. https://github.com/zestyping/q
  2. http://pyvideo.org/video/1858/sunday-evening-lightning-talks#t=25m15s