From 4846a8e828e345772de53aa410fcabe6a4aae772 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sat, 3 Apr 2010 14:05:10 +0000 Subject: Issue #8300: Let struct.pack use __index__ to convert and pack non-integers. Based on a patch by Meador Inge. --- Doc/library/struct.rst | 11 ++++++++--- Lib/test/test_struct.py | 18 ++++++++++++++++++ Misc/NEWS | 5 +++++ Modules/_struct.c | 50 ++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 70 insertions(+), 14 deletions(-) diff --git a/Doc/library/struct.rst b/Doc/library/struct.rst index 3664e35..ea6b416 100644 --- a/Doc/library/struct.rst +++ b/Doc/library/struct.rst @@ -125,9 +125,14 @@ Notes: (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 diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 6d2a95a..4329e95 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -315,6 +315,24 @@ class StructTest(unittest.TestCase): 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: diff --git a/Misc/NEWS b/Misc/NEWS index a557f41..1f715b9 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -173,6 +173,11 @@ Extension Modules - 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 diff --git a/Modules/_struct.c b/Modules/_struct.c index d09013f..fe54a47 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -107,25 +107,50 @@ static char *integer_codes = "bBhHiIlLqQ"; 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 @@ get_pylong(PyObject *v) 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; } } -- cgit v0.12