0

I have a function in c++ that receives a initialised class as a PyObject. The python class is:

class Expression:
    def __init__(self, obj):
        self.obj = obj

    def get_source(self):
        #Check if the object whose source is being obtained is a function.
        if inspect.isfunction(self.obj):
            source = inspect.getsourcelines(self.obj)[0][1:]
            ls = len(source[0]) - len(source[0].lstrip())
            source = [line[ls:] for line in source]
            #get rid of comments from the source
            source = [item for item in source if item.lstrip()[0] != '#']
            source = ''.join(source)
            return source
        else:
            raise Exception("Expression object is not a function.")

The c++ receives this:

Expression(somefunctogetsource)

From c++ how do I call the get_source method of the expression object? So far I've read the python c-api docs and tried things like this:

PyObject* baseClass = (PyObject*)expression->ob_type;
PyObject* func = PyObject_GetAttrString(baseClass, "get_source");
PyObject* result = PyObject_CallFunctionObjArgs(func, expression, NULL);

And convert the result to a string, but this doesn't work.

1 Answer 1

2

Simpler than you're making it. You don't need to retrieve anything from the base class directly. Just do:

PyObject* result = PyObject_CallMethod(expression, "get_source", NULL);
if (result == NULL) {
    // Exception occurred, return your own failure status here
}
// result is a PyObject* (in this case, it should be a PyUnicode_Object)

PyObject_CallMethod takes an object to call a method of, a C-style string for the method name, and a format string + varargs for the arguments. When no arguments are needed, the format string can be NULL.

The resulting PyObject* isn't super useful to C++ code (it has runtime determined 1, 2 or 4 byte characters, depending on the ordinals involved, so straight memory copying from it into std::string or std::wstring won't work), but PyUnicode_AsUTF8AndSize can be used to get a UTF-8 encoded version and length, which can be used to efficiently construct a std::string with equivalent data.

If performance counts, you may want to explicitly make a PyObject* representing "get_source" during module load, e.g. with a global like:

PyObject *get_source_name;

which is initialized in the module's PyMODINIT_FUNC with:

get_source_name = PyUnicode_InternFromString("get_source");

Once you have that, you can use the more efficient PyObject_CallMethodObjArgs with:

PyObject* result = PyObject_CallMethodObjArgs(expression, get_source_name, NULL);

The savings there are largely in avoiding constructing a Python level str from a C char* over and over, and by using PyUnicode_InternFromString to construct the string, you're using the interned string, making the lookup more efficient (since the name of get_source is itself automatically interned when def-ed in the interpreter, no actual memory comparison of the contents takes place; it realizes the two strings are both interned, and just checks if they point to the same memory or not).

Sign up to request clarification or add additional context in comments.

3 Comments

Ah wow, I really tried making that more difficult for myself. Thanks for explaining it so well. The extra comments about performance are incredibly useful, much appreciated!
@Methodicle: Glad I could help. Minor side-note: Pretty sure [item for item in source if item.lstrip()[0] != '#'] is an accident waiting to happen; it'll die with an IndexError if one of the source lines is empty (nothing but whitespace). Change item.lstrip()[0] != '#' to not item.lstrip().startswith('#') to make it safe (item.lstrip()[:1] != '#' would also be safe, but less obvious in intent). I'd also recommend changing your raise Exception to raise TypeError, since the whole point is that the input was of the incorrect type, and more specific exceptions are more helpful.
Wow thanks Again, just before I saw your comment It actually died died because of that exact reason. I must need more practice I thought I'd done well thinking about comment lines but completely forgot empty lines.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.