Is it possible to call a Python module from ObjC? - python

Using PyObjC, is it possible to import a Python module, call a function and get the result as (say) a NSString?
For example, doing the equivalent of the following Python code:
import mymodule
result = mymodule.mymethod()
..in pseudo-ObjC:
PyModule *mypymod = [PyImport module:#"mymodule"];
NSString *result = [[mypymod getattr:"mymethod"] call:#"mymethod"];

As mentioned in Alex Martelli's answer (although the link in the mailing-list message was broken, it should be https://docs.python.org/extending/embedding.html#pure-embedding).. The C way of calling..
print urllib.urlopen("http://google.com").read()
Add the Python.framework to your project (Right click External Frameworks.., Add > Existing Frameworks. The framework in in /System/Library/Frameworks/
Add /System/Library/Frameworks/Python.framework/Headers to your "Header Search Path" (Project > Edit Project Settings)
The following code should work (although it's probably not the best code ever written..)
#include <Python.h>
int main(){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Py_Initialize();
// import urllib
PyObject *mymodule = PyImport_Import(PyString_FromString("urllib"));
// thefunc = urllib.urlopen
PyObject *thefunc = PyObject_GetAttrString(mymodule, "urlopen");
// if callable(thefunc):
if(thefunc && PyCallable_Check(thefunc)){
// theargs = ()
PyObject *theargs = PyTuple_New(1);
// theargs[0] = "http://google.com"
PyTuple_SetItem(theargs, 0, PyString_FromString("http://google.com"));
// f = thefunc.__call__(*theargs)
PyObject *f = PyObject_CallObject(thefunc, theargs);
// read = f.read
PyObject *read = PyObject_GetAttrString(f, "read");
// result = read.__call__()
PyObject *result = PyObject_CallObject(read, NULL);
if(result != NULL){
// print result
printf("Result of call: %s", PyString_AsString(result));
}
}
[pool release];
}
Also this tutorial is good

Not quite, AFAIK, but you can do it "the C way", as suggested for example in http://lists.apple.com/archives/Cocoa-dev/2004/Jan/msg00598.html -- or "the Pyobjc way" as per http://osdir.com/ml/python.pyobjc.devel/2005-06/msg00019.html (see also all other messages on that thread for further clarification).

Related

Unable to receive integer return value when calling Python function from C++

I am calling a Python function from my C++ code. The Python function takes two numbers as input and returns the sum. I am unable to retrieve the sum value in C++ code. Below are code for both C++ and Python files.
C++ code:
#include<iostream>
#include "Python.h"
int main()
{
Py_Initialize();
PyRun_SimpleString("import sys; sys.path.insert(0, '/home/aakash/Desktop/code/testpc')");
//Run a python function
PyObject *pName, *pModule, *pFunc, *pArgs, *pValue;
pName = PyUnicode_FromString((char*)"sample");
pModule = PyImport_Import(pName);
pFunc = PyObject_GetAttrString(pModule, (char*)"main2");
// pArgs = PyTuple_Pack(1, PyUnicode_FromString((char*)"Greg"));
pArgs = PyTuple_New(2);
PyTuple_SetItem(pArgs, 0, PyLong_FromLong(5));
PyTuple_SetItem(pArgs, 1, PyLong_FromLong(6));
pValue = PyObject_CallObject(pFunc, pArgs);
auto result = _PyUnicode_AsString(pValue);
std::cout << result << std::endl;
Py_Finalize();
return 0;
}
Python code in sample.py file:
def main2(a, b):
print("getting sum ")
c = a + b
return c
Getting error:- TypeError: bad argument type for built-in operation.
Request to help for other datatypes too.
I got the solution by replacing auto result = _PyUnicode_AsString(pValue); with int result = PyFloat_AsDouble(pValue);. But I am not fully convinced as I needed an integer, but I had to use a function which looks to be meant for dealing with Float/Double. So, waiting for an accurate answer. Thanks!

Why might a C-based Python extension always return the same value?

The following code seems incredibly simple. an integer is passed to the function in Python, which creates a PyList in C then populates it:
hello.c:
#include <Python.h>
PyObject* getlist(int *len)
{
printf("Passed to C: %d\n", *len);
PyObject *dlist = PyList_New(*len);
double num = 0.1;
for (int i = 0; i < *len; i++)
{
PyList_SetItem(dlist, i, PyFloat_FromDouble(num));
num += 0.1;
}
return dlist;
}
static char helloworld_docs[] =
"Fill docs where possible\n";
static PyMethodDef helloworld_funcs[] = {
{"getlist", (PyCFunction)getlist, METH_VARARGS, helloworld_docs},
{NULL}
};
static struct PyModuleDef Helloworld =
{
PyModuleDef_HEAD_INIT,
"Helloworld", // module name
"NULL", // module documentation
-1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */
helloworld_funcs
};
PyMODINIT_FUNC PyInit_helloworld(void)
{
return PyModule_Create(&Helloworld);
}
setup.py:
from distutils.core import setup
from distutils.extension import Extension
setup(name='helloworld',
version='1.0',
ext_modules=[Extension('helloworld', ['hello.c'])])
usepkg.py:
#!/usr/bin/python
import sys
import helloworld
print("Input to Python:", sys.argv[1])
print (helloworld.getlist(sys.argv[1]))
I build and install using
python3 setup.py build
python3 setup.py install
and I see no errors.
The odd behaviour happens when I test it. For example:
python3 usepkg.py 4
No matter what value I give as an argument, the output is always the same:
Input to Python: 4
Passed to C: 6
[0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6]
The value passed to C is always 6. This is the same whether the input agument is int or Py_ssize_t. What am I missing?
I'm quite surprised there's no warnings here when building, the types of functions shouldn't be their primitive types but of PyObject* -- you'll then parse the types and execute your function
Here's an adjustment to your function:
PyObject* getlist(PyObject* self, PyObject* args)
{
int len;
if (!PyArg_ParseTuple(args, "i", &len)) {
return NULL;
}
printf("Passed to C: %d\n", len);
PyObject *dlist = PyList_New(len);
double num = 0.1;
for (int i = 0; i < len; i++)
{
PyList_SetItem(dlist, i, PyFloat_FromDouble(num));
num += 0.1;
}
return dlist;
}
More information on this can be found in the parsing arguments and building values documentation
The number you were getting was likely the value in PyObject*->ob_refcount of self (the number of references to the C module)
in my case I saw 4 instead of 6, though I'm likely using a different version of python and/or calling approach

Problem of embedding python-opencv in C++ program(OK for single picture, but failed on web camera)

I am surviving to make my C++ program can embed python script so that I can modify the image processing code externally.
I have managed to make it can run on single picture,
but when I try to capture continuous picture and perform image processing, it failed.
Can you kindly help me out?
My environment is :
Windows 10, Python version: 3.8.1 (32bit) and corresponding numpy
Visual Studio 2019 v16.4.3 [vcvarsall.bat] Environment initialized for: 'x86'
Qt creator 5.14(MSVC 2017,32 bit) and its qmake as my IDE
The following are my source codes.
Qt project file: testPyScript.pro
QT -= gui
CONFIG += c++11 console
CONFIG -= app_bundle
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
Python_wrapper.cpp \
main.cpp
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
Python_wrapper.h
# Python
INCLUDEPATH += "C:/Python/Python38-32/include"
LIBS += -L"C:/Python/Python38-32/libs" \
-lpython38 \
-lpython3
#numpy
INCLUDEPATH +="C:/Python/Python38-32/Lib/site-packages/numpy/core/include"
# opencv
INCLUDEPATH += "C:/opencv/include"
CONFIG(debug, debug|release) {
LIBS += -L"C:/opencv/lib/Debug" \
-lopencv_core420d \
-lopencv_highgui420d \
-lopencv_imgcodecs420d \
-lopencv_imgproc420d \
-lopencv_videoio420d
}
CONFIG(release, debug|release) {
LIBS += -L"C:/opencv/lib/Release" \
-lopencv_core420 \
-lopencv_highgui420 \
-lopencv_imgcodecs420 \
-lopencv_imgproc420 \
-lopencv_videoio420
}
Python_wrapper.h
#ifndef PYTHON_WRAPPER_H
#define PYTHON_WRAPPER_H
#pragma push_macro("slots")
#undef slots
#include <Python.h>
#include <numpy/arrayobject.h>
#include <opencv2/core.hpp>
extern PyObject *pyModule,*pyFunc;
bool init_python();
void end_python();
PyObject* convertImage(const cv::Mat& image) ;
std::string type2str(int type) ;
#pragma pop_macro("slots")
#endif // PYTHON_WRAPPER_H
Python_wrapper.cpp
#include"Python_wrapper.h"
#include<fstream>
#include<QDebug>
#include <sys/stat.h>
PyObject *pyModule=nullptr;
PyObject *pyFunc=nullptr;
bool IsPathExist(const std::string &s)
{
struct stat buffer;
return (stat (s.c_str(), &buffer) == 0);
}
bool init_python()
{
if (!Py_IsInitialized())
{
//set python path
std::ifstream infile;
infile.open("PYTHON_PATH",std::ios::in);
if(infile)
{
qDebug()<<"Given python_path file."<<endl;
std::string python_path;
infile>>python_path;
infile.close();
qDebug()<<"Given python path:"<<python_path.c_str()<<endl;
// check path if exists
if(!IsPathExist(python_path))
{
qDebug()<<"Can not find given python path."<<endl;
return false;
}
std::string env = getenv("PATH");
env += ";"+python_path;
putenv(env.c_str());
}
else
{
qDebug()<<"No specify on python path. Default python will be used."<<endl;
}
qDebug()<<"Py_Initialize..."<<endl;
Py_Initialize();
if(Py_IsInitialized())
qDebug()<<"Py_Initialize. OK."<<endl;
else
{
qDebug()<<"Failed to initialize Python."<<endl;
return false;
}
qDebug()<<"Python version:"<<Py_GetVersion()<<endl;
//add current folder to module serach parth
QString modulePath=QString::fromWCharArray(Py_GetPath());
qDebug()<<"Module search path:"<<modulePath<<endl;
//import modoule
qDebug()<<"Import python module <py_cv>..."<<endl;
pyModule = PyImport_ImportModule("py_cv");
if (pyModule == nullptr)
{
qDebug()<<"Failed to load python module <py_cv>"<<endl;
PyErr_Print();
return false;
}
//import module function
qDebug()<<"Import python function <test>"<<endl;
pyFunc =PyObject_GetAttrString(pyModule,"test");
if (pyFunc == NULL)
{
qDebug()<<"Failed to load python function <test>"<<endl;
PyErr_Print();
return false;
}
}
import_array();
}
void end_python()
{
if(Py_IsInitialized())
Py_Finalize();
}
PyObject* convertImage(const cv::Mat& image) {
//2D image with 3 channels.
npy_intp dimensions[3] = {image.rows, image.cols, image.channels()};
//image.dims = 2 for a 2D image, so add another dimension for channels.
return PyArray_SimpleNewFromData(image.dims + 1, (npy_intp*)&dimensions, NPY_UINT8, image.data);
}
std::string type2str(int type) {
std::string r;
uchar depth = type & CV_MAT_DEPTH_MASK;
uchar chans = 1 + (type >> CV_CN_SHIFT);
switch ( depth ) {
case CV_8U: r = "8U"; break;
case CV_8S: r = "8S"; break;
case CV_16U: r = "16U"; break;
case CV_16S: r = "16S"; break;
case CV_32S: r = "32S"; break;
case CV_32F: r = "32F"; break;
case CV_64F: r = "64F"; break;
default: r = "User"; break;
}
r += "C";
r += (chans+'0');
return r;
}
main.cpp
#include "Python_wrapper.h"
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include<iostream>
int py_image_process(cv::Mat &img)
{
int ierr=-1;
std::cout<<"MatToNDArray"<<std::endl;
PyObject *pyMat =convertImage(img);
std::cout<<"Image type:"<<type2str(img.type()).c_str()<<std::endl;
double d=100.0;
PyObject *pyArgs = PyTuple_New(2);
PyObject* pyD=PyFloat_FromDouble(d);
PyTuple_SetItem(pyArgs,0, pyMat);
PyTuple_SetItem(pyArgs,1,pyD);
PyObject *pyValue= PyObject_CallObject(pyFunc, pyArgs);
if (pyValue != NULL)
{
std::cout<<"Function performed OK"<<std::endl;
if (PyTuple_Check(pyValue))
{
std::cout<<"Check PyValue as Tuple OK"<<std::endl;
ierr = PyLong_AsLong(PyTuple_GetItem(pyValue, 0));
PyObject* bytes = PyTuple_GetItem(pyValue, 1);
std::string msg = PyUnicode_AsUTF8(bytes);
std::cout<<"msg:"<<msg.c_str()<<std::endl;
}
Py_DECREF(pyValue);
}
else
std::cout<<"Failed to perform function"<<std::endl;
Py_XDECREF(pyArgs);
return ierr;
}
int main()
{
int ierr=-1;
std::cout<<"Test embeded python"<<std::endl;
if(!init_python())
{
end_python();
return 2;
}
cv::VideoCapture cap =cv::VideoCapture(0);
// cv::Mat img =cv::imread("0.jpg",cv::IMREAD_COLOR);
cv::Mat img;
for(;;)
{
cap.read(img);
if(!img.empty())
{
// ierr= py_image_process(img);
cv::imshow("image",img);
}
else
break;
if(cv::waitKey(5)>=0) break;
}
cv::destroyAllWindows();
return ierr;
}
and python test script: py_cv.py
import cv2
import numpy as np
def test(img,d):
print(type(img),type(d))
rows,cols,chs=img.shape
cx,cy=int(rows/2),int(cols/2)
d=int(d/2.0)
cv2.circle(img,(cx,cy),d,(0,255,0),2)
return -99,"test message"
Your help is highly appreciated.
Take a look at the examle given in the OpenCv documentation for VideoCapture.
//--- INITIALIZE VIDEOCAPTURE
VideoCapture cap;
// open the default camera using default API
// cap.open(0);
// OR advance usage: select any API backend
int deviceID = 0; // 0 = open default camera
int apiID = cv::CAP_ANY; // 0 = autodetect default API
// open selected camera using selected API
cap.open(deviceID + apiID);
// check if we succeeded
if (!cap.isOpened()) {
cerr << "ERROR! Unable to open camera\n";
return -1;
}
The code above is a small snippit from it.
It seems as if you don't open the VideoCapture and you also don't check if it is initialized correctly by using isOpened() in your main() in main.cpp.
Finally, I checked my program and found my above code can work normally. But when I use move it in Qt thread( C++), some errors occurred. It seems that embedding python is difficult in a multi-thread application .

Python str to C++ to Python str

I am struggling with converting from Python str to C++ and back. For Python 2/3 compatibility, I thought using str/bytes for Py2/3, respectively, would suffice (the defines).
Note this is extracted from a larger codebase; apologies for any missing imports.
// C++ stuff compiled to convertor.so
#include "Python.h"
#if PY_MAJOR_VERSION >= 3
#define PyString_Size PyBytes_Size
#define PyString_AsString PyBytes_AsString
#define PyString_FromStringAndSize PyBytes_FromStringAndSize
#endif
template<typename T>
struct vec {
T *ptr;
i64 size;
};
extern "C"
vec<uint8_t> str_to_char_arr(PyObject* in) {
int64_t dimension = (int64_t) PyString_Size(in);
vec<uint8_t> t;
t.size = dimension;
t.ptr = (uint8_t*) PyString_AsString(in);
return t;
}
extern "C"
PyObject* char_arr_to_str(vec<uint8_t> inp) {
Py_Initialize();
PyObject* buffer = PyString_FromStringAndSize((const char*) inp.ptr, inp.size);
return buffer;
}
# Python stuff
class Vec(Structure):
_fields_ = [
("ptr", POINTER(c_wchar_p)),
("size", c_long),
]
lib = to_shared_lib('convertor')
lib_file = pkg_resources.resource_filename(__name__, lib)
utils = ctypes.PyDLL(lib_file)
str_to_char_arr = utils.str_to_char_arr
str_to_char_arr.restype = Vec()
str_to_char_arr.argtypes = [py_object]
encoded = str_to_char_arr('abc'.encode('utf-8'))
char_arr_to_str = utils.char_arr_to_str
char_arr_to_str.restype = py_object
char_arr_to_str.argtypes = [py_object.ctype_class]
result = ctypes.cast(encoded, ctypes.POINTER(Vec())).contents
decoded = char_arr_to_str(result).decode('utf-8')
Trying this with 'abc' on python 3.5 seems to yield '\x03\x00\x00' which clearly means something went wrong.
Can anyone spot the issue?
It might be that you expect UCS2 and the Python is configured for UCS4. See also Building an UCS4 string buffer in python 2.7 ctypes
Haven't managed to make this work for Python 2; perhaps someone understands the unicode/str/bytes differences better between the Python versions to fix this. Also this means the issue I have is probably with another package which unfortunately I have no control of atm.
Nevertheless, here is some working code (for me) with Python 3.5 and clang 6.0.
#include "Python.h"
#if PY_MAJOR_VERSION >= 3
#define PyString_Size PyBytes_Size
#define PyString_AsString PyBytes_AsString
#define PyString_FromStringAndSize PyBytes_FromStringAndSize
#endif
template<typename T>
struct vec {
T *ptr;
int64_t size;
};
extern "C"
vec<uint8_t> str_to_char_arr(PyObject* in) {
int64_t dimension = (int64_t) PyString_Size(in);
vec<uint8_t> t;
t.size = dimension;
t.ptr = (uint8_t*) PyString_AsString(in);
return t;
}
extern "C"
PyObject* char_arr_to_str(vec<uint8_t> inp) {
Py_Initialize();
PyObject* buffer = PyString_FromStringAndSize((const char*) inp.ptr, inp.size);
return buffer;
}
# Python
from ctypes import *
import pkg_resources
class Vec(Structure):
_fields_ = [
("ptr", POINTER(c_char_p)),
("size", c_long),
]
lib = 'test.so'
lib_file = pkg_resources.resource_filename(__name__, lib)
utils = PyDLL(lib_file)
str_to_char_arr = utils.str_to_char_arr
str_to_char_arr.restype = Vec
str_to_char_arr.argtypes = [py_object]
encoded = str_to_char_arr('Bürgermeister'.encode('utf-8'))
char_arr_to_str = utils.char_arr_to_str
char_arr_to_str.restype = py_object
char_arr_to_str.argtypes = [Vec]
decoded = char_arr_to_str(encoded).decode('utf-8')
print(decoded) # Bürgermeister
Changing c_char_p to c_wchar_p seems to have no effect(?). Still works.

Accessing a Python traceback from the C API

I'm having some trouble figuring out the proper way to walk a Python traceback using the C API. I'm writing an application that embeds the Python interpreter. I want to be able to execute arbitrary Python code, and if it raises an exception, to translate it to my own application-specific C++ exception. For now, it is sufficient to extract just the file name and line number where the Python exception was raised. This is what I have so far:
PyObject* pyresult = PyObject_CallObject(someCallablePythonObject, someArgs);
if (!pyresult)
{
PyObject* excType, *excValue, *excTraceback;
PyErr_Fetch(&excType, &excValue, &excTraceback);
PyErr_NormalizeException(&excType, &excValue, &excTraceback);
PyTracebackObject* traceback = (PyTracebackObject*)traceback;
// Advance to the last frame (python puts the most-recent call at the end)
while (traceback->tb_next != NULL)
traceback = traceback->tb_next;
// At this point I have access to the line number via traceback->tb_lineno,
// but where do I get the file name from?
// ...
}
Digging around in the Python source code, I see they access both the filename and module name of the current frame via the _frame structure, which looks like it is a privately-defined struct. My next idea was to programmatically load the Python 'traceback' module and call its functions with the C API. Is this sane? Is there a better way to access a Python traceback from C?
This is an old question but for future reference, you can get the current stack frame from the thread state object and then just walk the frames backward. A traceback object isn't necessary unless you want to preserve the state for the future.
For example:
PyThreadState *tstate = PyThreadState_GET();
if (NULL != tstate && NULL != tstate->frame) {
PyFrameObject *frame = tstate->frame;
printf("Python stack trace:\n");
while (NULL != frame) {
// int line = frame->f_lineno;
/*
frame->f_lineno will not always return the correct line number
you need to call PyCode_Addr2Line().
*/
int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti);
const char *filename = PyString_AsString(frame->f_code->co_filename);
const char *funcname = PyString_AsString(frame->f_code->co_name);
printf(" %s(%d): %s\n", filename, line, funcname);
frame = frame->f_back;
}
}
I prefer calling into python from C:
err = PyErr_Occurred();
if (err != NULL) {
PyObject *ptype, *pvalue, *ptraceback;
PyObject *pystr, *module_name, *pyth_module, *pyth_func;
char *str;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
pystr = PyObject_Str(pvalue);
str = PyString_AsString(pystr);
error_description = strdup(str);
/* See if we can get a full traceback */
module_name = PyString_FromString("traceback");
pyth_module = PyImport_Import(module_name);
Py_DECREF(module_name);
if (pyth_module == NULL) {
full_backtrace = NULL;
return;
}
pyth_func = PyObject_GetAttrString(pyth_module, "format_exception");
if (pyth_func && PyCallable_Check(pyth_func)) {
PyObject *pyth_val;
pyth_val = PyObject_CallFunctionObjArgs(pyth_func, ptype, pvalue, ptraceback, NULL);
pystr = PyObject_Str(pyth_val);
str = PyString_AsString(pystr);
full_backtrace = strdup(str);
Py_DECREF(pyth_val);
}
}
I've discovered that _frame is actually defined in the frameobject.h header included with Python. Armed with this plus looking at traceback.c in the Python C implementation, we have:
#include <Python.h>
#include <frameobject.h>
PyTracebackObject* traceback = get_the_traceback();
int line = traceback->tb_lineno;
const char* filename = PyString_AsString(traceback->tb_frame->f_code->co_filename);
But this still seems really dirty to me.
One principal I've found useful in writing C extensions is to use each language where it's best suited. So if you have a task to do that would be best implemented in Python, implement in Python, and if it would be best implemented in C, do it in C. Interpreting tracebacks is best done in Python for two reasons: first, because Python has the tools to do it, and second, because it isn't speed-critical.
I would write a Python function to extract the info you need from the traceback, then call it from C.
You could even go so far as to write a Python wrapper for your callable execution. Instead of invoking someCallablePythonObject, pass it as an argument to your Python function:
def invokeSomeCallablePythonObject(obj, args):
try:
result = obj(*args)
ok = True
except:
# Do some mumbo-jumbo with the traceback, etc.
result = myTraceBackMunger(...)
ok = False
return ok, result
Then in your C code, call this Python function to do the work. The key here is to decide pragmatically which side of the C-Python split to put your code.
I used the following code to extract Python exception's error body. strExcType stores the exception type and strExcValue stores the exception body. Sample values are:
strExcType:"<class 'ImportError'>"
strExcValue:"ImportError("No module named 'nonexistingmodule'",)"
Cpp code:
if(PyErr_Occurred() != NULL) {
PyObject *pyExcType;
PyObject *pyExcValue;
PyObject *pyExcTraceback;
PyErr_Fetch(&pyExcType, &pyExcValue, &pyExcTraceback);
PyErr_NormalizeException(&pyExcType, &pyExcValue, &pyExcTraceback);
PyObject* str_exc_type = PyObject_Repr(pyExcType);
PyObject* pyStr = PyUnicode_AsEncodedString(str_exc_type, "utf-8", "Error ~");
const char *strExcType = PyBytes_AS_STRING(pyStr);
PyObject* str_exc_value = PyObject_Repr(pyExcValue);
PyObject* pyExcValueStr = PyUnicode_AsEncodedString(str_exc_value, "utf-8", "Error ~");
const char *strExcValue = PyBytes_AS_STRING(pyExcValueStr);
// When using PyErr_Restore() there is no need to use Py_XDECREF for these 3 pointers
//PyErr_Restore(pyExcType, pyExcValue, pyExcTraceback);
Py_XDECREF(pyExcType);
Py_XDECREF(pyExcValue);
Py_XDECREF(pyExcTraceback);
Py_XDECREF(str_exc_type);
Py_XDECREF(pyStr);
Py_XDECREF(str_exc_value);
Py_XDECREF(pyExcValueStr);
}
I had reason to do this recently while writing an allocation tracker for numpy. The previous answers are close but frame->f_lineno will not always return the correct line number--you need to call PyFrame_GetLineNumber(). Here's an updated code snippet:
#include "frameobject.h"
...
PyFrameObject* frame = PyEval_GetFrame();
int lineno = PyFrame_GetLineNumber(frame);
PyObject *filename = frame->f_code->co_filename;
The full thread state is also available in the PyFrameObject; if you want to walk the stack keep iterating on f_back until it's NULL. Checkout the full data structure in frameobject.h: http://svn.python.org/projects/python/trunk/Include/frameobject.h
See also: https://docs.python.org/2/c-api/reflection.html
You can access Python traceback similar to tb_printinternal function. It iterates over PyTracebackObject list. I have tried also suggestions above to iterate over frames, but it does not work for me (I see only the last stack frame).
Excerpts from CPython code:
static int
tb_displayline(PyObject *f, PyObject *filename, int lineno, PyObject *name)
{
int err;
PyObject *line;
if (filename == NULL || name == NULL)
return -1;
line = PyUnicode_FromFormat(" File \"%U\", line %d, in %U\n",
filename, lineno, name);
if (line == NULL)
return -1;
err = PyFile_WriteObject(line, f, Py_PRINT_RAW);
Py_DECREF(line);
if (err != 0)
return err;
/* ignore errors since we are not able to report them, are we? */
if (_Py_DisplaySourceLine(f, filename, lineno, 4))
PyErr_Clear();
return err;
}
static int
tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit)
{
int err = 0;
long depth = 0;
PyTracebackObject *tb1 = tb;
while (tb1 != NULL) {
depth++;
tb1 = tb1->tb_next;
}
while (tb != NULL && err == 0) {
if (depth <= limit) {
err = tb_displayline(f,
tb->tb_frame->f_code->co_filename,
tb->tb_lineno,
tb->tb_frame->f_code->co_name);
}
depth--;
tb = tb->tb_next;
if (err == 0)
err = PyErr_CheckSignals();
}
return err;
}
As of python 3.11, accessing the frame objects seems to need a different approach. Anyway, this works in 3.11, hth someone
py_err(void)
{
PyObject *err = PyErr_Occurred();
if (! err) {
return;
}
PyObject *ptype, *pvalue, *pbacktrace, *pyobj_str;
PyObject *ret, *list, *string;
PyObject *mod;
char *py_str;
PyErr_Fetch(&ptype, &pvalue, &pbacktrace);
PyErr_NormalizeException(&ptype, &pvalue, &pbacktrace);
PyErr_Display(ptype, pvalue, pbacktrace);
PyTraceBack_Print(pbacktrace, pvalue);
pyobj_str = PyObject_Str(pvalue);
py_str = py_obj_to_string(pyobj_str);
printf("%s", py_str);
myfree(py_str);
mod = PyImport_ImportModule("traceback");
list = PyObject_CallMethod(mod, "format_exception", "OOO", ptype, pvalue, pbacktrace);
if (list) {
string = PyUnicode_FromString("\n");
ret = PyUnicode_Join(string, list);
Py_DECREF(list);
Py_DECREF(string);
py_str = py_obj_to_string(ret);
printf("%s", py_str);
myfree(py_str);
Py_DECREF(ret);
}
PyErr_Clear();
}
and you will probably need this too
char *py_obj_to_string(const PyObject *py_str)
{
PyObject *py_encstr;
char *outstr = nullptr;
char *str;
py_encstr = nullptr;
str = nullptr;
if (! PyUnicode_Check((PyObject *) py_str)) {
goto err_out;
}
py_encstr = PyUnicode_AsEncodedString((PyObject *) py_str, "utf-8", nullptr);
if (! py_encstr) {
goto err_out;
}
str = PyBytes_AS_STRING(py_encstr);
if (! str) {
goto err_out;
}
outstr = strdup(str);
err_out:
if (py_encstr) {
Py_XDECREF(py_encstr);
}
return outstr;
}
actual working code if someone needs it can be found in my larger project https://github.com/goblinhack/zorbash

Categories

Resources