Saya sedang menulis beberapa fungsi level C, itu harus menerima callable, *args dan **kwargs, mencetak sesuatu dan memanggil callable dengan argumen. Tanda tangannya harus mencerminkan yang ini dari Python:

def my_function(callable, *args, **kwargs): ...

Berikut kode C yang saya miliki:

static PyObject *call_c(PyObject *self, PyObject *args, PyObject *keywords) {
    PyObject *f;
    PyObject *arguments;

    if (!PyArg_ParseTuple(args, "O|O", &f, &arguments))
    {
        PyErr_SetString(PyExc_ValueError, "invalid args");
        return NULL;
    }

    printf("Calling...\n");

    return PyObject_Call(f, arguments, keywords);
}

Inilah cara saya mencoba menjalankannya:

def printer(value="test"):
    print(f"printed: {value}")

def call_c_wrapper(callable, *args, **kwargs):
    call_c(callable, args, **kwargs)

call_c(print, "some print value")
# Calling...
# Segmentation fault

call_c(printer, value="test")
# Traceback (most recent call last):
#   File "main.py", line 35, in <module>
#     call_c(printer, value="test")
# TypeError: call_c() takes no keyword arguments

call_c_wrapper(print, "some print value")
# > this is the only one that works:

# Calling...
# some print value

call_c_wrapper(printer, value="my value")
# > same as `call_c` with kwargs:

# Traceback (most recent call last):
#   File "main.py", line 37, in <module>
#     call_c_wrapper(printer, value="my value")
#   File "main.py", line 32, in call_c_wrapper
#     call_c(callable, args, **kwargs)
#  TypeError: call_c() takes no keyword arguments

Apa yang saya lakukan salah dengan kode? Penguraian *args tampaknya agak berfungsi, saya setuju dengan menggunakan fungsi pembungkus jika itu perlu, tetapi mengapa **kwargs tidak berfungsi sama sekali?

Sunting

Saya telah mengubah PyMethodDef dari METH_VARARGS menjadi METH_VARARGS | METH_KEYWORDS, jadi call_c_wrapper() sekarang berfungsi dengan baik.
call_c dengan args masih memberi saya SegmentationFault, dan yang dengan kwargs menaikkan MemoryError.

1
Djent 20 November 2020, 11:54

1 menjawab

Jawaban Terbaik

Seperti yang saya sarankan di komentar untuk Anda yang sekarang dihapus sebelumnya versi pertanyaan ini: Saya pikir PyArg_ParseTuple adalah alat yang salah untuk ini karena tidak benar-benar memiliki mekanisme untuk menangani *args.

Apa yang ingin Anda lakukan adalah sesuatu seperti kode Python berikut:

def f(*args, **kwds):
    f = args[0]
    args = args[1:]

    return f(*args, **kwds)

Cara termudah untuk melakukannya adalah dengan menggunakan PyTuple_... API ( https://docs.python.org/3/c-api/tuple.html).

Sesuatu di sepanjang baris:

static PyObject *call_c(PyObject *self, PyObject *args, PyObject *keywords) {
    PyObject *f;
    PyObject *arguments;

    if (PyTuple_Size(args) == 0)
    {
        PyErr_SetString(PyExc_ValueError, "Missing argument 'f'");
        return NULL;
    }
    f = PyTuple_GET_ITEM(args, 0); // Note: borrowed reference so no reference counting needed
    arguments = PyTuple_GetSlice(args, 1, PyTuple_Size(args));
    // ideally you should check arguments isn't NULL too

    printf("Calling...\n");

    PyObject* result = PyObject_Call(f, arguments, keywords);
    Py_XDECREF(arguments);
    return result;
}
0
DavidW 20 November 2020, 18:00