Python 没有可移植的方式来跟踪帧……但 CPython 实现可以:sys._getframe
返回一个帧对象。
你可以用一个框架对象做什么?请参阅文档中的方便图表以inspect
了解它具有的所有有趣的东西,但它们包括框架所见的locals()
和globals()
,以及框架中执行的代码对象——它本身包括本地名称、未绑定名称和用于闭包的单元格。
但是,正如其他人所指出的那样,您实际上并不需要框架。您所需要的只是本地人,而将其显式传递给您的上下文管理器要简单得多。
如果你真的想这样做:
import contextlib
import sys
@contextlib.contextmanager
def dumping():
f = sys._getframe(2)
fl = f.f_locals.copy()
try:
yield None
finally:
for name, value in f.f_locals.items():
if name not in fl:
print('{} = {}'.format(name, value))
bar = 0
def foo():
global bar
bar = 3
baz = 4
qux = 5
with dumping():
spam = 'eggs'
eggs = 3
bar = 4
baz = 5
foo()
运行时,应打印:
eggs = 3
spam = eggs
换句话说,只有在with
块中声明的新变量的名称和值——我想这就是你想要的,对吧?
如果你想要新的和反弹的本地人,你可能想要存储这样的东西:
fl = {(name, id(value)) for name, value in f.f_locals.items()}
当然,你也可以重新绑定非局部变量和全局变量,所以如果你关心这个,要么也存储全局变量(但一定要检查locals is globals
模块级代码),或者遍历闭包。
如果您使用的是 CPython 2(为什么?对于实际项目,它有时是有意义的,但是为了好玩而学习内部如何工作?然而,有些人……),相同的代码也可以工作。属性名称可能略有不同,但您可以通过转储dir
框架和代码来猜测它们。显然你想要 2.xprint
语法。
它也适用于 PyPy,至少 2.0b。
如果你想知道我是怎么知道使用的_getframe(2)
……我没有。我很确定它会增加 1 或 2 帧,可能只有 3 帧,但哪一个?所以我只是这样做了:
@contextlib.contextmanager
def dumping():
for i in range(4):
f = sys._getframe(i)
print(f.f_code.co_filename, f.f_code.co_firstlineno, f.f_lineno)
0当然是dumping
它自己;1 是中的包装函数contextlib.contextmanager
;2是调用帧;3是模块顶层。仔细想想,这很明显,但在我知道答案之前并不明显。:)