2017-02-19 10 views
2

Скажет, у меня есть надуманные функции вроде следующий:Как разрешить None или конкретный тип в качестве аргумента функции Python C Extension?

 
static int foo(PyObject *self, PyObject *args) { 
    char *a = ""; 
    char *b = ""; 
    int c = 0; 

    if (!PyArg_ParseTuple(args, "ss|i", &a, &b, &c) { 
     return NULL; 
    } 

    printf("c is %i\n", c); 

    //some_function_requiring_int_data_type(c); 
} 

Я хотел бы, чтобы пользователь имел возможность представить либо int или None в качестве значения к c арг, но приведенный выше код не позволяют это:

>>>from spam import foo 
>>>foo('a', 'b') 
c is 0 
>>>foo('a', 'b', 100) 
c is 100 
>>>foo('a', 'b', None) 
TypeError: an integer is required 

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

 
static int foo(PyObject *self, PyObject *args) { 
    char *a = ""; 
    char *b = ""; 
    PyObject *c = NULL; // Note how I use PyObject * 
    int c_int = 0; // Note how I have an accompanying int 

    if (!PyArg_ParseTuple(args, "ss|O", &a, &b, &c) { 
     return NULL; 
    } 

    // Ugly code starts here 
    if (c) { 
     if (c != Py_None) { 
      if (!PyInt_Check(c)) { 
       PyExc_SetString(PyExc_TypeError, "c must be int or None"); 
       return; 
      } 
      c_int = PyInt_AsSsize_t(c); 
     } 
    } 

    printf("c_int is %i\n", c_int); 

    //some_function_requiring_int_data_type(c_int); 
} 

И его использование:

>>>from spam import foo 
>>>foo('a', 'b') 
c is 0 
>>>foo('a', 'b', 100) 
c is 100 
>>>foo('a', 'b', None) 
c is 0 
+0

Если возможно, я бы сделал это в качестве аргументов только для ключевого слова (тогда нет необходимости когда-либо передавать значение «Нет» в качестве значения заполнителя). В противном случае я бы использовал «O &» и функцию конвертера. У этого есть такой же уродливый код, но он по крайней мере в ясной автономной единице. (Я могу опубликовать код, если он полезен, но его легко понять). К сожалению, очень сложно избежать уродливого кода полностью с помощью C api. – DavidW

+0

@DavidW Спасибо за подсказку с функцией конвертера. Не могли бы вы проверить мой ответ? У меня было несколько проблем с этим. Кроме того, хотите ли вы опубликовать свою рекомендацию использовать аргументы только для ключевого слова в качестве ответа? –

+0

Я опубликовал свою версию функции конвертера. Основное отличие от вашего заключается в том, что он ничего не меняет на 'None' или ошибке, поэтому по умолчанию может быть установлен вызывающий. Я не думаю, что ваш должен segfault, если я ничего не пропущу. Я попытаюсь добавить пример только для ключевого слова позже ... – DavidW

ответ

1

Мое первое предложение - использовать только аргументы ключевого слова. Главное преимущество этого заключается в том, чтобы избежать необходимости пропускать значения закладок 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, так как он использует отдельные большие и малые целые типы)

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

1

converter function Используя это один из способов. Спасибо @DavidW за подсказку.

У меня есть некоторые проблемы с ним:

  • я теперь обязан вызвать ошибки сегментацию, если я не прохожу в правильном типе данных
  • Это требует, чтобы на нуле, значение INT может только одно значение (0 в этом случае). Он не может быть обобщен, так что в другом случае я бы хотел, чтобы значение по умолчанию (например, -1)
  • Мне нужно жестко закодировать сообщение об исключении («c должно быть int»), поэтому я не могу его повторно использовать для другого переменная

Если у кого-то есть работа, отправьте ее как ответ.

 
static int int_or_none(PyObject *python, void *c) { 
    int temp = 0; 
    if (python) { 
     if (python != PyNone) { 
      if (!PyInt_Check(python)) { 
       PyErr_SetString(PyExc_TypeError, "c must be int"); 
       return 0; 
      } 

      tmp = PyInt_AsSsize_t(python); 
      if (tmp 0, not %i", tmp); 
       return 0; 
      } 
     } 
    } 

    *((int *) c) = tmp; 

    return 0; 

} 


static int foo(PyObject *self, PyObject *args) { 
    char *a = ""; 
    char *b = ""; 
    int *c = NULL; // If I accidentally make this a char *c, it may segfault 

    if (!PyArg_ParseTuple(args, "ss|O&", &a, &b, &int_or_none, &c) { 
     return NULL; 
    } 

    printf("c_int is %i\n", c_int); 

    //some_function_requiring_int_data_type(c_int); 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^