diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index 4e4b2e1d5f8fff..1907758e8847d8 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -1,6 +1,13 @@ Pending removal in Python 3.20 ------------------------------ +* Calling the ``Struct.__new__()`` without required argument now is + deprecated and will be removed in Python 3.20. Calling + :meth:`~object.__init__` method on initialized :class:`~struct.Struct` + objects is deprecated and will be removed in Python 3.20. + + (Contributed by Sergey B Kirpichev in :gh:`78724`.) + * The ``__version__``, ``version`` and ``VERSION`` attributes have been deprecated in these standard library modules and will be removed in Python 3.20. Use :py:data:`sys.version_info` instead. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 95549d5fa7b2b4..29f329fded5f75 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1140,6 +1140,15 @@ New deprecations (Contributed by Bénédikt Tran in :gh:`134978`.) +* :mod:`struct`: + + * Calling the ``Struct.__new__()`` without required argument now is + deprecated and will be removed in Python 3.20. Calling + :meth:`~object.__init__` method on initialized :class:`~struct.Struct` + objects is deprecated and will be removed in Python 3.20. + + (Contributed by Sergey B Kirpichev in :gh:`78724`.) + * ``__version__`` * The ``__version__``, ``version`` and ``VERSION`` attributes have been diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 88662fec60fe4a..b4379ccd9eb7ce 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -575,11 +575,16 @@ def test_Struct_reinitialization(self): # Struct instance. This test can be used to detect the leak # when running with regrtest -L. s = struct.Struct('i') - s.__init__('ii') + with self.assertWarns(DeprecationWarning): + s.__init__('ii') + self.assertEqual(s.format, 'ii') + packed = b'\x01\x00\x00\x00\x02\x00\x00\x00' + self.assertEqual(s.pack(1, 2), packed) + self.assertEqual(s.unpack(packed), (1, 2)) def check_sizeof(self, format_str, number_of_codes): # The size of 'PyStructObject' - totalsize = support.calcobjsize('2n3P') + totalsize = support.calcobjsize('2n3P1?') # The size taken up by the 'formatcode' dynamic array totalsize += struct.calcsize('P3n0P') * (number_of_codes + 1) support.check_sizeof(self, struct.Struct(format_str), totalsize) @@ -783,7 +788,8 @@ class MyStruct(struct.Struct): def __init__(self): super().__init__('>h') - my_struct = MyStruct() + with self.assertWarns(DeprecationWarning): + my_struct = MyStruct() self.assertEqual(my_struct.pack(12345), b'\x30\x39') def test_repr(self): @@ -817,7 +823,8 @@ def test_endian_table_init_subinterpreters(self): self.assertListEqual(list(results), [None] * 5) def test_operations_on_half_initialized_Struct(self): - S = struct.Struct.__new__(struct.Struct) + with self.assertWarns(DeprecationWarning): + S = struct.Struct.__new__(struct.Struct) spam = array.array('b', b' ') self.assertRaises(RuntimeError, S.iter_unpack, spam) diff --git a/Misc/NEWS.d/next/Library/2026-01-10-16-23-21.gh-issue-78724.HZrfSA.rst b/Misc/NEWS.d/next/Library/2026-01-10-16-23-21.gh-issue-78724.HZrfSA.rst new file mode 100644 index 00000000000000..93c3f6f74f207b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-10-16-23-21.gh-issue-78724.HZrfSA.rst @@ -0,0 +1,3 @@ +Calling the ``Struct.__new__()`` without required argument now is deprecated. +Calling :meth:`~object.__init__` method on initialized :class:`~struct.Struct` +objects is deprecated. Patch by Sergey B Kirpichev. diff --git a/Modules/_struct.c b/Modules/_struct.c index a8e9021f0a303a..0a72ab53dac520 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -70,6 +70,7 @@ typedef struct { formatcode *s_codes; PyObject *s_format; PyObject *weakreflist; /* List of weak references */ + bool init_called; } PyStructObject; #define PyStructObject_CAST(op) ((PyStructObject *)(op)) @@ -1757,30 +1758,67 @@ prepare_s(PyStructObject *self) return -1; } +static int +actual___init___impl(PyStructObject *self, PyObject *format) +{ + if (PyUnicode_Check(format)) { + format = PyUnicode_AsASCIIString(format); + if (format == NULL) + return -1; + } + else { + Py_INCREF(format); + } + if (!PyBytes_Check(format)) { + Py_DECREF(format); + PyErr_Format(PyExc_TypeError, + "Struct() argument 1 must be a str or bytes object, " + "not %.200s", + _PyType_Name(Py_TYPE(format))); + return -1; + } + Py_SETREF(self->s_format, format); + if (prepare_s(self)) { + return -1; + } + return 0; +} + static PyObject * s_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { - PyObject *self; + PyStructObject *self; + if ((PyTuple_GET_SIZE(args) != 1 || kwds) + && PyErr_WarnEx(PyExc_DeprecationWarning, + "Struct.__new__() has one positional argument", 1)) + { + return NULL; + } assert(type != NULL); allocfunc alloc_func = PyType_GetSlot(type, Py_tp_alloc); assert(alloc_func != NULL); - - self = alloc_func(type, 0); + self = (PyStructObject *)alloc_func(type, 0); if (self != NULL) { - PyStructObject *s = (PyStructObject*)self; - s->s_format = Py_NewRef(Py_None); - s->s_codes = NULL; - s->s_size = -1; - s->s_len = -1; + self->s_format = Py_NewRef(Py_None); + self->s_codes = NULL; + self->s_size = -1; + self->s_len = -1; + self->init_called = false; + if (PyTuple_GET_SIZE(args) > 0) { + if (actual___init___impl(self, PyTuple_GET_ITEM(args, 0))) { + Py_DECREF(self); + return NULL; + } + } } - return self; + return (PyObject *)self; } /*[clinic input] Struct.__init__ - format: object + format: object = NULL Create a compiled struct object. @@ -1792,32 +1830,28 @@ See help(struct) for more on format strings. static int Struct___init___impl(PyStructObject *self, PyObject *format) -/*[clinic end generated code: output=b8e80862444e92d0 input=192a4575a3dde802]*/ +/*[clinic end generated code: output=b8e80862444e92d0 input=14845875ad162992]*/ { - int ret = 0; - - if (PyUnicode_Check(format)) { - format = PyUnicode_AsASCIIString(format); - if (format == NULL) - return -1; + if (!format && !self->s_codes) { + PyErr_SetString(PyExc_TypeError, + "Struct() missing required argument 'format' (pos 1)"); + return -1; } - else { - Py_INCREF(format); + if (!self->init_called) { + if (!self->s_codes && actual___init___impl(self, format)) { + return -1; + } + self->init_called = true; + return 0; } - - if (!PyBytes_Check(format)) { - Py_DECREF(format); - PyErr_Format(PyExc_TypeError, - "Struct() argument 1 must be a str or bytes object, " - "not %.200s", - _PyType_Name(Py_TYPE(format))); + if ((self->s_codes && self->init_called) + && PyErr_WarnEx(PyExc_DeprecationWarning, + ("Explicit call of __init__() on " + "initialized Struct() is deprecated"), 1)) + { return -1; } - - Py_SETREF(self->s_format, format); - - ret = prepare_s(self); - return ret; + return actual___init___impl(self, format); } static int @@ -2460,9 +2494,7 @@ static PyType_Slot PyStructType_slots[] = { {Py_tp_members, s_members}, {Py_tp_getset, s_getsetlist}, {Py_tp_init, Struct___init__}, - {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_new, s_new}, - {Py_tp_free, PyObject_GC_Del}, {0, 0}, }; diff --git a/Modules/clinic/_struct.c.h b/Modules/clinic/_struct.c.h index e4eaadb91eb231..83ba6fdb5d0db9 100644 --- a/Modules/clinic/_struct.c.h +++ b/Modules/clinic/_struct.c.h @@ -10,7 +10,7 @@ preserve #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() PyDoc_STRVAR(Struct___init____doc__, -"Struct(format)\n" +"Struct(format=)\n" "--\n" "\n" "Create a compiled struct object.\n" @@ -57,14 +57,19 @@ Struct___init__(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *argsbuf[1]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); - PyObject *format; + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + PyObject *format = NULL; fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, - /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); if (!fastargs) { goto exit; } + if (!noptargs) { + goto skip_optional_pos; + } format = fastargs[0]; +skip_optional_pos: return_value = Struct___init___impl((PyStructObject *)self, format); exit: @@ -458,4 +463,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=29bd81cd67adda38 input=a9049054013a1b77]*/