Index: Lib/test/test_struct.py =================================================================== --- Lib/test/test_struct.py (revision 78756) +++ Lib/test/test_struct.py (working copy) @@ -291,17 +291,41 @@ class NotAnIntOS: def __int__(self): - return 10585 + return 85 def __long__(self): return -163L - for badobject in ("a string", 3+42j, randrange, - NotAnIntNS(), NotAnIntOS()): - self.assertRaises(struct.error, - struct.pack, format, + for badobject in ("a string", 3+42j, randrange): + self.assertRaises((TypeError, struct.error), + struct.pack, self.format, badobject) + # an attempt to convert a non-integer (with an + # implicit conversion via __int__) should succeed, + # with a DeprecationWarning + for nonint in NotAnIntNS(), NotAnIntOS(): + with warnings.catch_warnings(record=True) as w: + # ignore everything except the + # DeprecationWarning we're looking for + warnings.simplefilter("ignore") + warnings.filterwarnings( + "always", + message=(".*integer argument expected, " + "got non-integer.*"), + category=DeprecationWarning, + module=__name__ + ) + got = struct.pack(self.format, nonint) + nwarn = len(w) + self.assertEqual(nwarn, 1, + "expected exactly one warning from " + "struct.pack({!r}, {!r}); " + "got {} warnings".format( + self.format, nonint, nwarn)) + expected = struct.pack(self.format, int(nonint)) + self.assertEqual(got, expected) + byteorders = '', '@', '=', '<', '>', '!' for code in integer_codes: for byteorder in byteorders: Index: Modules/_struct.c =================================================================== --- Modules/_struct.c (revision 78756) +++ Modules/_struct.c (working copy) @@ -17,7 +17,10 @@ typedef int Py_ssize_t; #endif -#define FLOAT_COERCE "integer argument expected, got float" +/* warning messages */ +#define FLOAT_COERCE_WARN "integer argument expected, got float" +#define NON_INTEGER_WARN "integer argument expected, got non-integer " \ + "(implicit conversion using __int__ is deprecated)" /* The translation function for each format character is table driven */ @@ -104,21 +107,57 @@ static PyObject * get_pylong(PyObject *v) { + PyObject *r; assert(v != NULL); - if (PyInt_Check(v)) - return PyLong_FromLong(PyInt_AS_LONG(v)); - if (PyLong_Check(v)) { + 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 + Python 3.x. */ + m = Py_TYPE(v)->tp_as_number; + if (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)) + return NULL; + } + else { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + NON_INTEGER_WARN, 1)) + return NULL; + } + v = m->nb_int(v); + if (v == NULL) + return NULL; + if (!PyInt_Check(v) && !PyLong_Check(v)) { + PyErr_SetString(PyExc_TypeError, + "__int__ method returned non-integer"); + } + } + else { + PyErr_SetString(StructError, + "cannot convert argument to integer"); + return NULL; + } + } + else + /* Ensure we own a reference to v. */ Py_INCREF(v); - return v; + + if (PyInt_Check(v)) { + r = PyLong_FromLong(PyInt_AS_LONG(v)); + Py_DECREF(v); } - if (PyFloat_Check(v)) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, FLOAT_COERCE, 1)<0) - return NULL; - return PyNumber_Long(v); + else if (PyLong_Check(v)) { + assert(PyLong_Check(v)); + r = v; } - PyErr_SetString(StructError, - "cannot convert argument to long"); - return NULL; + else + assert(0); /* shouldn't ever get here */ + + return r; } /* Helper to convert a Python object to a C long. Sets an exception