# Struct instance. This test can be used to detect the leak
# when running with regrtest -L.
s = struct.Struct('>h')
- s.__init__('>hh')
+ msg = 'Re-initialization .* will not work'
+ with self.assertWarnsRegex(FutureWarning, msg):
+ s.__init__('>hh')
self.assertEqual(s.format, '>hh')
packed = b'\x00\x01\x00\x02'
self.assertEqual(s.pack(1, 2), packed)
self.assertEqual(s.unpack(packed), (1, 2))
- with self.assertRaises(UnicodeEncodeError):
- s.__init__('\udc00')
+ s.__init__('>hh') # same format
self.assertEqual(s.format, '>hh')
self.assertEqual(s.pack(1, 2), packed)
self.assertEqual(s.unpack(packed), (1, 2))
- with self.assertRaises(struct.error):
- s.__init__('$')
+ with self.assertWarnsRegex(FutureWarning, msg):
+ with self.assertRaises(UnicodeEncodeError):
+ s.__init__('\udc00')
+ self.assertEqual(s.format, '>hh')
+ self.assertEqual(s.pack(1, 2), packed)
+ self.assertEqual(s.unpack(packed), (1, 2))
+
+ with self.assertWarnsRegex(FutureWarning, msg):
+ with self.assertRaises(struct.error):
+ s.__init__('$')
self.assertEqual(s.format, '>hh')
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('2n3P?0P')
# 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)
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('\udc00')
+ self.assertEqual(my_struct.pack(12345), b'\x30\x39')
+ with self.assertWarnsRegex(DeprecationWarning, warnmsg + ".*can't encode"):
+ my_struct = MyStruct(format='\udc00')
+ 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')
+
+ for format in '>h', '<h', 42, '$', '\u20ac', '\udc00', b'\xa4':
+ with self.subTest(format=format):
+ my_struct = MyStruct(format)
+ self.assertEqual(my_struct.format, '>h')
+ self.assertEqual(my_struct.pack(12345), b'\x30\x39')
+ my_struct = MyStruct(format='<h')
+ self.assertEqual(my_struct.format, '>h')
+ self.assertEqual(my_struct.pack(12345), b'\x30\x39')
my_struct = MyStruct()
+ self.assertEqual(my_struct.format, '>h')
+ self.assertEqual(my_struct.pack(12345), b'\x30\x39')
+ my_struct = MyStruct('<h', 42)
+ self.assertEqual(my_struct.format, '>h')
+ 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.format, '>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.assertWarns(FutureWarning):
+ with self.assertRaises(TypeError):
+ MyStruct(('>h',), (42,))
+ with self.assertRaises(struct.error):
+ MyStruct(('$',), ('>h',))
+ with self.assertWarns(FutureWarning):
+ with self.assertRaises(struct.error):
+ MyStruct(('>h',), ('$',))
+ with self.assertRaises(UnicodeEncodeError):
+ MyStruct(('\udc00',), ('>h',))
+ with self.assertWarns(FutureWarning):
+ with self.assertRaises(UnicodeEncodeError):
+ MyStruct(('>h',), ('\udc00',))
+ with self.assertWarns(FutureWarning):
+ my_struct = MyStruct(('>h',), ('<h',))
+ self.assertEqual(my_struct.format, '<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.format, '>h')
+ self.assertEqual(my_struct.pack(12345), b'\x30\x39')
+ my_struct = MyStruct(format='>h')
+ self.assertEqual(my_struct.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('\udc00')
+ 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')
formatcode *s_codes;
PyObject *s_format;
PyObject *weakreflist; /* List of weak references */
+ bool init_called;
} PyStructObject;
#define PyStructObject_CAST(op) ((PyStructObject *)(op))
return -1;
}
+/* This should be moved to Struct_impl() when Struct___init__() and
+ * s_new() will be removed (see gh-143715 and gh-94532). */
+static int
+set_format(PyStructObject *self, PyObject *format)
+{
+ if (PyUnicode_Check(format)) {
+ format = PyUnicode_AsASCIIString(format);
+ if (format == NULL)
+ return -1;
+ }
+ else if (PyBytes_Check(format)) {
+ Py_INCREF(format);
+ }
+ else {
+ PyErr_Format(PyExc_TypeError,
+ "Struct() argument 1 must be a str or bytes object, "
+ "not %T", format);
+ return -1;
+ }
+ if (prepare_s(self, format)) {
+ Py_DECREF(format);
+ return -1;
+ }
+ Py_DECREF(format);
+ return 0;
+}
+
+/*[clinic input]
+@classmethod
+Struct.__new__
+
+ format: object
+
+Create a compiled struct object.
+
+Return a new Struct object which writes and reads binary data according
+to the format string. See help(struct) for more on format strings.
+[clinic start generated code]*/
+
+static PyObject *
+Struct_impl(PyTypeObject *type, PyObject *format)
+/*[clinic end generated code: output=49468b044e334308 input=8381a9796f20f24e]*/
+{
+ PyStructObject *self = (PyStructObject *)type->tp_alloc(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 (set_format(self, format) < 0) {
+ Py_DECREF(self);
+ return NULL;
+ }
+ return (PyObject *)self;
+}
+
+
+static int
+s_init(PyObject *self, PyObject *args, PyObject *kwargs);
+
static PyObject *
s_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
- PyObject *self;
+ if (type->tp_new != s_new) {
+ /* Struct.__new__() was called explicitly in a subclass' __new__(). */
+ return Struct(type, args, kwds);
+ }
- assert(type != NULL);
- allocfunc alloc_func = PyType_GetSlot(type, Py_tp_alloc);
- assert(alloc_func != NULL);
+ PyObject *format = NULL;
+ if (PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || PyDict_GET_SIZE(kwds) == 0)) {
+ format = Py_NewRef(PyTuple_GET_ITEM(args, 0));
+ }
+ else if (PyTuple_GET_SIZE(args) == 0 && kwds != NULL && PyDict_GET_SIZE(kwds) == 1) {
+ if (PyDict_GetItemStringRef(kwds, "format", &format) < 0) {
+ return NULL;
+ }
+ }
+ if (format == NULL && type->tp_init != s_init) {
+ Py_ssize_t nargs = PyTuple_GET_SIZE(args) + (kwds ? PyDict_GET_SIZE(kwds) : 0);
+ if (nargs > 1) {
+ if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
+ "Struct() takes at most 1 argument (%zd given)", nargs))
+ {
+ Py_XDECREF(format);
+ return NULL;
+ }
+ }
+ else {
+ if (PyErr_WarnEx(PyExc_DeprecationWarning,
+ "Struct() missing required argument 'format' (pos 1)", 1))
+ {
+ Py_XDECREF(format);
+ return 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;
+ PyStructObject *self = (PyStructObject *)type->tp_alloc(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 (format && set_format(self, format) < 0) {
+ if (type->tp_init == s_init) {
+ /* No custom __init__() method, so __new__() should do
+ * all the work. */
+ Py_DECREF(format);
+ Py_DECREF(self);
+ return NULL;
+ }
+ PyObject *exc = PyErr_GetRaisedException();
+ Py_SETREF(self->s_format, Py_NewRef(Py_None));
+ if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
+ "Invalid 'format' argument for Struct.__new__(): %S", exc))
+ {
+ Py_DECREF(exc);
+ Py_DECREF(format);
+ Py_DECREF(self);
+ return NULL;
+ }
+ Py_DECREF(exc);
+ }
+ Py_XDECREF(format);
+ return (PyObject *)self;
+}
+
+static bool
+same_format(PyStructObject *s, PyObject *format)
+{
+ Py_ssize_t size = PyBytes_GET_SIZE(s->s_format);
+ const void *data = PyBytes_AS_STRING(s->s_format);
+ if (PyUnicode_Check(format) && PyUnicode_IS_ASCII(format)) {
+ return PyUnicode_GET_LENGTH(format) == size
+ && memcmp(PyUnicode_1BYTE_DATA(format), data, size) == 0;
+ }
+ if (PyBytes_Check(format)) {
+ return PyBytes_GET_SIZE(format) == size
+ && memcmp(PyBytes_AS_STRING(format), data, size) == 0;
}
- return self;
+ return false;
}
/*[clinic input]
Struct___init___impl(PyStructObject *self, PyObject *format)
/*[clinic end generated code: output=b8e80862444e92d0 input=1af78a5f57d82cec]*/
{
- int ret = 0;
-
- if (PyUnicode_Check(format)) {
- format = PyUnicode_AsASCIIString(format);
- if (format == NULL)
+ if (self->s_format == Py_None) {
+ if (set_format(self, format) < 0) {
return -1;
+ }
}
- else {
- Py_INCREF(format);
+ else if (!same_format(self, format)) {
+ const char *msg = self->init_called
+ ? "Re-initialization of Struct by calling the __init__() method "
+ "will not work in future Python versions"
+ : "Different format arguments for __new__() and __init__() "
+ "methods of Struct";
+ if (PyErr_WarnEx(PyExc_FutureWarning, msg, 1)) {
+ return -1;
+ }
+ if (set_format(self, format) < 0) {
+ 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)));
- return -1;
+static int
+s_init(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ if (!((PyStructObject *)self)->init_called
+ && Py_TYPE(self)->tp_init == s_init
+ && ((PyStructObject *)self)->s_format != Py_None)
+ {
+ /* Struct.__init__() was called implicitly.
+ * __new__() already did all the work. */
+ ((PyStructObject *)self)->init_called = true;
+ return 0;
}
-
- ret = prepare_s(self, format);
- Py_DECREF(format);
- return ret;
+ /* Struct.__init__() was called explicitly. */
+ return Struct___init__(self, args, kwargs);
}
static int
{Py_tp_methods, s_methods},
{Py_tp_members, s_members},
{Py_tp_getset, s_getsetlist},
- {Py_tp_init, Struct___init__},
- {Py_tp_alloc, PyType_GenericAlloc},
+ {Py_tp_init, s_init},
{Py_tp_new, s_new},
- {Py_tp_free, PyObject_GC_Del},
{0, 0},
};