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:
parent
c083864fc8
commit
154b7ad07e
|
@ -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'``.
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
16
Misc/NEWS
16
Misc/NEWS
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue