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__.
This commit is contained in:
Mark Dickinson 2010-03-07 16:24:45 +00:00
parent c083864fc8
commit 154b7ad07e
4 changed files with 128 additions and 35 deletions

View File

@ -123,6 +123,18 @@ Notes:
.. 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'``.

View File

@ -2,10 +2,6 @@ import array
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 @@ def bigendian_to_native(value):
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 StructTest(unittest.TestCase):
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:

View File

@ -15,6 +15,18 @@ Core and Builtins
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 @@ Extension Modules
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

View File

@ -17,7 +17,10 @@ static PyTypeObject PyStructType;
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 char *integer_codes = "bBhHiIlLqQ";
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