Python — Документация для PyCFunction_New / PyCFunction_NewEx

Я изо всех сил пытаюсь понять некоторые PyCXX код (оболочка C ++ Python), который вращается вокруг PyCFunction_New.

Может кто-нибудь объяснить, как работает эта функция?

(Я не могу понять это из CPython исходный код.)


Здесь я подробно опишу проблему, с которой я столкнулся. Я управлял строкой выше, потому что это, вероятно, не будет такого общего использования.

Причина для того, чтобы спросить, что я имею дело со странным кодом. У меня есть функция обработчика метода ключевого слова:

    static PyObject* keyword_handler( PyObject* _self_and_name_tuple,
PyObject* _args,
PyObject* _keywords ) { }

Это хранится как:

PyMethodDef meth_def_ext;
meth_def_ext.ml_meth = reinterpret_cast<PyCFunction>( _handler );
meth_def.ml_flags = METH_VARARGS | METH_KEYWORDS;

Затем он включается в PyCFunction_New:

        MethodDefExt<T>* method_def_ext = ...;

Tuple args{2}; // Tuple wraps a CPython Tuple
args[0] = Object{ this };
args[1] = Object{ PyCapsule_New( (void*)method_def_ext, nullptr, nullptr ), true };

PyObject* func = PyCFunction_New( & method_def_ext->meth_def, args.ptr() );

return Object(func, true);
}

Прав ли я, если предположить, что CPython позаботится о том, чтобы привести его обратно к функции с тремя параметрами, где первым параметром является args (что соответствует первому параметру обработчика _self_and_name_tuple)?

И CPython будет знать только из того факта, что ему нужно проанализировать: ‘myFunc (7, a = 1)’, что он на самом деле имеет дело с функцией ключевых слов a.k.a. 3-param?

Это не выглядит правильно.

Может быть, CPython набирает аргументы1 вернуться к PyMethodDef, а затем проверить его .ml_flags

Если это происходит, то мне нужно знать, потому что код, с которым я работаю, просто имеет:

template<class T>
class MethodDefExt //: public PyMethodDef <-- I commented this out
{
// ... Constructors ...

PyMethodDef               meth_def;

method_noargs_function_t  ext_noargs_function  = nullptr;
method_varargs_function_t ext_varargs_function = nullptr;
method_keyword_function_t ext_keyword_function = nullptr;

Object                    py_method;
};

В своем первоначальном виде, я думаю, он имел две копии PyMethodDef.
И первый никогда не трогали, потому что это был базовый класс

Если это действительно происходит, т. Е. Если этот класс действительно возвращает тип обратно в PyMethodDef внутренними компонентами PyCFunction_New, то это хитроумно.

Конечно, кто-то может добавить переменную-член в начале MethodDefExt, и тогда приведение типов прервется. Это хрупкий …


Класс, с которым я имею дело, позволяет будущему кодеру C ++ реализовывать пользовательский тип Python, а в этом типе — реализовывать методы, которые можно вызывать из Python.

Таким образом, они выводят MyExt: CustomExt и напишите метод:

// one of these three
MyExt::foo(){...}
MyExt::foo(PyObject* args){...}
MyExt::foo(PyObject* args, PyObject* kw){...}

Теперь они должны хранить этот метод в уважать, вызывая соответствующую одну из этих трех функций:

    typedef Object (T::*method_noargs_function_t)();
static void add_noargs_method( const char* name,
method_noargs_function_t function ) {
lookup()[std::string{name}] = new MethodDefExt<T>
{name,function,noargs_handler,doc};
}

typedef Object (T::*method_varargs_function_t)( const Tuple& args );
static void add_varargs_method( const char* name,
method_varargs_function_t function ) {
lookup()[std::string{name}] = new MethodDefExt<T>
{name,function,varargs_handler,doc};
}

typedef Object (T::*method_keyword_function_t)( const Tuple& args, const Dict& kws );
static void add_keyword_method( const char* name,
method_keyword_function_t function ) {
lookup()[std::string{name}] = new MethodDefExt<T>
{name,function,keyword_handler,doc};
}

Обратите внимание, что для каждого есть связанная с ним функция-обработчик. Эти функции-обработчики являются статическими CustomExt — потому что указатель на статический метод может быть вызван из CPython, то есть это просто стандартный указатель на функцию в стиле C.

Поэтому, когда Python хочет указатель для этого Foo Функция, которую мы перехватываем здесь:

    // turn a name into function object
virtual Object getattr_methods( const char* _name )
{
std::string name{ _name };

// see if name exists and get entry with method
auto i = lookup().find( name );

DBG_LINE( "packaging relevant C++ method and extension object instance into PyCFunction" );

// assume name was found in the method map
MethodDefExt<T>* method_def_ext = i->second;

// this must be the _self_and_name_tuple that gets received
//   as the first parameter by the handler
Tuple args{2};

args[0] = Object{ this };
args[1] = Object{ PyCapsule_New( (void*)method_def_ext, nullptr, nullptr ), true };

Создайте функцию Python, которая будет вызывать обработчик для этого метода
(при передаче этого объекта args [0] подробности самого метода args1).
Обработчик позаботится о запуске метода во время перехвата ошибок.

Обратите внимание, что в этот момент мы не выполняем обработчик
Вместо этого мы возвращаем эту функцию Python обратно во время выполнения Python
Возможно, кодировщик Python не хотел, чтобы функция выполнялась, а просто хотел получить указатель на нее:
fp = MyExt.func;

        PyObject* func = PyCFunction_New( & method_def_ext->meth_def, args.ptr() );

X (см. Ниже) & method_def_ext-> meth_def извлекает функцию-обработчик, которая является одним из трех обработчиков
Однако, благодаря конструкторам MethodDefExt, все они были типизированы в объекты PyCFunction
Это означает, что список параметров не подходит для обработчика ключевых слов.

        return Object(func, true);
}

