Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 79661) +++ Misc/NEWS (working copy) @@ -173,6 +173,11 @@ - Issue #8142: Update libffi to the 3.0.9 release. +- Issue #8300: When passing a non-integer argument to struct.pack with any + integer format code, struct.pack first attempts to convert the non-integer + using its __index__ method. If that method is non-existent or raises + TypeError it goes on to try the __int__ method, as described below. + - Issue #1530559: When passing a non-integer argument to struct.pack with *any* integer format code (one of 'bBhHiIlLqQ'), struct.pack attempts to use the argument's __int__ method to convert to an integer before packing. It also Index: Doc/library/struct.rst =================================================================== --- Doc/library/struct.rst (revision 79661) +++ Doc/library/struct.rst (working copy) @@ -125,16 +125,20 @@ (3) When attempting to pack a non-integer using any of the integer conversion - codes, the non-integer's :meth:`__int__` method (if present) will be called - to convert to an integer before packing. However, this behaviour is - deprecated, and will raise :exc:`DeprecationWarning`. + codes, if the non-integer has a :meth:`__index__` method then that method is + called to convert the argument to an integer before packing. If no + :meth:`__index__` method exists, or the call to :meth:`__index__` raises + :exc:`TypeError`, then the :meth:`__int__` method is tried. However, the use + of `__int__` is deprecated, and will raise :exc:`DeprecationWarning`. .. versionchanged:: 2.7 + Use of the :meth:`__index__` method for non-integers is new in 2.7. + + .. versionchanged:: 2.7 Prior to version 2.7, not all integer conversion codes would use the :meth:`__int__` method to convert, and :exc:`DeprecationWarning` was raised only for float arguments. - A format character may be preceded by an integral repeat count. For example, the format string ``'4h'`` means exactly the same as ``'hhhh'``. Index: Lib/test/test_struct.py =================================================================== --- Lib/test/test_struct.py (revision 79661) +++ Lib/test/test_struct.py (working copy) @@ -315,6 +315,24 @@ expected = struct.pack(self.format, int(nonint)) self.assertEqual(got, expected) + # Objects with an '__index__' method should be allowed + # to pack as integers. + class Indexable(object): + def __init__(self, value): + self._value = value + + def __index__(self): + return self._value + + for obj in (Indexable(0), Indexable(10), Indexable(17), + Indexable(42), Indexable(100), Indexable(127)): + try: + struct.pack(format, obj) + except: + self.fail("integer code pack failed on object " + "with '__index__' method") + + byteorders = '', '@', '=', '<', '>', '!' for code in integer_codes: for byteorder in byteorders: Index: Modules/_struct.c =================================================================== --- Modules/_struct.c (revision 79661) +++ Modules/_struct.c (working copy) @@ -107,25 +107,50 @@ static PyObject * get_pylong(PyObject *v) { - PyObject *r; + PyObject *r, *w; + int converted = 0; assert(v != NULL); if (!PyInt_Check(v) && !PyLong_Check(v)) { PyNumberMethods *m; - /* Not an integer; try to use __int__ to convert to an - integer. This behaviour is deprecated, and is removed in + /* Not an integer; first try to use __index__ to + convert to an integer. If the __index__ method + doesn't exist, or raises a TypeError, try __int__. + Use of the latter is deprecated, and will fail in Python 3.x. */ + m = Py_TYPE(v)->tp_as_number; - if (m != NULL && m->nb_int != NULL) { + if (PyIndex_Check(v)) { + w = PyNumber_Index(v); + if (w != NULL) { + v = w; + if (!PyInt_Check(v) && !PyLong_Check(v)) { + PyErr_SetString(PyExc_TypeError, + "__index__ method " + "returned non-integer"); + return NULL; + } + /* successfully converted to an integer */ + converted = 1; + } + else if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + } + else + return NULL; + } + if (!converted && m != NULL && m->nb_int != NULL) { /* Special case warning message for floats, for backwards compatibility. */ if (PyFloat_Check(v)) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - FLOAT_COERCE_WARN, 1)) + if (PyErr_WarnEx( + PyExc_DeprecationWarning, + FLOAT_COERCE_WARN, 1)) return NULL; } else { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - NON_INTEGER_WARN, 1)) + if (PyErr_WarnEx( + PyExc_DeprecationWarning, + NON_INTEGER_WARN, 1)) return NULL; } v = m->nb_int(v); @@ -133,13 +158,16 @@ return NULL; if (!PyInt_Check(v) && !PyLong_Check(v)) { PyErr_SetString(PyExc_TypeError, - "__int__ method returned non-integer"); + "__int__ method returned " + "non-integer"); return NULL; } + converted = 1; } - else { + if (!converted) { PyErr_SetString(StructError, - "cannot convert argument to integer"); + "cannot convert argument " + "to integer"); return NULL; } }