Мое первое предложение - использовать только аргументы ключевого слова. Главное преимущество этого заключается в том, чтобы избежать необходимости пропускать значения закладок None
, так как вам никогда не нужно «заполнять» (скажем) неопределенный третий позиционный аргумент, чтобы вы могли указать 4-й. Это в основном изменение интерфейса Python, чтобы «совместить то, что вы имеете в виду» немного больше.
static PyObject* int_from_kw(PyObject* self, PyObject* args, PyObject* kwargs) {
char *a, *b;
Py_ssize_t c = 0; // default value
char* kwarg_names[] = {"a","b","c",NULL};
// optional check to ensure c is passed only as a keyword argument - not needed with Python 3
if (PyTuple_Size(args)>2) {
PyErr_SetString(PyExc_TypeError,"Only two positional arguments allowed");
return NULL;
}
if (!PyArg_ParseTupleAndKeywords(args,kwargs,"ss|i",kwarg_names,&a,&b,&c)) {
return NULL;
}
printf("c_int is %li\n", c);
return PyLong_FromSsize_t(c);
}
(В Python 3 вы можете удалить проверку длины и использовать "ss|$i"
, чтобы указать, что аргументы после $
являются ключевым словом только, что немного лучше). Вам нужно указать тип функции как METH_VARARGS|METH_KEYWORDS
.
Вы можете вызвать из Python, как
int_from_kw("something","something else") # default c
int_from_kw("something","something else",c=5)
int_from_kw(a="something",b="something else",c=5) # etc
но не
int_from_kw("something","something else",c="not an int")
int_from_kw("something","something else",5)
Недостатком является то, что такой подход не всегда работает - иногда нужна функция, чтобы соответствовать неподвижному интерфейс, который обеспечивает сторонняя библиотека.
Мое второе предложение с использованием функции преобразователя. Это не устраняет любую плиту котла, но сохраняет все в одном хорошо сохраненном и повторно используемом месте. Версия здесь для Python 3 (потому что это то, что я установил!), но я думаю, что основное изменение Python 2 должно заменить PyLong
на PyInt
.
int int_or_none(PyObject* o, void* i) {
Py_ssize_t tmp;
Py_ssize_t* i2 = i;
if (o==Py_None) {
return 1; // happy - leave integer as the default
}
if (PyLong_Check(o)) {
tmp = PyLong_AsSize_t(o);
if (PyErr_Occurred()) {
return 0;
} else {
*i2 = tmp;
return 1;
}
}
PyErr_SetString(PyExc_TypeError, "c must be int or None");
return 0; // conversion failed
}
static PyObject* test_int_none(PyObject* self, PyObject* args) {
char *a, *b;
Py_ssize_t c = 0; // default value
if (!PyArg_ParseTuple(args, "ss|O&", &a, &b, int_or_none, &c)) {
return NULL;
}
printf("c_int is %i\n", c);
return PyLong_FromSsize_t(c);
}
Некоторые краткие заметки (со ссылкой на вашу версию):
- Мы уверены, что
o
никогда не NULL
, поскольку она исходит из Python, который всегда даст вам объект.
- В случае неисправности или
None
мы не изменяем указатель. Это позволяет установить значение по умолчанию в вызывающей функции.
- После преобразования в целочисленный тип C мы должны проверить, произошла ли ошибка, поскольку вы можете получить ошибки переполнения, если целое число слишком велико. В этом случае правильное исключение уже установлено, поэтому нам нужно вернуть только 0, чтобы указать сбой. (Я думаю, что это меньше беспокойства с Python 2, так как он использует отдельные большие и малые целые типы)
Ни одно из этих предложений действительно отвечает на вопрос, как просили, но они обеспечивают то, что я думаю, более чистые альтернативы.
Если возможно, я бы сделал это в качестве аргументов только для ключевого слова (тогда нет необходимости когда-либо передавать значение «Нет» в качестве значения заполнителя). В противном случае я бы использовал «O &» и функцию конвертера. У этого есть такой же уродливый код, но он по крайней мере в ясной автономной единице. (Я могу опубликовать код, если он полезен, но его легко понять). К сожалению, очень сложно избежать уродливого кода полностью с помощью C api. – DavidW
@DavidW Спасибо за подсказку с функцией конвертера. Не могли бы вы проверить мой ответ? У меня было несколько проблем с этим. Кроме того, хотите ли вы опубликовать свою рекомендацию использовать аргументы только для ключевого слова в качестве ответа? –
Я опубликовал свою версию функции конвертера. Основное отличие от вашего заключается в том, что он ничего не меняет на 'None' или ошибке, поэтому по умолчанию может быть установлен вызывающий. Я не думаю, что ваш должен segfault, если я ничего не пропущу. Я попытаюсь добавить пример только для ключевого слова позже ... – DavidW