Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 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
05eb292
Fix more corner cases.
serhiy-storchaka Mar 6, 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
149 changes: 145 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 @@ -787,14 +792,150 @@ def test_error_propagation(fmt_str):
test_error_propagation('N')
test_error_propagation('n')

def test_struct_subclass_instantiation(self):
def test_custom_struct_init(self):
# Regression test for https://github.com/python/cpython/issues/112358
class MyStruct(struct.Struct):
def __init__(self):
def __init__(self, *args, **kwargs):
super().__init__('>h')

my_struct = MyStruct('>h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct(format='>h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')

warnmsg = r"Different format arguments for __new__\(\) and __init__\(\) methods of Struct"
with self.assertWarnsRegex(FutureWarning, warnmsg):
my_struct = MyStruct('<h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(FutureWarning, warnmsg):
my_struct = MyStruct(format='<h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')

warnmsg = r"Struct\(\) missing required argument 'format' \(pos 1\)"
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
my_struct = MyStruct()
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
my_struct = MyStruct(arg='>h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')

warnmsg = r"Struct\(\) takes at most 1 argument \(2 given\)"
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
my_struct = MyStruct('>h', 42)
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
my_struct = MyStruct('>h', arg=42)
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
my_struct = MyStruct('>h', format=42)
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
my_struct = MyStruct(format='>h', arg=42)
self.assertEqual(my_struct.pack(12345), b'\x30\x39')

warnmsg = r"Invalid 'format' argument for Struct\.__new__\(\): "
with self.assertWarnsRegex(DeprecationWarning, warnmsg + '.*must be'):
my_struct = MyStruct(42)
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(DeprecationWarning, warnmsg + '.*must be'):
my_struct = MyStruct(format=42)
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(DeprecationWarning, warnmsg + 'bad char'):
my_struct = MyStruct('$')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(DeprecationWarning, warnmsg + 'bad char'):
my_struct = MyStruct(format='$')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(DeprecationWarning, warnmsg + ".*can't encode"):
my_struct = MyStruct('\u20ac')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertWarnsRegex(DeprecationWarning, warnmsg + ".*can't encode"):
my_struct = MyStruct(format='\u20ac')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')

def test_custom_struct_new(self):
# New way, no warnings:
class MyStruct(struct.Struct):
def __new__(cls, *args, **kwargs):
return super().__new__(cls, '>h')

my_struct = MyStruct('>h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct('<h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct(format='<h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct()
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct(42)
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct('$')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct('\u20ac')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct('<h', 42)
self.assertEqual(my_struct.pack(12345), b'\x30\x39')

def test_custom_struct_new_and_init(self):
# New way, no warnings:
class MyStruct(struct.Struct):
def __new__(cls, newargs, initargs):
return super().__new__(cls, *newargs)
def __init__(self, newargs, initargs):
if initargs is not None:
super().__init__(*initargs)

my_struct = MyStruct(('>h',), ('>h',))
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertRaises(TypeError):
MyStruct((), ())
with self.assertRaises(TypeError):
MyStruct(('>h',), ())
with self.assertRaises(TypeError):
MyStruct((), ('>h',))
with self.assertRaises(TypeError):
MyStruct((42,), ('>h',))
with self.assertRaises(TypeError):
with self.assertWarns(FutureWarning):
MyStruct(('>h',), (42,))
with self.assertRaises(struct.error):
MyStruct(('$',), ('>h',))
with self.assertRaises(struct.error):
with self.assertWarns(FutureWarning):
MyStruct(('>h',), ('$',))
with self.assertRaises(UnicodeEncodeError):
MyStruct(('\u20ac',), ('>h',))
with self.assertRaises(UnicodeEncodeError):
with self.assertWarns(FutureWarning):
MyStruct(('>h',), ('\u20ac',))
with self.assertWarns(FutureWarning):
my_struct = MyStruct(('>h',), ('<h',))
self.assertEqual(my_struct.pack(12345), b'\x39\x30')

def test_no_custom_struct_new_or_init(self):
class MyStruct(struct.Struct):
pass

my_struct = MyStruct('>h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct(format='>h')
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
with self.assertRaises(TypeError):
MyStruct()
with self.assertRaises(TypeError):
MyStruct(42)
with self.assertRaises(struct.error):
MyStruct('$')
with self.assertRaises(UnicodeEncodeError):
MyStruct('\u20ac')
with self.assertRaises(TypeError):
MyStruct('>h', 42)
with self.assertRaises(TypeError):
MyStruct('>h', arg=42)
with self.assertRaises(TypeError):
MyStruct(arg=42)
with self.assertRaises(TypeError):
MyStruct('>h', format='>h')

def test_repr(self):
s = struct.Struct('=i2H')
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.
Loading
Loading