Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 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
09d6ebd
+test new idiom
skirpichev Feb 27, 2026
7f0a133
Some cleanup; also add reference to issue and reverted PR
skirpichev Feb 28, 2026
26cde89
Merge branch 'master' into deprecate-struct-init-2/78724
skirpichev Feb 28, 2026
7697820
issue DeprecationWarning for implicit tp_new calls in subclasses
skirpichev Feb 28, 2026
8a1fa9a
Apply suggestions from code review
skirpichev Mar 2, 2026
d9ed032
address review: fix byte order in test_Struct_reinitialization()
skirpichev Mar 2, 2026
309229c
address review: PEP 7
skirpichev Mar 2, 2026
d90dfc2
address review: test format attribute instead of pack()
skirpichev Mar 2, 2026
e1d3b7d
Update Modules/_struct.c
skirpichev Mar 2, 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
43 changes: 39 additions & 4 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,11 +585,13 @@ 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')

def check_sizeof(self, format_str, number_of_codes):
# The size of 'PyStructObject'
totalsize = support.calcobjsize('2n3P')
totalsize = support.calcobjsize('2n3P?')
# 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,8 +795,40 @@ class MyStruct(struct.Struct):
def __init__(self):
super().__init__('>h')

with self.assertWarns(DeprecationWarning):
my_struct = MyStruct()
self.assertEqual(my_struct.format, '>h')

# New way, no warnings:
class MyStruct(struct.Struct):
def __new__(cls):
return super().__new__(cls, '>h')
my_struct = MyStruct()
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
self.assertEqual(my_struct.format, '>h')

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

my_struct = MyStruct(5)
self.assertEqual(my_struct.format, '>h')

class MyStruct(struct.Struct):
def __init__(self, *args, **kwargs):
super().__init__('>h')

with self.assertWarns(DeprecationWarning):
my_struct = MyStruct('<h')
self.assertEqual(my_struct.format, '>h')

with self.assertWarns(DeprecationWarning):
my_struct = MyStruct(5)
self.assertEqual(my_struct.format, '>h')

with self.assertWarns(DeprecationWarning):
my_struct = MyStruct('>h')
self.assertEqual(my_struct.format, '>h')

def test_repr(self):
s = struct.Struct('=i2H')
Expand Down Expand Up @@ -827,7 +861,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 @@
:mod:`struct`: 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.
124 changes: 91 additions & 33 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,88 @@ prepare_s(PyStructObject *self)
return -1;
}

/* This should be moved to s_new() when Struct___init__() will
be removed (see gh-143715 and gh-94532). */
static int
s_create(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;
PyObject *mod = PyType_GetModuleByDef(type, &_structmodule);
_structmodulestate *state = PyModule_GetState(mod);
PyStructObject *self;
bool implicit_new_call = s_new == PyType_GetSlot(type, Py_tp_new);
bool in_subclass = type != (PyTypeObject *)state->PyStructType;

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;
}
return self;
self->s_format = 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 (PyErr_WarnEx(PyExc_DeprecationWarning,
"Struct.__new__() has one positional argument", 1)) {
goto err;
}
}
else {
if (implicit_new_call && in_subclass) {
if (PyErr_WarnEx(PyExc_DeprecationWarning,
("Creation of half-initialized Struct() objects"
" is deprecated, use Struct.__new__(cls, format)"), 1))
{
goto err;
}
}
else {
PyObject *format = PyTuple_GET_ITEM(args, 0);
if (s_create(self, format)) {
goto err;
}
}
}
return (PyObject *)self;
err:
Py_DECREF(self);
return NULL;
}

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

format: object
format: object = NULL

Create a compiled struct object.

Expand All @@ -1798,32 +1857,33 @@ 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;
bool explicit_init_call = (Struct___init__
!= PyType_GetSlot(Py_TYPE(self), Py_tp_init));

if (PyUnicode_Check(format)) {
format = PyUnicode_AsASCIIString(format);
if (format == NULL)
if (!format && !self->s_codes) {
PyErr_SetString(PyExc_TypeError,
"Struct() missing required argument 'format' (pos 1)");
return -1;
}
if (self->init_called) {
if (self->s_codes
&& PyErr_WarnEx(PyExc_DeprecationWarning,
("Explicit call of __init__() on "
"initialized Struct() is deprecated"), 1))
{
return -1;
}
return s_create(self, format);
}
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;
if (explicit_init_call && s_create(self, format)) {
return -1;
}
self->init_called = true;
return 0;
}

Py_SETREF(self->s_format, format);

ret = prepare_s(self);
return ret;
}

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