Вызов метода класса Python из C++, если задан инициализированный класс как PyObject

У меня есть функция на С++, которая получает инициализированный класс как PyObject. Класс питона:

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

С++ получает это:

Expression(somefunctogetsource)

Из С++, как мне вызвать метод get_source объекта выражения? До сих пор я читал документы python c-api и пробовал такие вещи:

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

И преобразовать результат в строку, но это не работает.


person Methodicle    schedule 22.03.2019    source источник


Ответы (1)


Проще, чем вы это делаете. Вам не нужно ничего извлекать из базового класса напрямую. Просто сделать:

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 принимает объект для вызова метода, строка в стиле C для имени метода и строка формата + varargs для аргументов. Если аргументы не нужны, строка формата может быть NULL.

Полученный PyObject* не очень полезен для кода C++ (в его среде выполнения определены 1-, 2- или 4-байтовые символы, в зависимости от задействованных порядковых номеров, поэтому прямое копирование памяти из него в std::string или std::wstring не сработает), но PyUnicode_AsUTF8AndSize можно использовать для получения версии и длины в кодировке UTF-8, который можно использовать для эффективного построения std::string с эквивалентными данными.

Если важна производительность, вы можете явно указать PyObject*, представляющий "get_source" во время загрузки модуля, например. с глобальным вроде:

PyObject *get_source_name;

который инициализируется в PyMODINIT_FUNC модуля с помощью:

get_source_name = PyUnicode_InternFromString("get_source");

Получив это, вы можете использовать более эффективный PyObject_CallMethodObjArgs с:

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

Экономия здесь в значительной степени заключается в том, чтобы избежать создания уровня Python str из C char* снова и снова, и, используя PyUnicode_InternFromString для построения строки, вы используете интернированную строку, делая поиск более эффективным (поскольку имя get_source само по себе автоматически интернируется при def-ed в интерпретаторе, фактического сравнения содержимого памяти не происходит; он понимает, что обе строки интернированы, и просто проверяет, указывают ли они на одну и ту же память или нет).

person ShadowRanger    schedule 22.03.2019
comment
Ух ты, я действительно пытался усложнить себе задачу. Спасибо, что так хорошо объяснили. Дополнительные комментарии о производительности невероятно полезны, очень признательны! - person Methodicle; 22.03.2019
comment
@Methodicle: рад, что смог помочь. Небольшое примечание: почти уверен, что [item for item in source if item.lstrip()[0] != '#'] это авария, ожидающая своего часа; он умрет с IndexError, если одна из исходных строк пуста (ничего, кроме пробелов). Измените item.lstrip()[0] != '#' на not item.lstrip().startswith('#'), чтобы сделать его безопасным (item.lstrip()[:1] != '#' также будет безопасным, но его намерения менее очевидны). Я бы также рекомендовал изменить ваш raise Exception на raise TypeError, так как все дело в том, что ввод был неправильного типа, и более конкретные исключения более полезны. - person ShadowRanger; 22.03.2019
comment
Вау, спасибо Еще раз, как раз перед тем, как я увидел ваш комментарий. Он действительно умер именно по этой причине. Мне нужно больше практики. Я думал, что хорошо подумал о строках комментариев, но совершенно забыл о пустых строках. - person Methodicle; 22.03.2019