简体   繁体   English

在 Python C API 中运行 PyRun_String() 时如何用代码注释行?

[英]How to annotate lines with code when running PyRun_String() in Python C API?

I'm using PyRun_String() from Python C API to run python code.我正在使用来自 Python C API 的PyRun_String()来运行 Z23EEEB4347BDD26BDDZFC6BEE7 代码。

Passing Py_file_input for start and for globals and locals I pass dictionary created with PyDict_New() , and for string of code str I pass my code. Py_file_input传递给startglobalslocals我传递用PyDict_New()创建的字典,对于字符串str我传递我的代码。

For example I'm having next code:例如我有下一个代码:

def f():
    def g():
        assert False, 'TestExc'
    g()
f()

Of cause this code is expected to throw an exception and show stack.当然,此代码预计会引发异常并显示堆栈。 I print error with PyErr_Print() and get next stack:我用PyErr_Print()打印错误并获得下一个堆栈:

Traceback (most recent call last):
  File "<string>", line 5, in <module>
  File "<string>", line 4, in f
  File "<string>", line 3, in g
AssertionError: TestExc

As one can see this exception stack is missing lines with code, for example if same script is run in pure Python interpreter then it prints next stack:正如人们所看到的,这个异常堆栈缺少代码行,例如,如果在纯 Python 解释器中运行相同的脚本,那么它会打印下一个堆栈:

Traceback (most recent call last):
  File "test.py", line 5, in <module>
    f()
  File "test.py", line 4, in f
    g()
  File "test.py", line 3, in g
    assert False, 'TestExc'
AssertionError: TestExc

So it has code annotation eg assert False, 'TestExc' for last line of stack and f() and g() for previous lines.所以它有代码注释,例如assert False, 'TestExc'和前一行的f()g() Also it has file name (but having file name is not very important).它也有文件名(但有文件名不是很重要)。

Is there any way to have this code shown when using PyRun_String()?使用 PyRun_String() 时有什么方法可以显示此代码? I expect that I need to use another function for example PyRun_StringFlags() which has extra parameters PyCompilerFlags * flags that I can probably use to tell compiler to save code attached to each line when compiling.我希望我需要使用另一个 function 例如PyRun_StringFlags()它有额外的参数PyCompilerFlags * flags我可以用来告诉编译器在编译时保存附加到每一行的代码。 But I don't see anywhere documentation for PyCompilerFlags and don't know what should I pass.但是我在任何地方都没有看到PyCompilerFlags的文档,也不知道我应该通过什么。

Also maybe there are other useful flags for PyCompilerFlags , for example would be nice to have test.py file name instead of <string> inside exception stack, probably this behaviour is also tweakable by some of PyCompilerFlags values?也可能PyCompilerFlags有其他有用的标志,例如在异常堆栈中使用test.py文件名而不是<string>会很好,可能这种行为也可以通过某些PyCompilerFlags值进行调整?

Also I was using exec() from built-ins of Python, passing string with program's code to it.我还使用 Python 内置函数中的exec() ,将带有程序代码的字符串传递给它。 But got same exception stack without code annotation.但是在没有代码注释的情况下得到了相同的异常堆栈。 Seems that if it is some interpreter-wide param whether to save code annotation or not.似乎如果它是一些解释器范围的参数,是否保存代码注释。

I also tried to write special function to get current stack, using standard traceback and sys modules:我还尝试使用标准的tracebacksys模块编写特殊的 function 来获取当前堆栈:

def GetStack():
    import traceback, sys
    frame = sys._getframe()
    extracted = traceback.extract_stack(frame)
    def AllFrames(f):
        while f is not None:
            yield f
            f = f.f_back
    all_frames = list(reversed(list(AllFrames(frame))))
    assert len(extracted) == len(all_frames)
    return [{
        'file': fs.filename, 'first_line': fr.f_code.co_firstlineno,
        'line': fs.lineno, 'func': fs.name, 'code': fs._line,
    } for fs, fr in zip(extracted, all_frames)]

This function returns whole stack correctly, ut inside code fields there are empty strings.此 function 正确返回整个堆栈,但code字段内有空字符串。 Looks like frame objects don't have code annotation inside theirs ._line attribute as they probably should, this might be the reason of having no code annotation inside all the functions used above.看起来frame对象在他们的._line属性中没有代码注释,因为它们可能应该,这可能是在上面使用的所有函数中没有代码注释的原因。

