11

I want to create a function in C that extends Python that can take inputs of either float or int type. So basically, I want f(5) and f(5.5) to be acceptable inputs.

I don't think I can use if (!PyArg_ParseTuple(args, "i", $value)) because it only takes only int or only float.

How can I make my function allow inputs that are either ints or floats?

I'm wondering if I should just take the input and put it into a PyObject and somehow take the type of the PyObject - is that the right approach?

2
  • Note: I've edited your question to make it easier to read. If you don't like something I've changed then feel free to change it back, because it is your question not mine. Commented Oct 4, 2016 at 4:30
  • 1
    You probably want to work with PyObject and then use the Number Protocol for working with the numbers. If you need to coerce to a float for example, you can call PyNumber_Float on your PyObject. Commented Oct 4, 2016 at 4:32

3 Answers 3

6

If you declare a C function to accept floats, the compiler won't complain if you hand it an int. For instance, this program produces the answer 2.000000:

#include <stdio.h>

float f(float x) {
  return x+1;
}

int main() {
  int i=1;
  printf ("%f", f(i));
}

A python module version, iorf.c:

#include <Python.h>

static PyObject *IorFError;

float f(float x) {
  return x+1;
}


static PyObject *
fwrap(PyObject *self, PyObject *args) {
  float in=0.0;
  if (!PyArg_ParseTuple(args, "f", &in))
    return NULL;
  return Py_BuildValue("f", f(in));
}

static PyMethodDef IorFMethods[] = {
    {"fup",  fwrap, METH_VARARGS,
     "Arg + 1"},
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


PyMODINIT_FUNC
initiorf(void)
{
  PyObject *m;

  m = Py_InitModule("iorf", IorFMethods);
  if (m == NULL)
    return;

  IorFError = PyErr_NewException("iorf.error", NULL, NULL);
  Py_INCREF(IorFError);
  PyModule_AddObject(m, "error", IorFError);
}

The setup.py:

from distutils.core import setup, Extension

module1 = Extension('iorf',
                    sources = ['iorf.c'])

setup (name = 'iorf',
       version = '0.1',
       description = 'This is a test package',
       ext_modules = [module1])

An example:

03:21 $ python
Python 2.7.10 (default, Jul 30 2016, 18:31:42)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import iorf
>>> print iorf.fup(2)
3.0
>>> print iorf.fup(2.5)
3.5
Sign up to request clarification or add additional context in comments.

9 Comments

But only because the C compiler converted the int to a float before calling the function.
Is that a problem?
I don't think it's a given that the Python runtime will do the same.
Then the problem remains, right? OP would still need to call ParseTuple up to two times, once for the case that the argument was an int object, and once for float. The question doesn't seem to be about what you do with the value afterwards.
No. Look at the example. I pass it an int, and a float, and it gives the right answer. Try it.
|
1

Floats are (usually) passed in via registers while ints are (usually) passed in via the stack. This means that you literally cannot, inside the function, check whether the argument is a float or an int.

The only workaround is to use variadic arguments, with the first argument specifying the type as either int or double (not float).

func_int_or_double (uint8_t type, ...) {
va_list ap;
va_start (ap, type);
int intarg;
double doublearg;
if (type==1) {
   intarg = va_arg (ap, int);
}
if (type==2) {
   doublearg = va_arg (ap, double);
}
va_end (ap);
// Your code goes here
}

Although, I'm not really sure if python can handle calling variadic functions, so YMMV. As a last ditch-effort you can always sprintf the value into a buffer and let your function sscanf float/int from the buffer.

Comments

1

You can check type of input value like this:

    PyObject* check_type(PyObject*self, PyObject*args) {
    PyObject*any;

    if (!PyArg_ParseTuple(args, "O", &any)) {
        PyErr_SetString(PyExc_TypeError, "Nope.");
        return NULL;
    }

    if (PyFloat_Check(any)) {
        printf("indeed float");
    }
    else {
        printf("\nint\n");
    }

    Py_INCREF(Py_None);

    return Py_None;
}

You can extract float value from object using:

double result=PyFloat_AsDouble(any);

But in this particular situation probably no need to do this, no matter what you parsing int or float you can grab it as a float and check on roundness:

    float target;
    if (!PyArg_ParseTuple(args, "f", &target)) {
                PyErr_SetString(PyExc_TypeError, "Nope.");
                return NULL;
    }

    if (target - (int)target) {
        printf("\n input is float \n");
    }
    else {
        printf("\n input is int \n");
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.