Python 在运行动态编译的代码对象时,如何在回溯中保留源代码行?

Python 在运行动态编译的代码对象时,如何在回溯中保留源代码行?,python,Python,假设我使用字符串和名称创建code对象: >>> a = compile('raise ValueError\n', '<at runtime>', 'exec') >a=compile('raisevalueerror\n','exec') 我希望该字符串中的行出现在回溯中(注意-以下是在空闲状态下运行的): 执行(a) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 行政主任(c) 文件“”,第1行,在 提升值错误>>执行(a) 回溯(最近一次呼叫最后

假设我使用字符串和名称创建
code
对象:

>>> a = compile('raise ValueError\n', '<at runtime>', 'exec')
>a=compile('raisevalueerror\n','exec')
我希望该字符串中的行出现在回溯中(注意-以下是在空闲状态下运行的):

执行(a) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 行政主任(c) 文件“”,第1行,在 提升值错误>>执行(a) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 行政主任(c) 文件“”,第1行,在 数值误差
在不创建临时文件的情况下,如何使
raisevalueerror
行显示在回溯中?

使用内置的未记录的
缓存
成员,这似乎可行:

def better_compile(src, name, mode):
    # there is an example of this being set at
    # https://hg.python.org/cpython/file/2.7/Lib/linecache.py#l104
    from linecache import cache
    cache[name] = (
        len(src), None,
        [line+'\n' for line in src.splitlines()], name
    )
    return compile(src, name, mode)

>>c=better\u compile('raise ValueError\n','

好的,您可以编写自己的异常处理程序来填充数据:

code = """
def f1():
    f2()

def f2():
    1 / 0

f1()
"""

a = compile(code, '<at runtime>', 'exec')

import sys
import traceback

try:
    exec(a)
except:
    etype, exc, tb = sys.exc_info()
    exttb = traceback.extract_tb(tb)

    ## Fill the missing data:
    exttb2 = [(fn, lnnr, funcname,
               (code.splitlines()[lnnr-1] if fn=='<at runtime>'
                else line))
              for fn, lnnr, funcname, line in exttb]

    # Print:
    sys.stderr.write('Traceback (most recent call last):\n')
    for line in traceback.format_list(exttb2):
        sys.stderr.write(line)
    for line in traceback.format_exception_only(etype, exc):
        sys.stderr.write(line)
code=”“”
def f1():
f2()
def f2():
1/0
f1()
"""
a=编译(代码,,'exec')
导入系统
导入回溯
尝试:
行政主任(a)
除:
etype,exc,tb=sys.exc_info()
exttb=回溯。提取\u tb(tb)
##填写缺失的数据:
exttb2=[(fn,lnnr,funcname,
(如果fn='',则代码.splitlines()[lnnr-1]
其他行)
对于fn、lnnr、funcname、exttb中的行]
#打印:
sys.stderr.write('回溯(最近一次调用):\n')
对于traceback.format_列表(exttb2)中的行:
系统标准写入(行)
对于traceback.format_exception_中的行,仅限(etype,exc):
系统标准写入(行)
结果:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<at runtime>", line 8, in <module>
    f1()
  File "<at runtime>", line 3, in f1
    f2()
  File "<at runtime>", line 6, in f2
    1 / 0
ZeroDivisionError: integer division or modulo by zero
回溯(最近一次呼叫最后一次):
文件“”,第2行,在
文件“”,第8行,在
f1()
文件“”,第3行,在f1中
f2()
文件“”,第6行,在f2中
1/0
ZeroDivisionError:整数除法或模零除法

现在,只需将compile&exec包装到一个
smart\u exec
函数中,以便每次调用…

只是为了澄清一点——您希望回溯指向行
a=compile(…)
?不,我希望回溯包括传递给
compile
的字符串的对应行。在我看来,它确实保留了行号…“文件”,第1行,in'。如果在
raise
语句之前添加一些换行符,您将看到该数字增加。我需要行内容以及行号。啊…好的,我现在明白了。对不起:-)这对monkey patch来说是一件非常讨厌的事情,当您使用相同的模块名编译不同的代码段时,这将导致错误的回溯,或者如果您不断生成新名称并缓存无限数量的代码段,将导致内存泄漏。@user2357112:我不是说
better\u compile
始终是
compile
的合适替代品,但有了受控使用(创建负责任的名称),也应该,根据@user2357112的反馈,这似乎是一个上下文管理器的好地方,它可以在上下文退出时删除缓存项。@mgilson:或者可能对
code
对象进行子类化,并在
\uu del_u
@user2357112:谢谢您的警告。使用更好的编译自动生成伪文件名(如
)可以避免重复的名称,如
。lru缓存有限制。对于真实文件名,文件在转储后可以重新读取(但导入后可能已更改)。如果一个回溯在被转储后需要
,我们将回到当前的情况——回溯中缺少代码。因此,您的论点是包装
exec
,而不是
compile
?这也可能是一个问题option@Eric没错,但是,当然,您也必须将原始代码(
code
)传递给包装器,因为编译的
a
没有保存源代码。
>>> c = better_compile('raise ValueError\n', '<a name>', 'exec')
>>> exec(c)
Traceback (most recent call last):
  File "<pyshell#50>", line 1, in <module>
    exec(c)
  File "<a name>", line 1, in <module>
    raise ValueError
ValueError
code = """
def f1():
    f2()

def f2():
    1 / 0

f1()
"""

a = compile(code, '<at runtime>', 'exec')

import sys
import traceback

try:
    exec(a)
except:
    etype, exc, tb = sys.exc_info()
    exttb = traceback.extract_tb(tb)

    ## Fill the missing data:
    exttb2 = [(fn, lnnr, funcname,
               (code.splitlines()[lnnr-1] if fn=='<at runtime>'
                else line))
              for fn, lnnr, funcname, line in exttb]

    # Print:
    sys.stderr.write('Traceback (most recent call last):\n')
    for line in traceback.format_list(exttb2):
        sys.stderr.write(line)
    for line in traceback.format_exception_only(etype, exc):
        sys.stderr.write(line)
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<at runtime>", line 8, in <module>
    f1()
  File "<at runtime>", line 3, in f1
    f2()
  File "<at runtime>", line 6, in f2
    1 / 0
ZeroDivisionError: integer division or modulo by zero