summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Dickinson <dickinsm@gmail.com>2010-04-03 14:05:10 +0000
committerMark Dickinson <dickinsm@gmail.com>2010-04-03 14:05:10 +0000
commit234d85755f72c727b9d325fe8d490fa9fcc317f2 (patch)
tree8167c305122a2769f7aed2e1cd361e86b58bb3d3
parent73ae72d5dd7e04aed922bdf366716bb7a641584c (diff)
downloadcpython-234d85755f72c727b9d325fe8d490fa9fcc317f2.tar.gz
Issue #8300: Let struct.pack use __index__ to convert and pack non-integers.
Based on a patch by Meador Inge.
-rw-r--r--Doc/library/struct.rst11
-rw-r--r--Lib/test/test_struct.py18
-rw-r--r--Misc/NEWS5
-rw-r--r--Modules/_struct.c50
4 files changed, 70 insertions, 14 deletions
diff --git a/Doc/library/struct.rst b/Doc/library/struct.rst
index 3664e35173..ea6b4163ff 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 6d2a95a8c2..4329e95d23 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 a557f41b81..1f715b986a 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 d09013fe4e..fe54a47267 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;
}
}