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).