Skip to content

Commit 7245630

Browse files
gh-143715: Deprecate incomplete initialization of struct.Struct() (GH-145580)
* Struct.__new__() will require a mandatory argument (format) * Calls of __init__() method with a different format argument on initialized Struct are deprecated Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com>
1 parent 0dce4c6 commit 7245630

File tree

6 files changed

+409
-42
lines changed

6 files changed

+409
-42
lines changed

Doc/deprecations/pending-removal-in-3.20.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
Pending removal in Python 3.20
22
------------------------------
33

4+
* Calling the ``__new__()`` method of :class:`struct.Struct` without the
5+
*format* argument is deprecated and will be removed in Python 3.20. Calling
6+
:meth:`~object.__init__` method on initialized :class:`~struct.Struct`
7+
objects is deprecated and will be removed in Python 3.20.
8+
9+
(Contributed by Sergey B Kirpichev and Serhiy Storchaka in :gh:`143715`.)
10+
411
* The ``__version__``, ``version`` and ``VERSION`` attributes have been
512
deprecated in these standard library modules and will be removed in
613
Python 3.20. Use :py:data:`sys.version_info` instead.

Doc/whatsnew/3.15.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,15 @@ New deprecations
15601560

15611561
(Contributed by Bénédikt Tran in :gh:`134978`.)
15621562

1563+
* :mod:`struct`:
1564+
1565+
* Calling the ``Struct.__new__()`` without required argument now is
1566+
deprecated and will be removed in Python 3.20. Calling
1567+
:meth:`~object.__init__` method on initialized :class:`~struct.Struct`
1568+
objects is deprecated and will be removed in Python 3.20.
1569+
1570+
(Contributed by Sergey B Kirpichev and Serhiy Storchaka in :gh:`143715`.)
1571+
15631572
* ``__version__``
15641573

15651574
* The ``__version__``, ``version`` and ``VERSION`` attributes have been

Lib/test/test_struct.py

Lines changed: 155 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -591,27 +591,36 @@ def test_Struct_reinitialization(self):
591591
# Struct instance. This test can be used to detect the leak
592592
# when running with regrtest -L.
593593
s = struct.Struct('>h')
594-
s.__init__('>hh')
594+
msg = 'Re-initialization .* will not work'
595+
with self.assertWarnsRegex(FutureWarning, msg):
596+
s.__init__('>hh')
595597
self.assertEqual(s.format, '>hh')
596598
packed = b'\x00\x01\x00\x02'
597599
self.assertEqual(s.pack(1, 2), packed)
598600
self.assertEqual(s.unpack(packed), (1, 2))
599601

600-
with self.assertRaises(UnicodeEncodeError):
601-
s.__init__('\udc00')
602+
s.__init__('>hh') # same format
602603
self.assertEqual(s.format, '>hh')
603604
self.assertEqual(s.pack(1, 2), packed)
604605
self.assertEqual(s.unpack(packed), (1, 2))
605606

606-
with self.assertRaises(struct.error):
607-
s.__init__('$')
607+
with self.assertWarnsRegex(FutureWarning, msg):
608+
with self.assertRaises(UnicodeEncodeError):
609+
s.__init__('\udc00')
610+
self.assertEqual(s.format, '>hh')
611+
self.assertEqual(s.pack(1, 2), packed)
612+
self.assertEqual(s.unpack(packed), (1, 2))
613+
614+
with self.assertWarnsRegex(FutureWarning, msg):
615+
with self.assertRaises(struct.error):
616+
s.__init__('$')
608617
self.assertEqual(s.format, '>hh')
609618
self.assertEqual(s.pack(1, 2), packed)
610619
self.assertEqual(s.unpack(packed), (1, 2))
611620

612621
def check_sizeof(self, format_str, number_of_codes):
613622
# The size of 'PyStructObject'
614-
totalsize = support.calcobjsize('2n3P')
623+
totalsize = support.calcobjsize('2n3P?0P')
615624
# The size taken up by the 'formatcode' dynamic array
616625
totalsize += struct.calcsize('P3n0P') * (number_of_codes + 1)
617626
support.check_sizeof(self, struct.Struct(format_str), totalsize)
@@ -809,14 +818,152 @@ def test_error_propagation(fmt_str):
809818
test_error_propagation('N')
810819
test_error_propagation('n')
811820

812-
def test_struct_subclass_instantiation(self):
821+
def test_custom_struct_init(self):
813822
# Regression test for https://github.com/python/cpython/issues/112358
814823
class MyStruct(struct.Struct):
815-
def __init__(self):
824+
def __init__(self, *args, **kwargs):
816825
super().__init__('>h')
817826