Do you know if there is any way to provide code annotation for all stack retrieving/printing operations?您知道是否有任何方法可以为所有堆栈检索/打印操作提供代码注释? Besides manually composing correct stack trace by hand.除了手动编写正确的堆栈跟踪之外。 Maybe there is at least some standard module that allows to set this lines of code somehow at least manually?也许至少有一些标准模块允许至少手动设置这行代码?

Update .更新 I found out that traceback module uses linecache.getline(self.filename, self.lineno) ( see here ) to get source code.我发现traceback模块使用linecache.getline(self.filename, self.lineno)见这里)来获取源代码。 Does anybody know how can I fill linecache with source text from memory with given filename without using temporary file?有谁知道如何在不使用临时文件的情况下使用给定文件名的 memory 中的源文本填充 linecache?

Also interesting if raised exception uses traceback module to output exception to console or maybe it has its own formatting implementation?如果引发的异常使用traceback模块到 output 异常到控制台或者它有自己的格式化实现,也很有趣?

Answering my own question.回答我自己的问题。 After reading source code of PyRun_String() I found out that it is impossible to annotate code-lines of exception (unless I missed something).在阅读PyRun_String()源代码后,我发现不可能注释异常的代码行(除非我错过了什么)。

Because PyRun_String() sets filename to "<string>" and doesn't allow to give other name, while exception printing code tries to read file from file system, and of course doesn't find this file name.因为 PyRun_String() 将文件名设置为"<string>"并且不允许给出其他名称,而异常打印代码尝试从文件系统读取文件,当然找不到这个文件名。

But I found out how to use Py_CompileString() withPyEval_EvalCode() to achieve line annotation instead of using just PyRun_String().但是我发现了如何使用Py_CompileString()PyEval_EvalCode()来实现行注释,而不仅仅是使用 PyRun_String()。

Basically I create temporary file with the help of tempfile standard module.基本上我在tempfile标准模块的帮助下创建临时文件。 You can create non-temporary file too, doesn't matter.您也可以创建非临时文件,没关系。 Then write source code to this file and provide file name to Py_CompileString().然后将源代码写入此文件并将文件名提供给 Py_CompileString()。 After this, lines are annotated correctly.在此之后,行被正确注释。

Below code is in C++, although you can use it in C too with small tweaks (like using PyObject * instead of auto ).下面的代码在 C++ 中,尽管您也可以在 C 中使用它,只需稍作调整(例如使用PyObject *而不是auto )。

Important .重要 For the sake of simplicity in my code I don't handle errors of all function, also don't do reference counting of objects.为了我的代码简单起见,我不处理所有 function 的错误,也不对对象进行引用计数。 Also finally I don't delete temporary file.最后我也不删除临时文件。 These all things should be done in real programs.这些都应该在真实的程序中完成。 Hence code below can't be used in production directly, without modifications.因此,下面的代码不能直接用于生产,无需修改。

Try it online!在线尝试!

#include <Python.h>

int main() {
    Py_SetProgramName(L"prog.py");
    Py_Initialize();
    
    char const source[] = R"(
def f():
    def g():
        assert False, 'TestExc'
    g()
f()
)";
    
    auto pTempFile = PyImport_ImportModule("tempfile");
    
    auto pDeleteFalse = PyDict_New();
    PyDict_SetItemString(pDeleteFalse, "delete", Py_False);
    
    auto pFile = PyObject_Call(
        PyObject_GetAttrString(pTempFile, "NamedTemporaryFile"),
        PyTuple_Pack(0), pDeleteFalse);
    auto pFileName = PyObject_GetAttrString(pFile, "name");
    
    PyObject_CallMethod(pFile, "write", "y", source);
    PyObject_CallMethod(pFile, "close", nullptr);
    
    auto pCompiled = Py_CompileString(
        source, PyUnicode_AsUTF8(pFileName), Py_file_input);
    auto pGlobal = PyDict_New(), pLocal = PyDict_New();
    auto pEval = PyEval_EvalCode(pCompiled, pGlobal, pLocal);
    PyErr_Print();
    
    Py_FinalizeEx();
}

Output: Output:

Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Temp\tmp_73evamv", line 6, in <module>
    f()
  File "C:\Users\User\AppData\Local\Temp\tmp_73evamv", line 5, in f
    g()
  File "C:\Users\User\AppData\Local\Temp\tmp_73evamv", line 4, in g
    assert False, 'TestExc'
AssertionError: TestExc

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM