从另一个Python程序中运行Python程序(具有特定要求)

5
假设我有两个 Python 脚本 A.py 和 B.py。我正在寻找一种在 A 中运行 B 的方法,使得:
  1. B 认为自己是 __main__(这样,如果 B 中有一个 if __name__=="__main__" 块,它将会运行)
  2. B 实际上并不是 __main__(这样它就不会覆盖 sys.modules 中的 "__main__" 条目)
  3. 在 B 中引发的异常传播到 A(即可以在 A 中用 except 子句捕获)。
  4. 如果未捕获这些异常,则生成正确的回溯,引用 B 中的行号。
我尝试了各种技术,但似乎没有一种能满足所有要求的。
  • 使用 subprocess 模块中的工具意味着 B 中的异常不会传播到 A。
  • execfile("B.py", {}) 运行 B,但它不认为它是主程序。
  • execfile("B.py", {'__name__': '__main__'}) 使 B.py 认为它是主程序,但它似乎也搞砸了异常回溯打印,以至于回溯引用 A 中的行(即真正的 __main__)。
  • 使用 imp.load_source 以 __main__ 作为名称几乎可以实现,但它实际上修改了 sys.modules,从而覆盖了现有的 __main__ 值。
有没有办法实现我想要的?
(我这样做的原因是因为我正在对一个现有库进行清理。这个库没有真正的测试套件,只有一组“示例”脚本,可以产生某些输出。我试图利用这些作为测试来确保我的清理不会影响库执行这些示例的能力,所以我想从我的测试套件中运行每个示例脚本。我希望能够在测试脚本中看到来自这些脚本的异常,以便测试脚本可以报告失败类型,而不仅仅是在示例脚本引发某些异常时报告通用的 SubprocessError。)
2个回答

2
回答自己的问题,因为结果有点有趣,可能对其他人有用:
事实证明我错了:execfile("B.py", {'__name__': '__main__'} 才是正确的方法。 它确实可以正确地产生跟踪信息。 我看到的不正确行号不是异常,而是警告。 这些警告使用warnings.warn("blah", stacklevel=2)生成。 stacklevel=2参数应该允许在使用已弃用的内容时引发诸如弃用警告之类的警告,而不是在警告调用处引发(请参阅文档)。
但是,似乎对于此目的,execfile-d文件不算作“堆栈级别”,并且对于stacklevel目的是不可见的。 因此,如果execfile-d模块的顶层代码导致具有堆栈级别2的警告,则警告不会在execfile-d源文件的正确行号处引发;相反,它将在运行execfile的文件的相应行号处引发。
这很不幸,但我可以接受它,因为这些只是警告,所以它们不会影响测试的实际性能。(起初我没有注意到行号不匹配的只是警告,因为测试输出中有很多警告和异常混合在一起。)

有趣的解决方案。我怀疑(也希望...)我永远不需要它,但Python允许和启用的东西还是挺酷的。 - Josh Smeaton

2

你的用例很有意义,但我仍然认为你最好重构测试脚本,以便可以在外部运行。

你的测试脚本有这样的功能吗?

def test():
    pass

if __name__ == '__main__':
    test()

如果没有,也许你应该将测试转换为调用一个名为test的函数。然后,从你的主测试脚本中,你只需要:
import test1
test1.test()
import test2
test2.test()

提供一个通用接口来运行测试,测试本身使用该接口。在__main__检查中有一大块代码是不好的事情。
很抱歉我没有回答你提出的问题,但我认为这是一种正确的解决方案,而且不会偏离原始测试代码太远。

很遗憾,有些测试脚本会这样做,但其他一些只是将代码作为顶层模块代码。这只是一些奇怪情况的混合。我找到了问题并回答了自己的问题,但还是感谢您的建议。我希望我能够遵循它! :-) - BrenBarn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接