From 877f65dadfabf3d07b0e93a88163daf7e583db27 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 11 Jan 2026 05:08:07 +0300 Subject: [PATCH 1/6] gh-143672: convert the struct module to AC --- Modules/_struct.c | 313 ++++++++++++++++++------------------- Modules/clinic/_struct.c.h | 304 +++++++++++++++++++++++++++++++---- 2 files changed, 425 insertions(+), 192 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index 2acb3df3a30395..c9f2b41dc1ca76 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -1784,15 +1784,13 @@ Struct.__init__ Create a compiled struct object. -Return a new Struct object which writes and reads binary data according to -the format string. - -See help(struct) for more on format strings. +Return a new Struct object which writes and reads binary data according +to the format string. See help(struct) for more on format strings. [clinic start generated code]*/ static int Struct___init___impl(PyStructObject *self, PyObject *format) -/*[clinic end generated code: output=b8e80862444e92d0 input=192a4575a3dde802]*/ +/*[clinic end generated code: output=b8e80862444e92d0 input=1af78a5f57d82cec]*/ { int ret = 0; @@ -1906,15 +1904,13 @@ Struct.unpack Return a tuple containing unpacked values. -Unpack according to the format string Struct.format. The buffer's size -in bytes must be Struct.size. - -See help(struct) for more on format strings. +Unpack according to the format self. The buffer's size in bytes must be +self.size. See help(struct) for more on format strings. [clinic start generated code]*/ static PyObject * Struct_unpack_impl(PyStructObject *self, Py_buffer *buffer) -/*[clinic end generated code: output=873a24faf02e848a input=3113f8e7038b2f6c]*/ +/*[clinic end generated code: output=873a24faf02e848a input=5b8bdd4ec3c35bab]*/ { _structmodulestate *state = get_struct_state_structinst(self); assert(self->s_codes != NULL); @@ -1935,18 +1931,15 @@ Struct.unpack_from Return a tuple containing unpacked values. -Values are unpacked according to the format string Struct.format. - -The buffer's size in bytes, starting at position offset, must be -at least Struct.size. - -See help(struct) for more on format strings. +Values are unpacked according to the format self. The buffer's size in +bytes, starting at position offset, must be at least self.size. See +help(struct) for more on format strings. [clinic start generated code]*/ static PyObject * Struct_unpack_from_impl(PyStructObject *self, Py_buffer *buffer, Py_ssize_t offset) -/*[clinic end generated code: output=57fac875e0977316 input=cafd4851d473c894]*/ +/*[clinic end generated code: output=57fac875e0977316 input=558382eeee3c47f3]*/ { _structmodulestate *state = get_struct_state_structinst(self); assert(self->s_codes != NULL); @@ -2090,14 +2083,13 @@ Struct.iter_unpack Return an iterator yielding tuples. Tuples are unpacked from the given bytes source, like a repeated -invocation of unpack_from(). - -Requires that the bytes length be a multiple of the struct size. +invocation of self.unpack_from(). Requires that the bytes length be a +multiple of the self.size. [clinic start generated code]*/ static PyObject * Struct_iter_unpack_impl(PyStructObject *self, PyObject *buffer) -/*[clinic end generated code: output=818f89ad4afa8d64 input=6d65b3f3107dbc99]*/ +/*[clinic end generated code: output=818f89ad4afa8d64 input=5d5c404776b815a4]*/ { _structmodulestate *state = get_struct_state_structinst(self); unpackiterobject *iter; @@ -2226,171 +2218,176 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset, return 0; } +/*[clinic input] +Struct.pack as s_pack -PyDoc_STRVAR(s_pack__doc__, -"S.pack(v1, v2, ...) -> bytes\n\ -\n\ -Return a bytes object containing values v1, v2, ... packed according\n\ -to the format string S.format. See help(struct) for more on format\n\ -strings."); + *args: array + +Return a bytes object with args, packed according the format self. + +See help(struct) for more on format strings. +[clinic start generated code]*/ static PyObject * -s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +s_pack_impl(PyStructObject *self, PyObject * const *args, + Py_ssize_t args_length) +/*[clinic end generated code: output=dc06e32695719fce input=24c5c3c677d4d7e2]*/ { - PyStructObject *soself; _structmodulestate *state = get_struct_state_structinst(self); /* Validate arguments. */ - soself = PyStructObject_CAST(self); assert(PyStruct_Check(self, state)); - assert(soself->s_codes != NULL); - if (nargs != soself->s_len) - { + assert(self->s_codes != NULL); + if (args_length != self->s_len) { PyErr_Format(state->StructError, - "pack expected %zd items for packing (got %zd)", soself->s_len, nargs); + "pack expected %zd items for packing (got %zd)", + self->s_len, args_length); return NULL; } /* Allocate a new string */ - PyBytesWriter *writer = PyBytesWriter_Create(soself->s_size); + PyBytesWriter *writer = PyBytesWriter_Create(self->s_size); if (writer == NULL) { return NULL; } char *buf = PyBytesWriter_GetData(writer); /* Call the guts */ - if ( s_pack_internal(soself, args, 0, buf, state) != 0 ) { + if ( s_pack_internal(self, args, 0, buf, state) != 0 ) { PyBytesWriter_Discard(writer); return NULL; } - return PyBytesWriter_FinishWithSize(writer, soself->s_size); + return PyBytesWriter_FinishWithSize(writer, self->s_size); } -PyDoc_STRVAR(s_pack_into__doc__, -"S.pack_into(buffer, offset, v1, v2, ...)\n\ -\n\ -Pack the values v1, v2, ... according to the format string S.format\n\ -and write the packed bytes into the writable buffer buf starting at\n\ -offset. Note that the offset is a required argument. See\n\ -help(struct) for more on format strings."); +/*[clinic input] +Struct.pack_into as s_pack_into + + buffer: Py_buffer + offset: Py_ssize_t + / + *args: array + +Pack args to the writtable buffer according to the format self. + +The packed bytes written starting at offset. See help(struct) for more +on format strings. +[clinic start generated code]*/ static PyObject * -s_pack_into(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +s_pack_into_impl(PyStructObject *self, Py_buffer *buffer, Py_ssize_t offset, + PyObject * const *args, Py_ssize_t args_length) +/*[clinic end generated code: output=40e08ea63f1d57bd input=687fd92ef694b362]*/ { - PyStructObject *soself; - Py_buffer buffer; - Py_ssize_t offset; _structmodulestate *state = get_struct_state_structinst(self); /* Validate arguments. +1 is for the first arg as buffer. */ - soself = PyStructObject_CAST(self); assert(PyStruct_Check(self, state)); - assert(soself->s_codes != NULL); - if (nargs != (soself->s_len + 2)) - { - if (nargs == 0) { - PyErr_Format(state->StructError, - "pack_into expected buffer argument"); - } - else if (nargs == 1) { - PyErr_Format(state->StructError, - "pack_into expected offset argument"); - } - else { - PyErr_Format(state->StructError, - "pack_into expected %zd items for packing (got %zd)", - soself->s_len, (nargs - 2)); - } - return NULL; - } - - /* Extract a writable memory buffer from the first argument */ - if (!PyArg_Parse(args[0], "w*", &buffer)) - return NULL; - assert(buffer.len >= 0); - - /* Extract the offset from the first argument */ - offset = PyNumber_AsSsize_t(args[1], PyExc_IndexError); - if (offset == -1 && PyErr_Occurred()) { - PyBuffer_Release(&buffer); + assert(self->s_codes != NULL); + if (args_length != self->s_len) { + PyErr_Format(state->StructError, + "pack_into expected %zd items for packing (got %zd)", + self->s_len, args_length); + PyBuffer_Release(buffer); return NULL; } + assert(buffer->len >= 0); /* Support negative offsets. */ if (offset < 0) { /* Check that negative offset is low enough to fit data */ - if (offset + soself->s_size > 0) { + if (offset + self->s_size > 0) { PyErr_Format(state->StructError, "no space to pack %zd bytes at offset %zd", - soself->s_size, + self->s_size, offset); - PyBuffer_Release(&buffer); + PyBuffer_Release(buffer); return NULL; } /* Check that negative offset is not crossing buffer boundary */ - if (offset + buffer.len < 0) { + if (offset + buffer->len < 0) { PyErr_Format(state->StructError, "offset %zd out of range for %zd-byte buffer", offset, - buffer.len); - PyBuffer_Release(&buffer); + buffer->len); + PyBuffer_Release(buffer); return NULL; } - offset += buffer.len; + offset += buffer->len; } /* Check boundaries */ - if ((buffer.len - offset) < soself->s_size) { + if ((buffer->len - offset) < self->s_size) { assert(offset >= 0); - assert(soself->s_size >= 0); + assert(self->s_size >= 0); PyErr_Format(state->StructError, "pack_into requires a buffer of at least %zu bytes for " "packing %zd bytes at offset %zd " "(actual buffer size is %zd)", - (size_t)soself->s_size + (size_t)offset, - soself->s_size, + (size_t)self->s_size + (size_t)offset, + self->s_size, offset, - buffer.len); - PyBuffer_Release(&buffer); + buffer->len); + PyBuffer_Release(buffer); return NULL; } /* Call the guts */ - if (s_pack_internal(soself, args, 2, (char*)buffer.buf + offset, state) != 0) { - PyBuffer_Release(&buffer); + if (s_pack_internal(self, args, 0, + (char*)buffer->buf + offset, state) != 0) + { + PyBuffer_Release(buffer); return NULL; } - PyBuffer_Release(&buffer); + PyBuffer_Release(buffer); Py_RETURN_NONE; } +/*[clinic input] +@getter +Struct.format as s_get_format + +Struct format string. +[clinic start generated code]*/ + static PyObject * -s_get_format(PyObject *op, void *Py_UNUSED(closure)) +s_get_format_get_impl(PyStructObject *self) +/*[clinic end generated code: output=16ee02604fef5898 input=05fccee7547f0796]*/ { - PyStructObject *self = PyStructObject_CAST(op); return PyUnicode_FromStringAndSize(PyBytes_AS_STRING(self->s_format), PyBytes_GET_SIZE(self->s_format)); } +/*[clinic input] +@getter +Struct.size as s_get_size + +Struct size in bytes. +[clinic start generated code]*/ + static PyObject * -s_get_size(PyObject *op, void *Py_UNUSED(closure)) +s_get_size_get_impl(PyStructObject *self) +/*[clinic end generated code: output=9cf037ff73fd4224 input=2870dd5b688f313a]*/ { - PyStructObject *self = PyStructObject_CAST(op); return PyLong_FromSsize_t(self->s_size); } -PyDoc_STRVAR(s_sizeof__doc__, -"S.__sizeof__() -> size of S in memory, in bytes"); +/*[clinic input] +Struct.__sizeof__ as s_sizeof + +Size of the self in memory, in bytes. +[clinic start generated code]*/ static PyObject * -s_sizeof(PyObject *op, PyObject *Py_UNUSED(dummy)) +s_sizeof_impl(PyStructObject *self) +/*[clinic end generated code: output=5994296a8236f514 input=9d260684863a7618]*/ + { - PyStructObject *self = PyStructObject_CAST(op); size_t size = _PyObject_SIZE(Py_TYPE(self)) + sizeof(formatcode); for (formatcode *code = self->s_codes; code->fmtdef != NULL; code++) { size += sizeof(formatcode); @@ -2416,11 +2413,11 @@ s_repr(PyObject *op) static struct PyMethodDef s_methods[] = { STRUCT_ITER_UNPACK_METHODDEF - {"pack", _PyCFunction_CAST(s_pack), METH_FASTCALL, s_pack__doc__}, - {"pack_into", _PyCFunction_CAST(s_pack_into), METH_FASTCALL, s_pack_into__doc__}, + S_PACK_METHODDEF + S_PACK_INTO_METHODDEF STRUCT_UNPACK_METHODDEF STRUCT_UNPACK_FROM_METHODDEF - {"__sizeof__", s_sizeof, METH_NOARGS, s_sizeof__doc__}, + S_SIZEOF_METHODDEF {NULL, NULL} /* sentinel */ }; @@ -2430,8 +2427,8 @@ static PyMemberDef s_members[] = { }; static PyGetSetDef s_getsetlist[] = { - {"format", s_get_format, NULL, PyDoc_STR("struct format string"), NULL}, - {"size", s_get_size, NULL, PyDoc_STR("struct size in bytes"), NULL}, + S_GET_FORMAT_GETSETDEF + S_GET_SIZE_GETSETDEF {NULL} /* sentinel */ }; @@ -2535,58 +2532,48 @@ calcsize_impl(PyObject *module, PyStructObject *s_object) return s_object->s_size; } -PyDoc_STRVAR(pack_doc, -"pack(format, v1, v2, ...) -> bytes\n\ -\n\ -Return a bytes object containing the values v1, v2, ... packed according\n\ -to the format string. See help(struct) for more on format strings."); +/*[clinic input] +pack + + format as s_object: cache_struct + / + *args: array + +Return a bytes object with args, packed according the format string. + +See help(struct) for more on format strings. +[clinic start generated code]*/ static PyObject * -pack(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +pack_impl(PyObject *module, PyStructObject *s_object, PyObject * const *args, + Py_ssize_t args_length) +/*[clinic end generated code: output=3c490d3313013a77 input=7ad2023f64935c39]*/ { - PyObject *s_object = NULL; - PyObject *format, *result; + return s_pack_impl(s_object, args, args_length); +} - if (nargs == 0) { - PyErr_SetString(PyExc_TypeError, "missing format argument"); - return NULL; - } - format = args[0]; +/*[clinic input] +pack_into - if (!cache_struct_converter(module, format, (PyStructObject **)&s_object)) { - return NULL; - } - result = s_pack(s_object, args + 1, nargs - 1); - Py_DECREF(s_object); - return result; -} + format as s_object: cache_struct + buffer: Py_buffer + offset: Py_ssize_t + / + *args: array -PyDoc_STRVAR(pack_into_doc, -"pack_into(format, buffer, offset, v1, v2, ...)\n\ -\n\ -Pack the values v1, v2, ... according to the format string and write\n\ -the packed bytes into the writable buffer buf starting at offset. Note\n\ -that the offset is a required argument. See help(struct) for more\n\ -on format strings."); +Pack args to the writtable buffer according to the format string. + +The packed bytes written starting at offset. See help(struct) for more +on format strings. +[clinic start generated code]*/ static PyObject * -pack_into(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +pack_into_impl(PyObject *module, PyStructObject *s_object, Py_buffer *buffer, + Py_ssize_t offset, PyObject * const *args, + Py_ssize_t args_length) +/*[clinic end generated code: output=86bd2d8d0628b540 input=4ace36b845e3167e]*/ { - PyObject *s_object = NULL; - PyObject *format, *result; - - if (nargs == 0) { - PyErr_SetString(PyExc_TypeError, "missing format argument"); - return NULL; - } - format = args[0]; - - if (!cache_struct_converter(module, format, (PyStructObject **)&s_object)) { - return NULL; - } - result = s_pack_into(s_object, args + 1, nargs - 1); - Py_DECREF(s_object); - return result; + return s_pack_into_impl(s_object, buffer, offset, args, args_length); } /*[clinic input] @@ -2598,14 +2585,13 @@ unpack Return a tuple containing values unpacked according to the format string. -The buffer's size in bytes must be calcsize(format). - -See help(struct) for more on format strings. +The buffer's size in bytes must be calcsize(format). See help(struct) +for more on format strings. [clinic start generated code]*/ static PyObject * unpack_impl(PyObject *module, PyStructObject *s_object, Py_buffer *buffer) -/*[clinic end generated code: output=48ddd4d88eca8551 input=05fa3b91678da727]*/ +/*[clinic end generated code: output=48ddd4d88eca8551 input=7df28c5d0b5b6f4e]*/ { return Struct_unpack_impl(s_object, buffer); } @@ -2620,15 +2606,14 @@ unpack_from Return a tuple containing values unpacked according to the format string. -The buffer's size, minus offset, must be at least calcsize(format). - -See help(struct) for more on format strings. +The buffer's size, minus offset, must be at least calcsize(format). See +help(struct) for more on format strings. [clinic start generated code]*/ static PyObject * unpack_from_impl(PyObject *module, PyStructObject *s_object, Py_buffer *buffer, Py_ssize_t offset) -/*[clinic end generated code: output=1042631674c6e0d3 input=6e80a5398e985025]*/ +/*[clinic end generated code: output=1042631674c6e0d3 input=599262b23559f6c5]*/ { return Struct_unpack_from_impl(s_object, buffer, offset); } @@ -2642,16 +2627,16 @@ iter_unpack Return an iterator yielding tuples unpacked from the given bytes. -The bytes are unpacked according to the format string, like -a repeated invocation of unpack_from(). - -Requires that the bytes length be a multiple of the format struct size. +The bytes are unpacked according to the format string, like a repeated +invocation of unpack_from(). Requires that the bytes length be a +multiple of the format struct size. See help(struct) for more on format +strings. [clinic start generated code]*/ static PyObject * iter_unpack_impl(PyObject *module, PyStructObject *s_object, PyObject *buffer) -/*[clinic end generated code: output=0ae50e250d20e74d input=b214a58869a3c98d]*/ +/*[clinic end generated code: output=0ae50e250d20e74d input=98fff4c9e3fa70db]*/ { return Struct_iter_unpack((PyObject*)s_object, buffer); } @@ -2660,8 +2645,8 @@ static struct PyMethodDef module_functions[] = { _CLEARCACHE_METHODDEF CALCSIZE_METHODDEF ITER_UNPACK_METHODDEF - {"pack", _PyCFunction_CAST(pack), METH_FASTCALL, pack_doc}, - {"pack_into", _PyCFunction_CAST(pack_into), METH_FASTCALL, pack_into_doc}, + PACK_METHODDEF + PACK_INTO_METHODDEF UNPACK_METHODDEF UNPACK_FROM_METHODDEF {NULL, NULL} /* sentinel */ diff --git a/Modules/clinic/_struct.c.h b/Modules/clinic/_struct.c.h index e4eaadb91eb231..eb29998a6a1e39 100644 --- a/Modules/clinic/_struct.c.h +++ b/Modules/clinic/_struct.c.h @@ -15,10 +15,8 @@ PyDoc_STRVAR(Struct___init____doc__, "\n" "Create a compiled struct object.\n" "\n" -"Return a new Struct object which writes and reads binary data according to\n" -"the format string.\n" -"\n" -"See help(struct) for more on format strings."); +"Return a new Struct object which writes and reads binary data according\n" +"to the format string. See help(struct) for more on format strings."); static int Struct___init___impl(PyStructObject *self, PyObject *format); @@ -77,10 +75,8 @@ PyDoc_STRVAR(Struct_unpack__doc__, "\n" "Return a tuple containing unpacked values.\n" "\n" -"Unpack according to the format string Struct.format. The buffer\'s size\n" -"in bytes must be Struct.size.\n" -"\n" -"See help(struct) for more on format strings."); +"Unpack according to the format self. The buffer\'s size in bytes must be\n" +"self.size. See help(struct) for more on format strings."); #define STRUCT_UNPACK_METHODDEF \ {"unpack", (PyCFunction)Struct_unpack, METH_O, Struct_unpack__doc__}, @@ -114,12 +110,9 @@ PyDoc_STRVAR(Struct_unpack_from__doc__, "\n" "Return a tuple containing unpacked values.\n" "\n" -"Values are unpacked according to the format string Struct.format.\n" -"\n" -"The buffer\'s size in bytes, starting at position offset, must be\n" -"at least Struct.size.\n" -"\n" -"See help(struct) for more on format strings."); +"Values are unpacked according to the format self. The buffer\'s size in\n" +"bytes, starting at position offset, must be at least self.size. See\n" +"help(struct) for more on format strings."); #define STRUCT_UNPACK_FROM_METHODDEF \ {"unpack_from", _PyCFunction_CAST(Struct_unpack_from), METH_FASTCALL|METH_KEYWORDS, Struct_unpack_from__doc__}, @@ -206,9 +199,8 @@ PyDoc_STRVAR(Struct_iter_unpack__doc__, "Return an iterator yielding tuples.\n" "\n" "Tuples are unpacked from the given bytes source, like a repeated\n" -"invocation of unpack_from().\n" -"\n" -"Requires that the bytes length be a multiple of the struct size."); +"invocation of self.unpack_from(). Requires that the bytes length be a\n" +"multiple of the self.size."); #define STRUCT_ITER_UNPACK_METHODDEF \ {"iter_unpack", (PyCFunction)Struct_iter_unpack, METH_O, Struct_iter_unpack__doc__}, @@ -226,6 +218,161 @@ Struct_iter_unpack(PyObject *self, PyObject *buffer) return return_value; } +PyDoc_STRVAR(s_pack__doc__, +"pack($self, /, *args)\n" +"--\n" +"\n" +"Return a bytes object with args, packed according the format self.\n" +"\n" +"See help(struct) for more on format strings."); + +#define S_PACK_METHODDEF \ + {"pack", _PyCFunction_CAST(s_pack), METH_FASTCALL, s_pack__doc__}, + +static PyObject * +s_pack_impl(PyStructObject *self, PyObject * const *args, + Py_ssize_t args_length); + +static PyObject * +s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject * const *__clinic_args; + Py_ssize_t args_length; + + __clinic_args = args; + args_length = nargs; + return_value = s_pack_impl((PyStructObject *)self, __clinic_args, args_length); + + return return_value; +} + +PyDoc_STRVAR(s_pack_into__doc__, +"pack_into($self, buffer, offset, /, *args)\n" +"--\n" +"\n" +"Pack args to the writtable buffer according to the format self.\n" +"\n" +"The packed bytes written starting at offset. See help(struct) for more\n" +"on format strings."); + +#define S_PACK_INTO_METHODDEF \ + {"pack_into", _PyCFunction_CAST(s_pack_into), METH_FASTCALL, s_pack_into__doc__}, + +static PyObject * +s_pack_into_impl(PyStructObject *self, Py_buffer *buffer, Py_ssize_t offset, + PyObject * const *args, Py_ssize_t args_length); + +static PyObject * +s_pack_into(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_buffer buffer = {NULL, NULL}; + Py_ssize_t offset; + PyObject * const *__clinic_args; + Py_ssize_t args_length; + + if (!_PyArg_CheckPositional("pack_into", nargs, 2, PY_SSIZE_T_MAX)) { + goto exit; + } + if (PyObject_GetBuffer(args[0], &buffer, PyBUF_SIMPLE) != 0) { + goto exit; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[1]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + offset = ival; + } + __clinic_args = args + 2; + args_length = nargs - 2; + return_value = s_pack_into_impl((PyStructObject *)self, &buffer, offset, __clinic_args, args_length); + +exit: + /* Cleanup for buffer */ + if (buffer.obj) { + PyBuffer_Release(&buffer); + } + + return return_value; +} + +PyDoc_STRVAR(s_get_format__doc__, +"Struct format string."); +#if defined(s_get_format_DOCSTR) +# undef s_get_format_DOCSTR +#endif +#define s_get_format_DOCSTR s_get_format__doc__ + +#if !defined(s_get_format_DOCSTR) +# define s_get_format_DOCSTR NULL +#endif +#if defined(S_GET_FORMAT_GETSETDEF) +# undef S_GET_FORMAT_GETSETDEF +# define S_GET_FORMAT_GETSETDEF {"format", (getter)s_get_format_get, (setter)s_get_format_set, s_get_format_DOCSTR}, +#else +# define S_GET_FORMAT_GETSETDEF {"format", (getter)s_get_format_get, NULL, s_get_format_DOCSTR}, +#endif + +static PyObject * +s_get_format_get_impl(PyStructObject *self); + +static PyObject * +s_get_format_get(PyObject *self, void *Py_UNUSED(context)) +{ + return s_get_format_get_impl((PyStructObject *)self); +} + +PyDoc_STRVAR(s_get_size__doc__, +"Struct size in bytes."); +#if defined(s_get_size_DOCSTR) +# undef s_get_size_DOCSTR +#endif +#define s_get_size_DOCSTR s_get_size__doc__ + +#if !defined(s_get_size_DOCSTR) +# define s_get_size_DOCSTR NULL +#endif +#if defined(S_GET_SIZE_GETSETDEF) +# undef S_GET_SIZE_GETSETDEF +# define S_GET_SIZE_GETSETDEF {"size", (getter)s_get_size_get, (setter)s_get_size_set, s_get_size_DOCSTR}, +#else +# define S_GET_SIZE_GETSETDEF {"size", (getter)s_get_size_get, NULL, s_get_size_DOCSTR}, +#endif + +static PyObject * +s_get_size_get_impl(PyStructObject *self); + +static PyObject * +s_get_size_get(PyObject *self, void *Py_UNUSED(context)) +{ + return s_get_size_get_impl((PyStructObject *)self); +} + +PyDoc_STRVAR(s_sizeof__doc__, +"__sizeof__($self, /)\n" +"--\n" +"\n" +"Size of the self in memory, in bytes."); + +#define S_SIZEOF_METHODDEF \ + {"__sizeof__", (PyCFunction)s_sizeof, METH_NOARGS, s_sizeof__doc__}, + +static PyObject * +s_sizeof_impl(PyStructObject *self); + +static PyObject * +s_sizeof(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return s_sizeof_impl((PyStructObject *)self); +} + PyDoc_STRVAR(_clearcache__doc__, "_clearcache($module, /)\n" "--\n" @@ -279,15 +426,117 @@ calcsize(PyObject *module, PyObject *arg) return return_value; } +PyDoc_STRVAR(pack__doc__, +"pack($module, format, /, *args)\n" +"--\n" +"\n" +"Return a bytes object with args, packed according the format string.\n" +"\n" +"See help(struct) for more on format strings."); + +#define PACK_METHODDEF \ + {"pack", _PyCFunction_CAST(pack), METH_FASTCALL, pack__doc__}, + +static PyObject * +pack_impl(PyObject *module, PyStructObject *s_object, PyObject * const *args, + Py_ssize_t args_length); + +static PyObject * +pack(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyStructObject *s_object = NULL; + PyObject * const *__clinic_args; + Py_ssize_t args_length; + + if (!_PyArg_CheckPositional("pack", nargs, 1, PY_SSIZE_T_MAX)) { + goto exit; + } + if (!cache_struct_converter(module, args[0], &s_object)) { + goto exit; + } + __clinic_args = args + 1; + args_length = nargs - 1; + return_value = pack_impl(module, s_object, __clinic_args, args_length); + +exit: + /* Cleanup for s_object */ + Py_XDECREF(s_object); + + return return_value; +} + +PyDoc_STRVAR(pack_into__doc__, +"pack_into($module, format, buffer, offset, /, *args)\n" +"--\n" +"\n" +"Pack args to the writtable buffer according to the format string.\n" +"\n" +"The packed bytes written starting at offset. See help(struct) for more\n" +"on format strings."); + +#define PACK_INTO_METHODDEF \ + {"pack_into", _PyCFunction_CAST(pack_into), METH_FASTCALL, pack_into__doc__}, + +static PyObject * +pack_into_impl(PyObject *module, PyStructObject *s_object, Py_buffer *buffer, + Py_ssize_t offset, PyObject * const *args, + Py_ssize_t args_length); + +static PyObject * +pack_into(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyStructObject *s_object = NULL; + Py_buffer buffer = {NULL, NULL}; + Py_ssize_t offset; + PyObject * const *__clinic_args; + Py_ssize_t args_length; + + if (!_PyArg_CheckPositional("pack_into", nargs, 3, PY_SSIZE_T_MAX)) { + goto exit; + } + if (!cache_struct_converter(module, args[0], &s_object)) { + goto exit; + } + if (PyObject_GetBuffer(args[1], &buffer, PyBUF_SIMPLE) != 0) { + goto exit; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[2]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + offset = ival; + } + __clinic_args = args + 3; + args_length = nargs - 3; + return_value = pack_into_impl(module, s_object, &buffer, offset, __clinic_args, args_length); + +exit: + /* Cleanup for s_object */ + Py_XDECREF(s_object); + /* Cleanup for buffer */ + if (buffer.obj) { + PyBuffer_Release(&buffer); + } + + return return_value; +} + PyDoc_STRVAR(unpack__doc__, "unpack($module, format, buffer, /)\n" "--\n" "\n" "Return a tuple containing values unpacked according to the format string.\n" "\n" -"The buffer\'s size in bytes must be calcsize(format).\n" -"\n" -"See help(struct) for more on format strings."); +"The buffer\'s size in bytes must be calcsize(format). See help(struct)\n" +"for more on format strings."); #define UNPACK_METHODDEF \ {"unpack", _PyCFunction_CAST(unpack), METH_FASTCALL, unpack__doc__}, @@ -330,9 +579,8 @@ PyDoc_STRVAR(unpack_from__doc__, "\n" "Return a tuple containing values unpacked according to the format string.\n" "\n" -"The buffer\'s size, minus offset, must be at least calcsize(format).\n" -"\n" -"See help(struct) for more on format strings."); +"The buffer\'s size, minus offset, must be at least calcsize(format). See\n" +"help(struct) for more on format strings."); #define UNPACK_FROM_METHODDEF \ {"unpack_from", _PyCFunction_CAST(unpack_from), METH_FASTCALL|METH_KEYWORDS, unpack_from__doc__}, @@ -424,10 +672,10 @@ PyDoc_STRVAR(iter_unpack__doc__, "\n" "Return an iterator yielding tuples unpacked from the given bytes.\n" "\n" -"The bytes are unpacked according to the format string, like\n" -"a repeated invocation of unpack_from().\n" -"\n" -"Requires that the bytes length be a multiple of the format struct size."); +"The bytes are unpacked according to the format string, like a repeated\n" +"invocation of unpack_from(). Requires that the bytes length be a\n" +"multiple of the format struct size. See help(struct) for more on format\n" +"strings."); #define ITER_UNPACK_METHODDEF \ {"iter_unpack", _PyCFunction_CAST(iter_unpack), METH_FASTCALL, iter_unpack__doc__}, @@ -458,4 +706,4 @@ iter_unpack(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } -/*[clinic end generated code: output=caa7f36443e91cb9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ab8ff8415c7556a9 input=a9049054013a1b77]*/ From 5716dba4654d526944e47b2aefcc1f4e56c1ee29 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 12 Jan 2026 03:07:17 +0300 Subject: [PATCH 2/6] +1 --- Modules/_struct.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index fc2b9772bd9559..cf9cd970be0544 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -2243,7 +2243,7 @@ s_pack_impl(PyStructObject *self, PyObject * const *args, _structmodulestate *state = get_struct_state_structinst(self); /* Validate arguments. */ - ENSURE_STRUCT_IS_READY(soself); + ENSURE_STRUCT_IS_READY(self); assert(PyStruct_Check(self, state)); if (args_length != self->s_len) { PyErr_Format(state->StructError, @@ -2290,7 +2290,7 @@ s_pack_into_impl(PyStructObject *self, Py_buffer *buffer, Py_ssize_t offset, _structmodulestate *state = get_struct_state_structinst(self); /* Validate arguments. +1 is for the first arg as buffer. */ - ENSURE_STRUCT_IS_READY(soself); + ENSURE_STRUCT_IS_READY(self); assert(PyStruct_Check(self, state)); if (args_length != self->s_len) { PyErr_Format(state->StructError, From df0f7c49e12f61ca972f0fec068ed845de343774 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 13:36:19 +0300 Subject: [PATCH 3/6] Change Struct's docstring & add test in test_inspect.py Co-authored-by: Serhiy Storchaka --- Lib/test/test_inspect/test_inspect.py | 4 ++++ Modules/_struct.c | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 075e1802bebc3e..b25414bea659b7 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -6266,6 +6266,10 @@ def test_stat_module_has_signatures(self): import stat self._test_module_has_signatures(stat) + def test_struct_module_has_signatures(self): + import struct + self._test_module_has_signatures(struct) + def test_string_module_has_signatures(self): import string self._test_module_has_signatures(string) diff --git a/Modules/_struct.c b/Modules/_struct.c index cf9cd970be0544..caa0c58ff2e300 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -2440,17 +2440,12 @@ static PyGetSetDef s_getsetlist[] = { {NULL} /* sentinel */ }; -PyDoc_STRVAR(s__doc__, -"Struct(fmt) --> compiled struct object\n" -"\n" -); - static PyType_Slot PyStructType_slots[] = { {Py_tp_dealloc, s_dealloc}, {Py_tp_getattro, PyObject_GenericGetAttr}, {Py_tp_setattro, PyObject_GenericSetAttr}, {Py_tp_repr, s_repr}, - {Py_tp_doc, (void*)s__doc__}, + {Py_tp_doc, (void*)Struct___init____doc__}, {Py_tp_traverse, s_traverse}, {Py_tp_clear, s_clear}, {Py_tp_methods, s_methods}, From 76907cf7fddcc5482de5cbd48dd0b3e7f9a9a55d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 14:47:17 +0300 Subject: [PATCH 4/6] + simplify s_pack_internal() --- Modules/_struct.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index caa0c58ff2e300..1d4a14faeff21d 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -2142,7 +2142,7 @@ Struct_iter_unpack_impl(PyStructObject *self, PyObject *buffer) * */ static int -s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset, +s_pack_internal(PyStructObject *soself, PyObject *const *args, char* buf, _structmodulestate *state) { formatcode *code; @@ -2151,7 +2151,7 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset, Py_ssize_t i; memset(buf, '\0', soself->s_size); - i = offset; + i = 0; for (code = soself->s_codes; code->fmtdef != NULL; code++) { const formatdef *e = code->fmtdef; char *res = buf + code->offset; @@ -2260,7 +2260,7 @@ s_pack_impl(PyStructObject *self, PyObject * const *args, char *buf = PyBytesWriter_GetData(writer); /* Call the guts */ - if ( s_pack_internal(self, args, 0, buf, state) != 0 ) { + if (s_pack_internal(self, args, buf, state) != 0) { PyBytesWriter_Discard(writer); return NULL; } @@ -2344,8 +2344,7 @@ s_pack_into_impl(PyStructObject *self, Py_buffer *buffer, Py_ssize_t offset, } /* Call the guts */ - if (s_pack_internal(self, args, 0, - (char*)buffer->buf + offset, state) != 0) + if (s_pack_internal(self, args, (char*)buffer->buf + offset, state) != 0) { PyBuffer_Release(buffer); return NULL; From 5e4947a8a6b515d72c9d3c1ab9f593e27d6f0a5e Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 15:09:22 +0300 Subject: [PATCH 5/6] args -> values and request a writable buffer --- Modules/_struct.c | 28 ++++++++++++++-------------- Modules/clinic/_struct.c.h | 24 +++++++++++++----------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index 1d4a14faeff21d..0fab79f6073123 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -2228,9 +2228,9 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args, /*[clinic input] Struct.pack as s_pack - *args: array + *values as args: array -Return a bytes object with args, packed according the format self. +Return a bytes object with values, packed according the format self. See help(struct) for more on format strings. [clinic start generated code]*/ @@ -2238,7 +2238,7 @@ See help(struct) for more on format strings. static PyObject * s_pack_impl(PyStructObject *self, PyObject * const *args, Py_ssize_t args_length) -/*[clinic end generated code: output=dc06e32695719fce input=24c5c3c677d4d7e2]*/ +/*[clinic end generated code: output=dc06e32695719fce input=ef2ef79bdfff3584]*/ { _structmodulestate *state = get_struct_state_structinst(self); @@ -2271,12 +2271,12 @@ s_pack_impl(PyStructObject *self, PyObject * const *args, /*[clinic input] Struct.pack_into as s_pack_into - buffer: Py_buffer + buffer: Py_buffer(accept={rwbuffer}) offset: Py_ssize_t / - *args: array + *values as args: array -Pack args to the writtable buffer according to the format self. +Pack values to the writtable buffer according to the format self. The packed bytes written starting at offset. See help(struct) for more on format strings. @@ -2285,7 +2285,7 @@ on format strings. static PyObject * s_pack_into_impl(PyStructObject *self, Py_buffer *buffer, Py_ssize_t offset, PyObject * const *args, Py_ssize_t args_length) -/*[clinic end generated code: output=40e08ea63f1d57bd input=687fd92ef694b362]*/ +/*[clinic end generated code: output=40e08ea63f1d57bd input=c6540e61893bb142]*/ { _structmodulestate *state = get_struct_state_structinst(self); @@ -2539,9 +2539,9 @@ pack format as s_object: cache_struct / - *args: array + *values as args: array -Return a bytes object with args, packed according the format string. +Return a bytes object with values, packed according the format string. See help(struct) for more on format strings. [clinic start generated code]*/ @@ -2549,7 +2549,7 @@ See help(struct) for more on format strings. static PyObject * pack_impl(PyObject *module, PyStructObject *s_object, PyObject * const *args, Py_ssize_t args_length) -/*[clinic end generated code: output=3c490d3313013a77 input=7ad2023f64935c39]*/ +/*[clinic end generated code: output=3c490d3313013a77 input=ca22c200fceadd40]*/ { return s_pack_impl(s_object, args, args_length); } @@ -2558,12 +2558,12 @@ pack_impl(PyObject *module, PyStructObject *s_object, PyObject * const *args, pack_into format as s_object: cache_struct - buffer: Py_buffer + buffer: Py_buffer(accept={rwbuffer}) offset: Py_ssize_t / - *args: array + *values as args: array -Pack args to the writtable buffer according to the format string. +Pack values to the writtable buffer according to the format string. The packed bytes written starting at offset. See help(struct) for more on format strings. @@ -2573,7 +2573,7 @@ static PyObject * pack_into_impl(PyObject *module, PyStructObject *s_object, Py_buffer *buffer, Py_ssize_t offset, PyObject * const *args, Py_ssize_t args_length) -/*[clinic end generated code: output=86bd2d8d0628b540 input=4ace36b845e3167e]*/ +/*[clinic end generated code: output=86bd2d8d0628b540 input=4b6c16fe7a0b81be]*/ { return s_pack_into_impl(s_object, buffer, offset, args, args_length); } diff --git a/Modules/clinic/_struct.c.h b/Modules/clinic/_struct.c.h index eb29998a6a1e39..f0acfd6af4b2ce 100644 --- a/Modules/clinic/_struct.c.h +++ b/Modules/clinic/_struct.c.h @@ -219,10 +219,10 @@ Struct_iter_unpack(PyObject *self, PyObject *buffer) } PyDoc_STRVAR(s_pack__doc__, -"pack($self, /, *args)\n" +"pack($self, /, *values)\n" "--\n" "\n" -"Return a bytes object with args, packed according the format self.\n" +"Return a bytes object with values, packed according the format self.\n" "\n" "See help(struct) for more on format strings."); @@ -248,10 +248,10 @@ s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(s_pack_into__doc__, -"pack_into($self, buffer, offset, /, *args)\n" +"pack_into($self, buffer, offset, /, *values)\n" "--\n" "\n" -"Pack args to the writtable buffer according to the format self.\n" +"Pack values to the writtable buffer according to the format self.\n" "\n" "The packed bytes written starting at offset. See help(struct) for more\n" "on format strings."); @@ -275,7 +275,8 @@ s_pack_into(PyObject *self, PyObject *const *args, Py_ssize_t nargs) if (!_PyArg_CheckPositional("pack_into", nargs, 2, PY_SSIZE_T_MAX)) { goto exit; } - if (PyObject_GetBuffer(args[0], &buffer, PyBUF_SIMPLE) != 0) { + if (PyObject_GetBuffer(args[0], &buffer, PyBUF_WRITABLE) < 0) { + _PyArg_BadArgument("pack_into", "argument 1", "read-write bytes-like object", args[0]); goto exit; } { @@ -427,10 +428,10 @@ calcsize(PyObject *module, PyObject *arg) } PyDoc_STRVAR(pack__doc__, -"pack($module, format, /, *args)\n" +"pack($module, format, /, *values)\n" "--\n" "\n" -"Return a bytes object with args, packed according the format string.\n" +"Return a bytes object with values, packed according the format string.\n" "\n" "See help(struct) for more on format strings."); @@ -467,10 +468,10 @@ pack(PyObject *module, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(pack_into__doc__, -"pack_into($module, format, buffer, offset, /, *args)\n" +"pack_into($module, format, buffer, offset, /, *values)\n" "--\n" "\n" -"Pack args to the writtable buffer according to the format string.\n" +"Pack values to the writtable buffer according to the format string.\n" "\n" "The packed bytes written starting at offset. See help(struct) for more\n" "on format strings."); @@ -499,7 +500,8 @@ pack_into(PyObject *module, PyObject *const *args, Py_ssize_t nargs) if (!cache_struct_converter(module, args[0], &s_object)) { goto exit; } - if (PyObject_GetBuffer(args[1], &buffer, PyBUF_SIMPLE) != 0) { + if (PyObject_GetBuffer(args[1], &buffer, PyBUF_WRITABLE) < 0) { + _PyArg_BadArgument("pack_into", "argument 2", "read-write bytes-like object", args[1]); goto exit; } { @@ -706,4 +708,4 @@ iter_unpack(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } -/*[clinic end generated code: output=ab8ff8415c7556a9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=86e67d1398bd81b8 input=a9049054013a1b77]*/ From 4063a99ffff632ea8d0752180d8d3cf752d702a7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 15 Jan 2026 15:20:04 +0300 Subject: [PATCH 6/6] + tests for read-only buffers --- Lib/test/test_struct.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 88662fec60fe4a..526ec8e8953f13 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -461,6 +461,9 @@ def test_pack_into(self): self.assertRaises((TypeError, struct.error), struct.pack_into, b'', sb, None) + # Just a wrong buf. + self.assertRaises(TypeError, s.pack_into, "ЫЫЫ", 0, b'gravitsappa') + def test_pack_into_fn(self): test_string = b'Reykjavik rocks, eow!' writable_buf = array.array('b', b' '*100) @@ -484,6 +487,9 @@ def test_pack_into_fn(self): self.assertRaises((ValueError, struct.error), pack_into, small_buf, 2, test_string) + # Just a wrong buf. + self.assertRaises(TypeError, pack_into, "ЫЫЫ", 0, b'pepelats') + def test_unpack_with_buffer(self): # SF bug 1563759: struct.unpack doesn't support buffer protocol objects data1 = array.array('B', b'\x12\x34\x56\x78')