[Python-checkins] r78762 - in python/trunk: Doc/library/struct.rst Lib/test/test_struct.py Misc/NEWS Modules/_struct.c

mark.dickinson python-checkins at python.org
Sun Mar 7 17:24:45 CET 2010


Author: mark.dickinson
Date: Sun Mar  7 17:24:45 2010
New Revision: 78762

Log:
Issue #1530559: When packing a non-integer with any integer conversion
code using struct.pack, attempt to convert to an integer first using
the argument's __int__ method (if present).  Also raise a
DeprecationWarning for any such usage of __int__.

This fixes a regression from 2.6, where some (but not all) integer
conversion codes already used __int__.


Modified:
   python/trunk/Doc/library/struct.rst
   python/trunk/Lib/test/test_struct.py
   python/trunk/Misc/NEWS
   python/trunk/Modules/_struct.c

Modified: python/trunk/Doc/library/struct.rst
==============================================================================
--- python/trunk/Doc/library/struct.rst	(original)
+++ python/trunk/Doc/library/struct.rst	Sun Mar  7 17:24:45 2010
@@ -123,6 +123,18 @@
 
    .. versionadded:: 2.2
 
+(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`.
+
+   .. 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'``.
 

Modified: python/trunk/Lib/test/test_struct.py
==============================================================================
--- python/trunk/Lib/test/test_struct.py	(original)
+++ python/trunk/Lib/test/test_struct.py	Sun Mar  7 17:24:45 2010
@@ -2,10 +2,6 @@
 import unittest
 import struct
 import warnings
-warnings.filterwarnings("ignore", "struct integer overflow masking is deprecated",
-                        DeprecationWarning)
-
-from functools import wraps
 from test.test_support import run_unittest
 
 import sys
@@ -35,22 +31,27 @@
 class StructTest(unittest.TestCase):
 
     def check_float_coerce(self, format, number):
-        # SF bug 1530559. struct.pack raises TypeError where it used to convert.
-        with warnings.catch_warnings():
+        # SF bug 1530559. struct.pack raises TypeError where it used
+        # to convert.
+        with warnings.catch_warnings(record=True) as w:
+            # ignore everything except the
+            # DeprecationWarning we're looking for
+            warnings.simplefilter("ignore")
             warnings.filterwarnings(
-                "ignore",
-                category=DeprecationWarning,
+                "always",
                 message=".*integer argument expected, got float",
-                module=__name__)
-            self.assertEqual(struct.pack(format, number), struct.pack(format, int(number)))
-
-        with warnings.catch_warnings():
-            warnings.filterwarnings(
-                "error",
                 category=DeprecationWarning,
-                message=".*integer argument expected, got float",
-                module="unittest")
-            self.assertRaises(DeprecationWarning, struct.pack, format, number)
+                module=__name__
+                )
+            got = struct.pack(format, number)
+            nwarn = len(w)
+        self.assertEqual(nwarn, 1,
+                         "expected exactly one warning from "
+                         "struct.pack({!r}, {!r});  "
+                         "got {} warnings".format(
+                format, number, nwarn))
+        expected = struct.pack(format, int(number))
+        self.assertEqual(got, expected)
 
     def test_isbigendian(self):
         self.assertEqual((struct.pack('=i', 1)[0] == chr(0)), ISBIGENDIAN)
@@ -291,17 +292,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:

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Sun Mar  7 17:24:45 2010
@@ -15,6 +15,18 @@
 Library
 -------
 
+Extension Modules
+-----------------
+
+- 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 produces a DeprecationWarning in
+  this case.  (In Python 2.6, the behaviour was inconsistent: __int__
+  was used for some integer codes but not for others, and the set of
+  integer codes for which it was used differed between native packing
+  and standard packing.)
+
 
 What's New in Python 2.7 alpha 4?
 =================================
@@ -2192,6 +2204,10 @@
       TypeError used to be raised (with a confusing error message) for
       'I', 'L', '*B', '*H', '*I', '*L', and struct.error in other cases.
 
+  Note: as of Python 2.7 beta 1, the above is out of date.  In 2.7
+  beta 1, any argument with an __int__ method can be packed, but use
+  of this feature triggers a DeprecationWarning.
+
 - Issue #4873: Fix resource leaks in error cases of pwd and grp.
 
 - Issue #4751: For hashlib algorithms provided by OpenSSL, the Python

Modified: python/trunk/Modules/_struct.c
==============================================================================
--- python/trunk/Modules/_struct.c	(original)
+++ python/trunk/Modules/_struct.c	Sun Mar  7 17:24:45 2010
@@ -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,58 @@
 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)) {
-		Py_INCREF(v);
-		return v;
-	}
-	if (PyFloat_Check(v)) {
-		if (PyErr_WarnEx(PyExc_DeprecationWarning, FLOAT_COERCE, 1)<0)
+	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");
+				return NULL;
+			}
+		}
+		else {
+			PyErr_SetString(StructError,
+					"cannot convert argument to integer");
 			return NULL;
-		return PyNumber_Long(v);
+		}
 	}
-	PyErr_SetString(StructError,
-			"cannot convert argument to long");
-	return NULL;
+	else
+		/* Ensure we own a reference to v. */
+		Py_INCREF(v);
+
+	if (PyInt_Check(v)) {
+		r = PyLong_FromLong(PyInt_AS_LONG(v));
+		Py_DECREF(v);
+	}
+	else if (PyLong_Check(v)) {
+		assert(PyLong_Check(v));
+		r = v;
+	}
+	else
+		assert(0);  /* shouldn't ever get here */
+
+	return r;
 }
 
 /* Helper to convert a Python object to a C long.  Sets an exception


More information about the Python-checkins mailing list