(Мне пришлось вычеркнуть комментарии, поскольку средство форматирования SO не обрабатывало их как комментарии к коду)

С чем я борюсь, так это: скажем, Foo это функция, которая принимает ключевые слова, поэтому ее подпись будет:

MyExt::foo(PyObject* args, PyObject* kw)

Соответствующий обработчик выглядит так:

    static PyObject* noargs_handler( PyObject* _self_and_name_tuple,
PyObject*  ) { }

static PyObject* varargs_handler( PyObject* _self_and_name_tuple,
PyObject* _args ) { }

static PyObject* keyword_handler( PyObject* _self_and_name_tuple,
PyObject* _args,
PyObject* _keywords ) { }

то есть третий. Я прочитал, что Python поставляет дополнительные сначала _self_and_name_tuple параметр.

Когда мы регистрируем foo в поиске, мы предоставляем этот обработчик:

    typedef                               Object (T::*method_keyword_function_t)( const Tuple& args, const Dict& kws );
static void add_keyword_method( const char* name, method_keyword_function_t function ) {
methods()[std::string{name}] = new MethodDefExt<T> {name,function,keyword_handler,doc};
}

И, глядя на конкретного конструктора MethodDefExt,

    // VARARGS + KEYWORD
MethodDefExt (
const char* _name,
method_keyword_function_t _function,
method_keyword_call_handler_t _handler
)
{
meth_def.ml_name = const_cast<char *>( _name );
meth_def.ml_doc  = nullptr;
meth_def.ml_meth = reinterpret_cast<PyCFunction>( _handler );
meth_def.ml_flags = METH_VARARGS | METH_KEYWORDS;

ext_noargs_function = nullptr;
ext_varargs_function = nullptr;
ext_keyword_function = _function;
}

… Видно, что он обрабатывает этот обработчик в PyCFunction

Но PyCFunction принимает только два аргумента !!!

typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);

Мы вписываем обработчики в это. И эти обработчики имеют 2 или 3 параметра.

Это выглядит действительно неправильно.

И затем, возвращаясь назад, когда CPython хочет выполнить foo, как описано выше, он получит meth_def.ml_meth и кормить его в PyCFunction_New:

        Tuple args{2};

args[0] = Object{ this };
args[1] = Object{ PyCapsule_New( (void*)method_def_ext, nullptr, nullptr ), true };

PyObject* func = PyCFunction_New( & method_def_ext->meth_def, args.ptr() ); // https://github.com/python/cpython/blob/master/Objects/methodobject.c#L19-L48

Так что я могу сделать предположение:
* первый параметр PyCFunction_New должен быть указателем на функцию PyCFunction
* второй параметр должен быть PyObject * _self_and_name_tuple

И мы возвращаем это обратно в CPython
Я предполагаю, что когда CPython хочет использовать ‘foo (7, a = 1, b = 2)’, он упаковывает 7 в args, a = 1, b = 2 в kwds и вызывает:

[the PyCFunction function pointer](_self_and_name_tuple, args, kwds)

0

Решение

Я рискну ответить:

PyObject* PyCFunction_New(PyMethodDef* ml, PyObject* data)

PyCFunction_New, вероятно, создает PyObject Callable-типа, заполненный функцией (в мл) и дополнительными данными (в самом себе)

Второй параметр может быть любым, на самом деле он даже не должен быть PyObject *. Когда Python выполняет функцию, упакованную в ml, это будет первый аргумент. Последующие аргументы зависят от ml-> ml_flags, как подробно описано ниже.

Первый параметр — это объект PyMethodDef, который мы можем использовать для инкапсуляции функции.

struct PyMethodDef {
const char  *ml_name;   /* The name of the built-in function/method */
PyCFunction ml_meth;    /* The C function that implements it */
int         ml_flags;   /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
const char  *ml_doc;    /* The __doc__ attribute, or NULL */
};
typedef struct PyMethodDef PyMethodDef;

Итак, он содержит (специфический) указатель на функцию:

typedef PyObject *(*PyCFunction)(PyObject*, PyObject*);

… и флаг,

/* Flag passed to newmethodobject */
/* #define METH_OLDARGS  0x0000   -- unsupported now */
#define METH_VARARGS  0x0001
#define METH_KEYWORDS 0x0002
/* METH_NOARGS and METH_O must not be combined with the flags above. */
#define METH_NOARGS   0x0004
#define METH_O        0x0008

https://docs.python.org/3.4/c-api/structures.html

Мы можем передать 3 вида функций в Python следующим образом:

PyObject*foo( PyObject* data )                                 // ml_meth=METH_NOARGS
PyObject*foo( PyObject* data, PyObject* args )                 // ml_meth=METH_VARARGS
PyObject*foo( PyObject* data, PyObject* args, PyObject* kwds ) // ml_meth=METH_KEYWORDS

РЕДАКТИРОВАТЬ: https://docs.python.org/3/tutorial/classes.html#method-objects

Если вы все еще не понимаете, как работают методы, посмотрите на
реализация может, возможно, прояснить вопросы. Когда атрибут экземпляра
ссылается на то, что это не атрибут данных, его класс ищется. Если
имя обозначает действительный атрибут класса, который является функциональным объектом,
Объект метода создается упаковкой (указателями на) объекта экземпляра.
и объект функции, только что найденный вместе в абстрактном объекте:
это метод объекта. Когда объект метода вызывается с
список аргументов, новый список аргументов строится из экземпляра
объект и список аргументов, а объект функции вызывается с
этот новый список аргументов.

2

Другие решения