This question is specific to Numpy and Python 2. In Python 3 there are no separate int and long types.
The behaviour happens due to an off-by-one error in numpy. int(x) with one argument converts x to number by calling PyNumber_Int(x). PyNumber_Int then specifically takes the path for int subclasses, as int64 returned by numpy.int_ is a subclass of int:
m = o->ob_type->tp_as_number;
if (m && m->nb_int) { /* This should include subclasses of int */
/* Classic classes always take this branch. */
PyObject *res = m->nb_int(o);
if (res && (!PyInt_Check(res) && !PyLong_Check(res))) {
PyErr_Format(PyExc_TypeError,
"__int__ returned non-int (type %.200s)",
res->ob_type->tp_name);
Py_DECREF(res);
return NULL;
}
return res;
}
Now, for this code calls a->ob_type->tp_as_number->nb_int, which is implemented in numpy/core/src/umath/scalarmath.c.src. This is the location for code that is parametrized for different types; this one for <typename>_int method that is used to fill the nb_int method slot. It has the following off-by one if there:
if(LONG_MIN < x && x < LONG_MAX)
return PyInt_FromLong(x);
both operators should be <= instead. With < there, neither LONG_MIN nor LONG_MAX pass the condition and they're instead are converted into a PyLong at line 1432:
return @func@(x);
with @func@ being replaced by PyLong_FromLongLong in the case of int_. Thus, long(sys.maxint) is returned.
Now, as the sys.maxint is still representable by int, int(long(sys.maxint)) returns an int; likewise int(sys.maxint + 1) returns a long.