Skip to content
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:`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.
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 @@ -1167,6 +1167,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
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 @@ -6268,7 +6268,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'}
self._test_module_has_signatures(struct, unsupported_signature=unsupported_signature)

def test_string_module_has_signatures(self):
import string
Expand Down
15 changes: 11 additions & 4 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,11 +582,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 @@ -790,7 +795,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):
Expand Down Expand Up @@ -824,7 +830,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.
98 changes: 65 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 @@ -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.

Expand All @@ -1790,32 +1828,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 && 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
Expand Down Expand Up @@ -2437,9 +2471,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