Jonatan Jonatan - 3 months ago 8
Python Question

Why does round raise on ndigits=None for integers but not for floats?

Why does

round()
behave different for int and float when
ndigits
is explicitly set to
None
?

Console test in Python 3.5.1:

>>> round(1, None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object cannot be interpreted as an integer
>>> round(1.0, None)
1


Update:

I reported this as a bug (https://bugs.python.org/issue27936) and there is currently a submitted patch. @PadraicCunningham and @CraigBurgler were correct.

Answer

From the source code for float_round in floatobjects.c in 3.5:

float_round(PyObject *v, PyObject *args)
    ... 
    if (!PyArg_ParseTuple(args, "|O", &o_ndigits))
        return NULL;
    ...
    if (o_ndigits == NULL || o_ndigits == Py_None) {
        /* single-argument round or with None ndigits:
         * round to nearest integer */
        ...

The || o_ndigits == Py_None bit catches an ndigits=None argument explicitly and discards it, treating the call to round as a single-argument call.

In 3.4, this code looks like:

float_round(PyObject *v, PyObject *args)
    ... 
    if (!PyArg_ParseTuple(args, "|O", &o_ndigits))
        return NULL;
    ...
    if (o_ndigits == NULL) {
            /* single-argument round: round to nearest integer */
    ...

there is no || o_ndigits == Py_None test and hence an ndgits=None argument falls through and is treated like an int, thus causing a TypeError for round(1.0, None) in 3.4.

There is no check for o_ndigits == Py_None in long_round in longobject.c in both 3.4 and 3.5, thus raising a TypeError for round(1, None) in both 3.4 and 3.5

                 treat ndigits=None as 
Version/Type     single-argument call    round(n, None) 
  3.4/float               No               TypeError
  3.4/long                No               TypeError
  3.5/float               Yes                  n
  3.5/long                No               TypeError