827+
my_struct = MyStruct('>h')
828+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
829+
my_struct = MyStruct(format='>h')
830+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
831+
832+
warnmsg = r"Different format arguments for __new__\(\) and __init__\(\) methods of Struct"
833+
with self.assertWarnsRegex(FutureWarning, warnmsg):
834+
my_struct = MyStruct('<h')
835+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
836+
with self.assertWarnsRegex(FutureWarning, warnmsg):
837+
my_struct = MyStruct(format='<h')
838+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
839+
840+
warnmsg = r"Struct\(\) missing required argument 'format' \(pos 1\)"
841+
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
842+
my_struct = MyStruct()
843+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
844+
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
845+
my_struct = MyStruct(arg='>h')
846+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
847+
848+
warnmsg = r"Struct\(\) takes at most 1 argument \(2 given\)"
849+
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
850+
my_struct = MyStruct('>h', 42)
851+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
852+
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
853+
my_struct = MyStruct('>h', arg=42)
854+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
855+
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
856+
my_struct = MyStruct('>h', format=42)
857+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
858+
with self.assertWarnsRegex(DeprecationWarning, warnmsg):
859+
my_struct = MyStruct(format='>h', arg=42)
860+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
861+
862+
warnmsg = r"Invalid 'format' argument for Struct\.__new__\(\): "
863+
with self.assertWarnsRegex(DeprecationWarning, warnmsg + '.*must be'):
864+
my_struct = MyStruct(42)
865+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
866+
with self.assertWarnsRegex(DeprecationWarning, warnmsg + '.*must be'):
867+
my_struct = MyStruct(format=42)
868+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
869+
with self.assertWarnsRegex(DeprecationWarning, warnmsg + 'bad char'):
870+
my_struct = MyStruct('$')
871+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
872+
with self.assertWarnsRegex(DeprecationWarning, warnmsg + 'bad char'):
873+
my_struct = MyStruct(format='$')
874+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
875+
with self.assertWarnsRegex(DeprecationWarning, warnmsg + ".*can't encode"):
876+
my_struct = MyStruct('\udc00')
877+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
878+
with self.assertWarnsRegex(DeprecationWarning, warnmsg + ".*can't encode"):
879+
my_struct = MyStruct(format='\udc00')
880+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
881+
882+
def test_custom_struct_new(self):
883+
# New way, no warnings:
884+
class MyStruct(struct.Struct):
885+
def __new__(cls, *args, **kwargs):
886+
return super().__new__(cls, '>h')
887+
888+
for format in '>h', '<h', 42, '$', '\u20ac', '\udc00', b'\xa4':
889+
with self.subTest(format=format):
890+
my_struct = MyStruct(format)
891+
self.assertEqual(my_struct.format, '>h')
892+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
893+
my_struct = MyStruct(format='<h')
894+
self.assertEqual(my_struct.format, '>h')
895+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
818896
my_struct = MyStruct()
897+
self.assertEqual(my_struct.format, '>h')
898+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
899+
my_struct = MyStruct('<h', 42)
900+
self.assertEqual(my_struct.format, '>h')
901+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
902+
903+
def test_custom_struct_new_and_init(self):
904+
# New way, no warnings:
905+
class MyStruct(struct.Struct):
906+
def __new__(cls, newargs, initargs):
907+
return super().__new__(cls, *newargs)
908+
def __init__(self, newargs, initargs):
909+
if initargs is not None:
910+
super().__init__(*initargs)
911+
912+
my_struct = MyStruct(('>h',), ('>h',))
913+
self.assertEqual(my_struct.format, '>h')
819914
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
915+
with self.assertRaises(TypeError):
916+
MyStruct((), ())
917+
with self.assertRaises(TypeError):
918+
MyStruct(('>h',), ())
919+
with self.assertRaises(TypeError):
920+
MyStruct((), ('>h',))
921+
with self.assertRaises(TypeError):
922+
MyStruct((42,), ('>h',))
923+
with self.assertWarns(FutureWarning):
924+
with self.assertRaises(TypeError):
925+
MyStruct(('>h',), (42,))
926+
with self.assertRaises(struct.error):
927+
MyStruct(('$',), ('>h',))
928+
with self.assertWarns(FutureWarning):
929+
with self.assertRaises(struct.error):
930+
MyStruct(('>h',), ('$',))
931+
with self.assertRaises(UnicodeEncodeError):
932+
MyStruct(('\udc00',), ('>h',))
933+
with self.assertWarns(FutureWarning):
934+
with self.assertRaises(UnicodeEncodeError):
935+
MyStruct(('>h',), ('\udc00',))
936+
with self.assertWarns(FutureWarning):
937+
my_struct = MyStruct(('>h',), ('<h',))
938+
self.assertEqual(my_struct.format, '<h')
939+
self.assertEqual(my_struct.pack(12345), b'\x39\x30')
940+
941+
def test_no_custom_struct_new_or_init(self):
942+
class MyStruct(struct.Struct):
943+
pass
944+
945+
my_struct = MyStruct('>h')
946+
self.assertEqual(my_struct.format, '>h')
947+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
948+
my_struct = MyStruct(format='>h')
949+
self.assertEqual(my_struct.format, '>h')
950+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
951+
with self.assertRaises(TypeError):
952+
MyStruct()
953+
with self.assertRaises(TypeError):
954+
MyStruct(42)
955+
with self.assertRaises(struct.error):
956+
MyStruct('$')
957+
with self.assertRaises(UnicodeEncodeError):
958+
MyStruct('\udc00')
959+
with self.assertRaises(TypeError):
960+
MyStruct('>h', 42)
961+
with self.assertRaises(TypeError):
962+
MyStruct('>h', arg=42)
963+
with self.assertRaises(TypeError):
964+
MyStruct(arg=42)
965+
with self.assertRaises(TypeError):
966+
MyStruct('>h', format='>h')
820967

821968
def test_repr(self):
822969
s = struct.Struct('=i2H')
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Calling the ``Struct.__new__()`` without required argument now is deprecated.
2+
Calling :meth:`~object.__init__` method on initialized :class:`~struct.Struct`
3+
objects is deprecated.

0 commit comments

Comments
 (0)