[Python-3000] PyLong_Check() behaviour clarification (test_getargs2 failing on Windows x64)

Neal Norwitz nnorwitz at gmail.com
Fri Mar 21 05:51:06 CET 2008


On Thu, Mar 20, 2008 at 2:13 AM, Trent Nelson <tnelson at onresolve.com> wrote:
> test_getargs2 is failing on Windows x64:
>  test test_getargs2 failed -- Traceback (most recent call last):
>   File "S:\buildbots\python.x64\3.0.nelson-win64\build\lib\test\test_getargs2.py", line 190, in test_n
>     self.failUnlessEqual(99, getargs_n(Long()))
>  TypeError: 'Long' object cannot be interpreted as an integer
>
>  Drilling down into the source, this code arrives at Python/getargs.c:convertsimple().  On Windows x64, SIZEOF_SIZE_T != SIZEOF_LONG, so there's a case 'n' statement that is defined that does the following:
>
>     iobj = PyNumber_Index(arg);
>
>  I'm a little confused.  At this point, arg is a PyObject *, and arg->obj_type->tp_as_number->nb_int has a non-zero value.  However, PyNumber_Index(arg) performs two checks, and this object fails both, which results in the TypeError being raised.

PyNumber_Index() works on an object that is an int or long or has an
__index__ method.  The "Long" class has only an __int__ method.  It is
otherwise a regular user-defined class that has no super class.

>  The first check is PyLong_Check(arg), which is handled via PyType_FastSubclass, which ends up doing the following:
>
>     ((arg->obj_type->tp_flags & Py_TPFLAGS_LONG_SUBCLASS) != 0)
>
>  tp_flags is 284160 at this point.  Py_TPFLAGS_LONG_SUBCLASS is 16777216 and Py_TPFLAGS_INT_SUBCLASS is 8388608, so this check fails.  Why doesn't tp_flags have either one of these values here?  obj_type->nb_int has a value, so shouldn't flags reflect the type?

Despite the class name of "Long", the object doesn't subclass int or
long.  That's why the tp_flags checks are failing.

nb_int has a value because of the __int__ method defined on Long.

>  I've just checked a 32-bit build and PyNumber_Index for a PyObject * representing a literal 99 also exhibits the same behaviour as above.
>
>  Also, if we're a 32-bit build, the 'n' code path visits PyLong_AsLong(), which calls PyLong_AsLongAndOverflow() -- the test cases calling getargs_n() test for the overflow being raised correctly.  Notionally, on x64 Windows where ((size_t == long long) > long), shouldn't we have a PyLong_AsLongLongAndOverflow() method?

The APIs seem very confused.  I'm not sure that is the best way to go
either.  Conceptually from what the test is trying to do, we need to
convert from a user-defined type (Long) to an PyObject (Python long or
PyLongObject) and then convert that to a Py_ssize_t.  I'm not sure the
test makes the most sense though.  There should be a test added that
uses a value between 2**32 and 2**64 to ensure we cover the win64
case.

I think the best thing to do for now (we need to clean up all the APIs
and have them make sense) is change the code that handles 'n' as
follows:

        {
                PyObject *iobj;
                Py_ssize_t *p = va_arg(*p_va, Py_ssize_t *);
                Py_ssize_t ival = -1;
                if (float_argument_error(arg))
                        return converterr("integer<n>", arg, msgbuf, bufsize);
                iobj = PyNumber_Index(arg);
                /* Code added here to handle __int__ methods on
classes which don't subclass int. */
                if (iobj == NULL) {
                        PyErr_Clear();
                        /* Using PyNumber_Long() here is probably too
permissive.  It converts strings and objects with __trunc__. Probably
we should just check the nb_int (or is it nb_long in 3k?) slot
specifically and call that? */
                        iobj = PyNumber_Long(arg);
                }
                if (iobj != NULL)
                        ival = PyLong_AsSsize_t(arg);
                if (ival == -1 && PyErr_Occurred())
                        return converterr("integer<n>", arg, msgbuf, bufsize);
                *p = ival;
                break;
        }

I'm not sure if we are abusing the 'n' format here or not.  The doc
(Doc/c-api/arg.rst) just says it turns the arg into a Py_ssize_t.  I
don't know if __index__ should really be (ab)used or not.  PEP 357
added __index__.

n


More information about the Python-3000 mailing list