Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
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
3 changes: 2 additions & 1 deletion Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6295,7 +6295,8 @@ def test_stat_module_has_signatures(self):

def test_struct_module_has_signatures(self):
import struct
self._test_module_has_signatures(struct)
unsupported_signature = {'Struct'}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a regression. You can use @text_signature to set a fake signature.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm,

$ python3.14
Python 3.14.3 (tags/v3.14.3:323c59a5e34, Feb 11 2026, 08:44:26) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import inspect, struct
>>> inspect.signature(struct.Struct)
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    inspect.signature(struct.Struct)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.14/inspect.py", line 3322, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   globals=globals, locals=locals, eval_str=eval_str,
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   annotation_format=annotation_format)
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.14/inspect.py", line 3037, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
                                    follow_wrapper_chains=follow_wrapped,
                                    globals=globals, locals=locals, eval_str=eval_str,
                                    annotation_format=annotation_format)
  File "/usr/local/lib/python3.14/inspect.py", line 2596, in _signature_from_callable
    raise ValueError(
        'no signature found for builtin type {!r}'.format(obj))
ValueError: no signature found for builtin type <class '_struct.Struct'>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it works in main after conversion to Argument Clinic. See #143672.

self._test_module_has_signatures(struct, unsupported_signature=unsupported_signature)

def test_string_module_has_signatures(self):
import string
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.
101 changes: 67 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,68 @@ 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]
Struct.__init__

format: object
format: object = NULL

Create a compiled struct object.

Expand All @@ -1798,32 +1837,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=6275ff3f85752dd7]*/
{
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 +2473,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
13 changes: 9 additions & 4 deletions Modules/clinic/_struct.c.h

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

Loading