Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 78750) +++ Misc/NEWS (working copy) @@ -53,6 +53,10 @@ Library ------- +- Issue #1530559: Changed the struct module to connvert non-integer types + to an integer type when packing, if the packed object has an '__index__' + method. + - Issue #6906: Tk should not set Unicode environment variables on Windows. - Issue #1054943: Fix unicodedata.normalize('NFC', text) for the Public Review Index: Doc/library/struct.rst =================================================================== --- Doc/library/struct.rst (revision 78750) +++ Doc/library/struct.rst (working copy) @@ -123,6 +123,14 @@ .. versionadded:: 2.2 +(3) + The type codes ``'b'``, ``'B'``, ``'h'``, ``'H'``, ``'i'``, ``'I'``, + ``'l'``, ``'L'``, ``'q'``, ``'Q'``, and ``'P'`` may be used to pack objects + other than Python integer types if the object being packed has a + :meth:`__index__` method. + + .. versionadded:: 2.7 + A format character may be preceded by an integral repeat count. For example, the format string ``'4h'`` means exactly the same as ``'hhhh'``. Index: Lib/struct.py =================================================================== --- Lib/struct.py (revision 78750) +++ Lib/struct.py (working copy) @@ -1,3 +1,143 @@ +"""Functions to convert between Python values and C structs represented + as Python strings. It uses format strings (explained below) as compact + descriptions of the lay-out of the C structs and the intended conversion + to/from Python values. + + The optional first format char indicates byte order, size and alignment: + @: native order, size & alignment (default) + =: native order, std. size & alignment + <: little-endian, std. size & alignment + >: big-endian, std. size & alignment + !: same as > + + The remaining chars indicate types of args and must match exactly; + these can be preceded by a decimal repeat count: + x: pad byte (no data); c:char; b:signed byte; B:unsigned byte; + ?: _Bool (requires C99; if not available, char is used instead) + h:short; H:unsigned short; i:int; I:unsigned int; + l:long; L:unsigned long; f:float; d:double. + Special cases (preceding decimal count indicates length): + s:string (array of char); p: pascal string (with count byte). + Special case (only available in native format): + P:an integer type that is wide enough to hold a pointer. + Special case (not in native mode unless 'long long' in platform C): + q:long long; Q:unsigned long long + Whitespace between formats is ignored. + + The variable struct.error is an exception raised on errors""" + from _struct import * from _struct import _clearcache -from _struct import __doc__ + +# An internal function used to transform objects that may be used as integers +# to integers. This allows them to be packed using the integer type codes. +def _transform_args(*args): + newargs = [] + for arg in args: + if hasattr(arg, '__index__'): + newargs.append(arg.__index__()) + else: + newargs.append(arg) + return tuple(newargs) + +class Struct(object): + def __init__(self, format): + """S.init(fomt) -> Struct + + Return a new Struct object which writes and reads binary data + according to the format string 'format'. + """ + + from _struct import _Struct + self._struct = _Struct(format) + + def pack(self, *args): + """S.pack(v1, v2, ...) -> string + + Return a string containing values v1, v2, ... packed according to + this Struct's format. See struct.__doc__ for more on format strings. + """ + + return self._struct.pack(*_transform_args(*args)) + + def pack_into(self, buffer, offset, *args): + """S.pack_into(buffer, offset, v1, v2, ...) + + Pack the values v1, v2, ... according to this Struct's format, + write the packed bytes into the writable buffer buf starting at + offset. Note that the offset is not an optional argument. See + struct.__doc__ for more on format strings. + """ + + return self._struct.pack_into(buffer, offset, *_transform_args(*args)) + + def unpack(self, str): + """S.unpack(str) -> (v1, v2, ...) + + Return tuple containing values unpacked according to this Struct's + format. Requires len(str) == self.size. See struct.__doc__ for + more on format strings. + """ + + return self._struct.unpack(str) + + def unpack_from(self, buffer, offset=0): + """S.unpack_from(buffer[, offset=0]) -> (v1, v2, ...) + + Return tuple containing values unpacked according to this Struct's + format. Unlike unpack, unpack_from can unpack values from any + object supporting the buffer API, not just str. Requires + len(buffer[offset:]) >= self.size. See struct.__doc__ for more on + format strings. + """ + + return self._struct.unpack_from(buffer, offset) + + @property + def format(self): + """struct format string""" + + return self._struct.format + + @property + def size(self): + """struct size in bytes""" + + return self._struct.size + +def pack(fmt, *args): + """Return string containing values v1, v2, ... packed according to fmt. + """ + + from _struct import _pack + return _pack(fmt, *_transform_args(*args)) + +def pack_into(fmt, buffer, offset, *args): + """Pack the values v1, v2, ... according to fmt. Write the packed bytes + into the writable buffer buf starting at offset." + """ + + from _struct import _pack_into + return _pack_into(fmt, buffer, offset, *_transform_args(*args)) + +def unpack(fmt, string): + """Unpack the string containing packed C structure data, according to + fmt. Requires len(string) == calcsize(fmt)."); + """ + + from _struct import _unpack + return _unpack(fmt, string) + +def unpack_from(fmt, buffer, offset=0): + """Unpack the buffer, containing packed C structure data, according to + fmt, starting at offset. Requires len(buffer[offset:]) >= calcsize(fmt). + """ + + from _struct import _unpack_from + return _unpack_from(fmt, buffer, offset) + +def calcsize(fmt): + """Return size of C struct described by format string fmt.""" + + from _struct import _calcsize + return _calcsize(fmt) Index: Lib/test/test_struct.py =================================================================== --- Lib/test/test_struct.py (revision 78750) +++ Lib/test/test_struct.py (working copy) @@ -302,6 +302,23 @@ struct.pack, format, badobject) + # 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: Modules/_struct.c =================================================================== --- Modules/_struct.c (revision 78750) +++ Modules/_struct.c (working copy) @@ -112,7 +112,7 @@ return v; } if (PyFloat_Check(v)) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, FLOAT_COERCE, 1)<0) + if (PyErr_WarnEx(PyExc_DeprecationWarning, FLOAT_COERCE, 2)<0) return NULL; return PyNumber_Long(v); } @@ -1346,14 +1346,6 @@ return NULL; } - -PyDoc_STRVAR(s_unpack__doc__, -"S.unpack(str) -> (v1, v2, ...)\n\ -\n\ -Return tuple containing values unpacked according to this Struct's format.\n\ -Requires len(str) == self.size. See struct.__doc__ for more on format\n\ -strings."); - static PyObject * s_unpack(PyObject *self, PyObject *inputstr) { @@ -1388,14 +1380,6 @@ return NULL; } -PyDoc_STRVAR(s_unpack_from__doc__, -"S.unpack_from(buffer[, offset]) -> (v1, v2, ...)\n\ -\n\ -Return tuple containing values unpacked according to this Struct's format.\n\ -Unlike unpack, unpack_from can unpack values from any object supporting\n\ -the buffer API, not just str. Requires len(buffer[offset:]) >= self.size.\n\ -See struct.__doc__ for more on format strings."); - static PyObject * s_unpack_from(PyObject *self, PyObject *args, PyObject *kwds) { @@ -1502,12 +1486,6 @@ } -PyDoc_STRVAR(s_pack__doc__, -"S.pack(v1, v2, ...) -> string\n\ -\n\ -Return a string containing values v1, v2, ... packed according to this\n\ -Struct's format. See struct.__doc__ for more on format strings."); - static PyObject * s_pack(PyObject *self, PyObject *args) { @@ -1539,13 +1517,6 @@ return result; } -PyDoc_STRVAR(s_pack_into__doc__, -"S.pack_into(buffer, offset, v1, v2, ...)\n\ -\n\ -Pack the values v1, v2, ... according to this Struct's format, write \n\ -the packed bytes into the writable buffer buf starting at offset. Note\n\ -that the offset is not an optional argument. See struct.__doc__ for \n\ -more on format strings."); static PyObject * s_pack_into(PyObject *self, PyObject *args) @@ -1614,11 +1585,11 @@ /* List of functions */ static struct PyMethodDef s_methods[] = { - {"pack", s_pack, METH_VARARGS, s_pack__doc__}, - {"pack_into", s_pack_into, METH_VARARGS, s_pack_into__doc__}, - {"unpack", s_unpack, METH_O, s_unpack__doc__}, + {"pack", s_pack, METH_VARARGS, NULL}, + {"pack_into", s_pack_into, METH_VARARGS, NULL}, + {"unpack", s_unpack, METH_O, NULL}, {"unpack_from", (PyCFunction)s_unpack_from, METH_VARARGS|METH_KEYWORDS, - s_unpack_from__doc__}, + NULL}, {NULL, NULL} /* sentinel */ }; @@ -1635,7 +1606,7 @@ static PyTypeObject PyStructType = { PyVarObject_HEAD_INIT(NULL, 0) - "Struct", + "_Struct", sizeof(PyStructObject), 0, (destructor)s_dealloc, /* tp_dealloc */ @@ -1719,11 +1690,8 @@ Py_RETURN_NONE; } -PyDoc_STRVAR(calcsize_doc, -"Return size of C struct described by format string fmt."); - static PyObject * -calcsize(PyObject *self, PyObject *fmt) +_calcsize(PyObject *self, PyObject *fmt) { Py_ssize_t n; PyObject *s_object = cache_struct(fmt); @@ -1734,19 +1702,12 @@ return PyInt_FromSsize_t(n); } -PyDoc_STRVAR(pack_doc, -"Return string containing values v1, v2, ... packed according to fmt."); - static PyObject * -pack(PyObject *self, PyObject *args) +_pack(PyObject *self, PyObject *args) { PyObject *s_object, *fmt, *newargs, *result; Py_ssize_t n = PyTuple_GET_SIZE(args); - if (n == 0) { - PyErr_SetString(PyExc_TypeError, "missing format argument"); - return NULL; - } fmt = PyTuple_GET_ITEM(args, 0); newargs = PyTuple_GetSlice(args, 1, n); if (newargs == NULL) @@ -1763,20 +1724,12 @@ return result; } -PyDoc_STRVAR(pack_into_doc, -"Pack the values v1, v2, ... according to fmt.\n\ -Write the packed bytes into the writable buffer buf starting at offset."); - static PyObject * -pack_into(PyObject *self, PyObject *args) +_pack_into(PyObject *self, PyObject *args) { PyObject *s_object, *fmt, *newargs, *result; Py_ssize_t n = PyTuple_GET_SIZE(args); - if (n == 0) { - PyErr_SetString(PyExc_TypeError, "missing format argument"); - return NULL; - } fmt = PyTuple_GET_ITEM(args, 0); newargs = PyTuple_GetSlice(args, 1, n); if (newargs == NULL) @@ -1793,12 +1746,8 @@ return result; } -PyDoc_STRVAR(unpack_doc, -"Unpack the string containing packed C structure data, according to fmt.\n\ -Requires len(string) == calcsize(fmt)."); - static PyObject * -unpack(PyObject *self, PyObject *args) +_unpack(PyObject *self, PyObject *args) { PyObject *s_object, *fmt, *inputstr, *result; @@ -1813,20 +1762,12 @@ return result; } -PyDoc_STRVAR(unpack_from_doc, -"Unpack the buffer, containing packed C structure data, according to\n\ -fmt, starting at offset. Requires len(buffer[offset:]) >= calcsize(fmt)."); - static PyObject * -unpack_from(PyObject *self, PyObject *args, PyObject *kwds) +_unpack_from(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *s_object, *fmt, *newargs, *result; Py_ssize_t n = PyTuple_GET_SIZE(args); - if (n == 0) { - PyErr_SetString(PyExc_TypeError, "missing format argument"); - return NULL; - } fmt = PyTuple_GET_ITEM(args, 0); newargs = PyTuple_GetSlice(args, 1, n); if (newargs == NULL) @@ -1845,47 +1786,18 @@ static struct PyMethodDef module_functions[] = { {"_clearcache", (PyCFunction)clearcache, METH_NOARGS, clearcache_doc}, - {"calcsize", calcsize, METH_O, calcsize_doc}, - {"pack", pack, METH_VARARGS, pack_doc}, - {"pack_into", pack_into, METH_VARARGS, pack_into_doc}, - {"unpack", unpack, METH_VARARGS, unpack_doc}, - {"unpack_from", (PyCFunction)unpack_from, - METH_VARARGS|METH_KEYWORDS, unpack_from_doc}, + {"_calcsize", _calcsize, METH_O, NULL}, + {"_pack", _pack, METH_VARARGS, NULL}, + {"_pack_into", _pack_into, METH_VARARGS, NULL}, + {"_unpack", _unpack, METH_VARARGS, NULL}, + {"_unpack_from", (PyCFunction)_unpack_from, + METH_VARARGS|METH_KEYWORDS, NULL}, {NULL, NULL} /* sentinel */ }; /* Module initialization */ -PyDoc_STRVAR(module_doc, -"Functions to convert between Python values and C structs represented\n\ -as Python strings. It uses format strings (explained below) as compact\n\ -descriptions of the lay-out of the C structs and the intended conversion\n\ -to/from Python values.\n\ -\n\ -The optional first format char indicates byte order, size and alignment:\n\ - @: native order, size & alignment (default)\n\ - =: native order, std. size & alignment\n\ - <: little-endian, std. size & alignment\n\ - >: big-endian, std. size & alignment\n\ - !: same as >\n\ -\n\ -The remaining chars indicate types of args and must match exactly;\n\ -these can be preceded by a decimal repeat count:\n\ - x: pad byte (no data); c:char; b:signed byte; B:unsigned byte;\n\ - ?: _Bool (requires C99; if not available, char is used instead)\n\ - h:short; H:unsigned short; i:int; I:unsigned int;\n\ - l:long; L:unsigned long; f:float; d:double.\n\ -Special cases (preceding decimal count indicates length):\n\ - s:string (array of char); p: pascal string (with count byte).\n\ -Special case (only available in native format):\n\ - P:an integer type that is wide enough to hold a pointer.\n\ -Special case (not in native mode unless 'long long' in platform C):\n\ - q:long long; Q:unsigned long long\n\ -Whitespace between formats is ignored.\n\ -\n\ -The variable struct.error is an exception raised on errors.\n"); - PyMODINIT_FUNC init_struct(void) { @@ -1895,7 +1807,7 @@ if (ver == NULL) return; - m = Py_InitModule3("_struct", module_functions, module_doc); + m = Py_InitModule3("_struct", module_functions, NULL); if (m == NULL) return; @@ -1957,7 +1869,7 @@ PyModule_AddObject(m, "error", StructError); Py_INCREF((PyObject*)&PyStructType); - PyModule_AddObject(m, "Struct", (PyObject*)&PyStructType); + PyModule_AddObject(m, "_Struct", (PyObject*)&PyStructType); PyModule_AddObject(m, "__version__", ver);