]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #8300: Let struct.pack use __index__ to convert and pack non-integers.
authorMark Dickinson <dickinsm@gmail.com>
Sat, 3 Apr 2010 14:05:10 +0000 (14:05 +0000)
committerMark Dickinson <dickinsm@gmail.com>
Sat, 3 Apr 2010 14:05:10 +0000 (14:05 +0000)
Based on a patch by Meador Inge.

Doc/library/struct.rst
Lib/test/test_struct.py
Misc/NEWS
Modules/_struct.c

index 3664e3517305a08f3791d22e1ae451b79e70514b..ea6b4163ff826c8a1961061983a354c0cd540082 100644 (file)
@@ -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
index 6d2a95a8c281ce169a0e3fdc6318289472ed63ff..4329e95d2385c19ee3ac88d39c777f50de3163d4 100644 (file)
@@ -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:
index a557f41b817d9ba113b2bc89f1cc97dba2c3a45c..1f715b986aee79b3bdc96f6c52d512314b5c78b4 100644 (file)
--- 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
index d09013fe4efb01da8b53215bb01fd7f9a62a8795..fe54a47267bfeca7d54a3a02eb18480ad5da0d60 100644 (file)
@@ -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;
                }
        }