Search code examples
pythoncdebuggingpython-c-api

Where does Python gets its traceback information from?


Consider the following verysimple.py:

if '__main__' == __name__:
    prnt('Line1')

Now, if I execute it with > python verysimple.py, I am, of course, greeted by:

    Traceback (most recent call last):
        File "verysimple.py", line 2, in <module>
          prnt('Line1')
    NameError: name 'prnt' is not defined`

I'd like to know where in the Python is pulling the traceback information from (especially the erroneous command).

I've already tried to find my way inside PyEval_FrameEx, but can't figure it out...


Solution

  • You were on the right track. When parsing the code tree, it just runs through PyEval_FrameEx multiple times. In the end, it will call format_exc_check_arg() to format the error, which for me happens at line 2100 in the ceval.c of the Python version 3.3.2 source. format_exc_check_arg() deduces the offensive 'object' (prnt) and calls PyErr_Format in errors.c to properly format the exception string (the exception type and its corresponding string, NAME_ERROR_MSG, were already passed to format_exc_check_arg() from this line 2100.

    I used simply this code for testing:

    prnt('Line1')
    

    and then run it through a debug build of Python 3.3 I had around.

    The surrounding code in PyEval_FrameEx is

        TARGET(LOAD_NAME)
            w = GETITEM(names, oparg);
            if ((v = f->f_locals) == NULL) {
                PyErr_Format(PyExc_SystemError,
                             "no locals when loading %R", w);
                why = WHY_EXCEPTION;
                break;
            }
            if (PyDict_CheckExact(v)) {
                x = PyDict_GetItem(v, w);
                Py_XINCREF(x);
            }
            else {
                x = PyObject_GetItem(v, w);
                if (x == NULL && PyErr_Occurred()) {
                    if (!PyErr_ExceptionMatches(
                                    PyExc_KeyError))
                        break;
                    PyErr_Clear();
                }
            }
            if (x == NULL) {
                x = PyDict_GetItem(f->f_globals, w);
                Py_XINCREF(x);
                if (x == NULL) {
                    if (PyDict_CheckExact(f->f_builtins)) {
                        x = PyDict_GetItem(f->f_builtins, w);
                        if (x == NULL) {
    // below is the line where the PyExc_NameError will be properly formatted.
                            format_exc_check_arg(
                                        PyExc_NameError,
                                        NAME_ERROR_MSG, w);
                            break;
                        }
                        Py_INCREF(x);
                    }
                    else {
                        x = PyObject_GetItem(f->f_builtins, w);
                        if (x == NULL) {
                            if (PyErr_ExceptionMatches(PyExc_KeyError))
                                format_exc_check_arg(
                                            PyExc_NameError,
                                            NAME_ERROR_MSG, w);
                            break;
                        }
                    }
                }
            }
            PUSH(x);
            DISPATCH();
    

    Note that two lines above it, PyDict_GetItem(...) will be the line trying to find prnt inside the builtin statements & functions (I deduce that from f->builtins, to which w is applied, w itself gotten from the second statement in the above code. Since that dictionary lookup will fail, x == NULL and the NameError is set and formatted.

    Hope this helps you further.