Hoe Python-uitzonderingstekst te krijgen

Ik wil python insluiten in mijn C++-toepassing. Ik gebruik de Boost-bibliotheek – geweldig hulpmiddel. Maar ik heb één probleem.

Als de python-functie een uitzondering genereert, wil ik deze opvangen en een fout afdrukken in mijn toepassing of gedetailleerde informatie krijgen, zoals het regelnummer in het python-script dat een fout veroorzaakte.

Hoe kan ik dat doen? Ik kan geen functies vinden om gedetailleerde uitzonderingsinformatie te krijgen in Python API of Boost.

try {
module=import("MyModule"); //this line will throw excetion if MyModule contains an   error
} catch ( error_already_set const & ) {
//Here i can said that i have error, but i cant determine what caused an error
std::cout << "error!" << std::endl;
}

PyErr_Print() drukt alleen de fouttekst af naar stderr en wist de fout zodat het geen oplossing kan zijn


Antwoord 1, autoriteit 100%

Nou, ik heb ontdekt hoe ik het moet doen.

Zonder boost (alleen foutmelding, omdat code om info uit traceback te halen te zwaar is om hier te posten):

PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
//pvalue contains error message
//ptraceback contains stack snapshot and many other information
//(see python traceback structure)
//Get error message
char *pStrErrorMessage = PyString_AsString(pvalue);

En BOOST-versie

try{
//some code that throws an error
}catch(error_already_set &){
    PyObject *ptype, *pvalue, *ptraceback;
    PyErr_Fetch(&ptype, &pvalue, &ptraceback);
    handle<> hType(ptype);
    object extype(hType);
    handle<> hTraceback(ptraceback);
    object traceback(hTraceback);
    //Extract error message
    string strErrorMessage = extract<string>(pvalue);
    //Extract line number (top entry of call stack)
    // if you want to extract another levels of call stack
    // also process traceback.attr("tb_next") recurently
    long lineno = extract<long> (traceback.attr("tb_lineno"));
    string filename = extract<string>(traceback.attr("tb_frame").attr("f_code").attr("co_filename"));
    string funcname = extract<string>(traceback.attr("tb_frame").attr("f_code").attr("co_name"));
... //cleanup here

Antwoord 2, autoriteit 35%

Dit is de meest robuuste methode die ik tot nu toe heb kunnen bedenken:

   try {
        ...
    }
    catch (bp::error_already_set) {
        if (PyErr_Occurred()) {
            msg = handle_pyerror(); 
        }
        py_exception = true;
        bp::handle_exception();
        PyErr_Clear();
    }
    if (py_exception) 
    ....
// decode a Python exception into a string
std::string handle_pyerror()
{
    using namespace boost::python;
    using namespace boost;
    PyObject *exc,*val,*tb;
    object formatted_list, formatted;
    PyErr_Fetch(&exc,&val,&tb);
    handle<> hexc(exc),hval(allow_null(val)),htb(allow_null(tb)); 
    object traceback(import("traceback"));
    if (!tb) {
        object format_exception_only(traceback.attr("format_exception_only"));
        formatted_list = format_exception_only(hexc,hval);
    } else {
        object format_exception(traceback.attr("format_exception"));
        formatted_list = format_exception(hexc,hval,htb);
    }
    formatted = str("\n").join(formatted_list);
    return extract<std::string>(formatted);
}

Antwoord 3, autoriteit 11%

In de Python C API, PyObject_Strretourneert een nieuwe verwijzing naar een Python-tekenreeksobject met de tekenreeksvorm van het Python-object dat u als argument doorgeeft — net zoals str(o)in Python-code. Merk op dat het exception-object geen “informatie zoals regelnummer” heeft — dat staat in het traceback-object (u kunt PyErr_Fetchom zowel het exception-object als het traceback-object op te halen). Weet niet wat Boost biedt om deze specifieke C API-functies gebruiksvriendelijker te maken, maar in het ergste geval kunt u altijd een beroep doen op deze functies omdat ze worden aangeboden in de C API zelf.


Antwoord 4, autoriteit 5%

Deze thread was erg handig voor mij, maar ik had problemen met de Python C API toen ik probeerde de foutmelding zelf te extraheren zonder traceback. Ik heb genoeg manieren gevonden om dat in Python te doen, maar ik kon geen manier vinden om dit in C++ te doen. Ik kwam uiteindelijk op de volgende versie, die de C API zo min mogelijk gebruikt en in plaats daarvan veel meer vertrouwt op boost python.

PyErr_Print();
using namespace boost::python;
exec("import traceback, sys", mainNamespace_);
auto pyErr = eval("str(sys.last_value)", mainNamespace_);
auto pyStackTrace = eval("'\\n'.join(traceback.format_exception(sys.last_type, sys.last_value, sys.last_traceback))", mainNamespace_);
stackTraceString_ = extract<std::string>(pyStackTrace);
errorSummary_ = extract<std::string>(pyErr);

De reden dat dit werkt is omdat PyErr_Print()stelt ook de waarde in voor sys.last_value, sys.last_typeen sys.last_traceback. Die zijn ingesteld op dezelfde waarden als sys.exc_infozou geven, dus dit is functioneel vergelijkbaar met de volgende python-code:

import traceback
import sys
try:
    raise RuntimeError("This is a test")
except:
    err_type = sys.exc_info()[0]
    value = sys.exc_info()[1]
    tb = sys.exc_info()[2]
    stack_trace = "\n".join(traceback.format_exception(err_type, value, tb))
    error_summary = str(value)
print(stack_trace)
print(error_summary)

Ik hoop dat iemand dit nuttig vindt!


Antwoord 5, autoriteit 2%

Hier is wat code gebaseerd op enkele van de andere antwoorden en opmerkingen, mooi opgemaakt met moderne C++ en opmerkingen. Minimaal getest, maar het lijkt te werken.

#include <string>
#include <boost/python.hpp>
#include <Python.h>
// Return the current Python error and backtrace as a string, or throw
// an exception if there was none.
std::string python_error_string() {
  using namespace boost::python;
  PyObject* ptype = nullptr;
  PyObject* pvalue = nullptr;
  PyObject* ptraceback = nullptr;
  // Fetch the exception information. If there was no error ptype will be set
  // to null. The other two values might set to null anyway.
  PyErr_Fetch(&ptype, &pvalue, &ptraceback);
  if (ptype == nullptr) {
    throw std::runtime_error("A Python error was detected but when we called "
                             "PyErr_Fetch() it returned null indicating that "
                             "there was no error.");
  }
  // Sometimes pvalue is not an instance of ptype. This converts it. It's
  // done lazily for performance reasons.
  PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
  if (ptraceback != nullptr) {
    PyException_SetTraceback(pvalue, ptraceback);
  }
  // Get Boost handles to the Python objects so we get an easier API.
  handle<> htype(ptype);
  handle<> hvalue(allow_null(pvalue));
  handle<> htraceback(allow_null(ptraceback));
  // Import the `traceback` module and use it to format the exception.
  object traceback = import("traceback");
  object format_exception = traceback.attr("format_exception");
  object formatted_list = format_exception(htype, hvalue, htraceback);
  object formatted = str("\n").join(formatted_list);
  return extract<std::string>(formatted);
}

Btw, ik was benieuwd waarom iedereen handle<>gebruikt in plaats van handle. Blijkbaar schakelt het de deductie van sjabloonargumenten uit. Ik weet niet zeker waarom je dat hier zou willen, maar het is toch niet hetzelfde, en de Boost-documenten zeggen dat je ook handle<>moet gebruiken, dus ik denk dat er een goede reden is.

Other episodes