Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fff147f
gh-78724: deprecate incomplete initialization of struct.Struct()
skirpichev Jan 10, 2026
d2a0345
Merge branch 'master' into deprecate-struct-init-2/78724
skirpichev Jan 12, 2026
589fbc7
address review: test_Struct_reinitialization()
skirpichev Jan 12, 2026
5e91e87
catch new warning in test_operations_on_half_initialized_Struct()
skirpichev Jan 12, 2026
babb274
add init_called flag
skirpichev Jan 12, 2026
0e4e84b
Merge branch 'master' into deprecate-struct-init-2/78724
skirpichev Jan 13, 2026
3f36635
a hack to support new idiom for subclassing
skirpichev Jan 13, 2026
e576475
Merge branch 'master' into deprecate-struct-init-2/78724
skirpichev Jan 17, 2026
628aadd
+ filter out Struct signature test
skirpichev Jan 17, 2026
979cc18
Update Modules/_struct.c
skirpichev Jan 19, 2026
f7492ec
Merge branch 'master' into deprecate-struct-init-2/78724
skirpichev Jan 19, 2026
db8f5f2
address review: reformat s_new()
skirpichev Jan 19, 2026
dc8cbef
address review: actual___init___impl -> s_init
skirpichev Jan 19, 2026
6206ae1
Merge branch 'master' into deprecate-struct-init-2/78724
skirpichev Feb 22, 2026
12143d1
Update Doc/deprecations/pending-removal-in-3.20.rst
skirpichev Feb 25, 2026
d4ca6b8
Merge branch 'master' into deprecate-struct-init-2/78724
skirpichev Feb 25, 2026
6b9b4fb
+ rename news
skirpichev Feb 25, 2026
79961a2
Apply suggestions from code review
skirpichev Feb 25, 2026
58550c0
address review: add test
skirpichev Feb 25, 2026
c815882
address review: fix for format
skirpichev Feb 25, 2026
f1388ee
address review: reformat if blocks
skirpichev Feb 25, 2026
685a6c4
address review: s_new
skirpichev Feb 25, 2026
76f8723
Merge branch 'master' into deprecate-struct-init-2/78724
skirpichev Feb 26, 2026
538d301
address review: add fake signature for Struct
skirpichev Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Doc/deprecations/pending-removal-in-3.20.rst
Original file line number Diff line number Diff line change
@@ -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:`143715`.)

* 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.
Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,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:`143715`.)

* ``__version__``

* The ``__version__``, ``version`` and ``VERSION`` attributes have been
Expand Down
23 changes: 19 additions & 4 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,11 +585,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)
Expand Down Expand Up @@ -793,9 +798,18 @@ 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')

class MyStruct(struct.Struct):
def __new__(cls, arg):
self = super().__new__(cls, '>h')
return self

my_struct = MyStruct(5)
self.assertEqual(my_struct.pack(123), b'\x00{')

def test_repr(self):
s = struct.Struct('=i2H')
self.assertEqual(repr(s), f'Struct({s.format!r})')
Expand Down Expand Up @@ -827,7 +841,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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
102 changes: 68 additions & 34 deletions Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -1765,30 +1766,69 @@ prepare_s(PyStructObject *self)
return -1;
}

static int
s_init(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)) {
PyErr_Format(PyExc_TypeError,
"Struct() argument 1 must be a str or bytes object, "
"not %T",
format);
Py_DECREF(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) {
if (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);
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 = (PyStructObject *)alloc_func(type, 0);
if (self == NULL) {
return NULL;
}
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) == 1) {
if (s_init(self, PyTuple_GET_ITEM(args, 0))) {
Py_DECREF(self);
return NULL;
}
}
return self;
return (PyObject *)self;
}

/*[clinic input]
@text_signature "(format)"
Struct.__init__

format: object
format: object = NULL

Create a compiled struct object.

Expand All @@ -1798,32 +1838,28 @@ to the format string. See help(struct) for more on format strings.

static int
Struct___init___impl(PyStructObject *self, PyObject *format)
/*[clinic end generated code: output=b8e80862444e92d0 input=1af78a5f57d82cec]*/
/*[clinic end generated code: output=b8e80862444e92d0 input=dcf0b5a00eb0dbd9]*/
{
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 && s_init(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 s_init(self, format);
}

static int
Expand Down Expand Up @@ -2438,9 +2474,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},
};

Expand Down
11 changes: 8 additions & 3 deletions Modules/clinic/_struct.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading