From 668b9e5f3dbc1ab5fe8cac0252b05c58025cc44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Tue, 29 Aug 2023 17:23:47 +0100 Subject: [PATCH 01/20] add _fields to PyStructSequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 6c07e6366293f9..da2f329af21495 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -18,6 +18,7 @@ static const char visible_length_key[] = "n_sequence_fields"; static const char real_length_key[] = "n_fields"; static const char unnamed_fields_key[] = "n_unnamed_fields"; static const char match_args_key[] = "__match_args__"; +static const char named_fields_list_key[] = "_fields"; /* Fields with this name have only a field index, not a field name. They are only allowed for indices < n_visible_fields. */ @@ -405,7 +406,7 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, SET_DICT_FROM_SIZE(real_length_key, n_members); SET_DICT_FROM_SIZE(unnamed_fields_key, n_unnamed_members); - // Prepare and set __match_args__ + // Prepare and set __match_args__ and _fields Py_ssize_t i, k; PyObject* keys = PyTuple_New(desc->n_in_sequence); if (keys == NULL) { @@ -432,6 +433,10 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, goto error; } + if (PyDict_SetItemString(dict, named_fields_list_key, keys) < 0) { + goto error; + } + Py_DECREF(keys); return 0; From 6ed9a8600d65b553f75f0d2a150634870c255164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Tue, 29 Aug 2023 17:25:19 +0100 Subject: [PATCH 02/20] add _asdict to PyStructSequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Objects/structseq.c b/Objects/structseq.c index da2f329af21495..3b6170599be288 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -366,8 +366,35 @@ structseq_reduce(PyStructSequence* self, PyObject *Py_UNUSED(ignored)) return NULL; } +static PyObject * +structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored)) +{ + PyObject* dict = NULL; + Py_ssize_t n_visible_fields, n_unnamed_fields, i; + + n_visible_fields = VISIBLE_SIZE(self); + n_unnamed_fields = UNNAMED_FIELDS(self); + + dict = PyDict_New(); + if (!dict) + return NULL; + + for (i = 0; i < n_visible_fields; i++) { + const char *n = Py_TYPE(self)->tp_members[i-n_unnamed_fields].name; + if (PyDict_SetItemString(dict, n, self->ob_item[i]) < 0) + goto error; + } + + return dict; + +error: + Py_XDECREF(dict); + return NULL; +} + static PyMethodDef structseq_methods[] = { {"__reduce__", (PyCFunction)structseq_reduce, METH_NOARGS, NULL}, + {"_asdict", (PyCFunction)structseq_asdict, METH_NOARGS, NULL}, {NULL, NULL} }; From 995a2d9a60d6b9f772d24b5526e3c8b9425d357c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Tue, 29 Aug 2023 17:27:08 +0100 Subject: [PATCH 03/20] add news MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- .../next/C API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/C API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst diff --git a/Misc/NEWS.d/next/C API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst b/Misc/NEWS.d/next/C API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst new file mode 100644 index 00000000000000..a04dd45939a46b --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst @@ -0,0 +1,2 @@ +Add :attr:`_fields` and :attr:`_asdict` helpers to :c:type:`PyStructSequence`, +improving its compatibility with :class:`collections.namedtuple`. From c7cf78b20bde75f6cc3dd25aaa3deec0241fc17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Tue, 29 Aug 2023 17:34:16 +0100 Subject: [PATCH 04/20] add _field_defaults to PyStructSequence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 3b6170599be288..bf1964c407cc64 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -19,6 +19,7 @@ static const char real_length_key[] = "n_fields"; static const char unnamed_fields_key[] = "n_unnamed_fields"; static const char match_args_key[] = "__match_args__"; static const char named_fields_list_key[] = "_fields"; +static const char named_fields_defaults_key[] = "_field_defaults"; /* Fields with this name have only a field index, not a field name. They are only allowed for indices < n_visible_fields. */ @@ -414,7 +415,7 @@ count_members(PyStructSequence_Desc *desc, Py_ssize_t *n_unnamed_members) { static int initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, Py_ssize_t n_members, Py_ssize_t n_unnamed_members) { - PyObject *v; + PyObject *v, *defaults = NULL; #define SET_DICT_FROM_SIZE(key, value) \ do { \ @@ -464,10 +465,21 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, goto error; } + // Set _fields_defaults to an empty dir, as we don't support defaults + defaults = PyDict_New(); + if (!defaults) + goto error; + + if (PyDict_SetItemString(dict, named_fields_defaults_key, defaults) < 0) { + goto error; + } + + Py_XDECREF(defaults); Py_DECREF(keys); return 0; error: + Py_XDECREF(defaults); Py_DECREF(keys); return -1; } From 76c0581b6464dd493a36a08babb326e6e87b9716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Tue, 29 Aug 2023 17:44:49 +0100 Subject: [PATCH 05/20] (from review) use Py_DECREF instead of Py_XDECREF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index bf1964c407cc64..83b884e924c63a 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -389,7 +389,7 @@ structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored)) return dict; error: - Py_XDECREF(dict); + Py_DECREF(dict); return NULL; } From 5c3e4a500b01f0df76551f6a57540ad9dd8f3a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Tue, 29 Aug 2023 17:58:03 +0100 Subject: [PATCH 06/20] add tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Lib/test/test_structseq.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py index a9fe193028ebe4..d265b56df21c6c 100644 --- a/Lib/test/test_structseq.py +++ b/Lib/test/test_structseq.py @@ -133,6 +133,28 @@ def test_match_args_with_unnamed_fields(self): self.assertEqual(os.stat_result.n_unnamed_fields, 3) self.assertEqual(os.stat_result.__match_args__, expected_args) + def test_tuple_field_keys(self): + expected_keys = ('tm_year', 'tm_mon', 'tm_mday', 'tm_hour', 'tm_min', + 'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst') + self.assertEqual(time.gmtime()._fields, time.struct_time._fields, expected_keys) + + def test_tuple_field_defaults(self): + self.assertEqual(time.gmtime()._field_defaults, time.struct_time._field_defaults, {}) + + def test_tuple_asdict(self): + t = time.gmtime(0) + self.assertEqual(t._asdict(), { + 'tm_year': 1970, + 'tm_mon': 1, + 'tm_mday': 1, + 'tm_hour': 0, + 'tm_min': 0, + 'tm_sec': 0, + 'tm_wday': 3, + 'tm_yday': 1, + 'tm_isdst': 0, + }) + if __name__ == "__main__": unittest.main() From 48b5c2a3d4937dc8865cd30f8553ac8a73eebce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Tue, 29 Aug 2023 17:59:53 +0100 Subject: [PATCH 07/20] use Py_DECREF instead of Py_XDECREF for defaults in initialize_structseq_dict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 83b884e924c63a..7e76c4205ca470 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -474,7 +474,7 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, goto error; } - Py_XDECREF(defaults); + Py_DECREF(defaults); Py_DECREF(keys); return 0; From 173546ce371c2b579bf4861feeb2a3022c191188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Wed, 30 Aug 2023 08:27:40 +0100 Subject: [PATCH 08/20] don't support namedtuple helpers when unnamed fields are present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Lib/test/test_structseq.py | 9 +++++++++ Modules/_testcapimodule.c | 22 ++++++++++++++++++++++ Objects/structseq.c | 32 +++++++++++++++++++++----------- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py index d265b56df21c6c..50972450f26c22 100644 --- a/Lib/test/test_structseq.py +++ b/Lib/test/test_structseq.py @@ -1,6 +1,7 @@ import os import time import unittest +import _testcapi class StructSeqTest(unittest.TestCase): @@ -155,6 +156,14 @@ def test_tuple_asdict(self): 'tm_isdst': 0, }) + def test_tuple_attributes_unnamed(self): + for key in ['_fields', '_field_defaults']: + self.assertNotIn(key, dir(_testcapi.MixedNamedTuple)) + + def test_asdict_unnamed(self): + t = _testcapi.MixedNamedTuple((0, 1)) + self.assertRaises(ValueError, t._asdict) + if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 20b96320f4c339..9f09d4dc8e95ba 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3946,6 +3946,20 @@ static struct PyModuleDef _testcapimodule = { NULL }; +static PyStructSequence_Field MixedNamedTuple_fields[] = { + {"something", "some named entry"}, + {NULL, "some unnamed entry"}, + {0} +}; + +static PyStructSequence_Desc MixedNamedTuple_desc = { + .name = "_testcpi.MixedNamedTuple", + .doc = PyDoc_STR("Example of a named tuple with both named and unnamed fields."), + .fields = MixedNamedTuple_fields, + .n_in_sequence = 2 +}; + + /* Per PEP 489, this module will not be converted to multi-phase initialization */ @@ -3953,6 +3967,7 @@ PyMODINIT_FUNC PyInit__testcapi(void) { PyObject *m; + PyTypeObject *type; m = PyModule_Create(&_testcapimodule); if (m == NULL) @@ -4047,6 +4062,13 @@ PyInit__testcapi(void) (PyObject *) &ContainerNoGC_type) < 0) return NULL; + MixedNamedTuple_desc.fields[1].name = PyStructSequence_UnnamedField; + type = PyStructSequence_NewType(&MixedNamedTuple_desc); + if (type == NULL) + return NULL; + if (PyModule_AddType(m, type) < 0) + return NULL; + /* Include tests from the _testcapi/ directory */ if (_PyTestCapi_Init_Vectorcall(m) < 0) { return NULL; diff --git a/Objects/structseq.c b/Objects/structseq.c index 7e76c4205ca470..08134ea0efc13b 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -376,6 +376,12 @@ structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored)) n_visible_fields = VISIBLE_SIZE(self); n_unnamed_fields = UNNAMED_FIELDS(self); + if (n_unnamed_fields != 0) { + PyErr_SetString(PyExc_ValueError, "named tuples with both named and " + "unnamed fields don't support _asdict"); + return NULL; + } + dict = PyDict_New(); if (!dict) return NULL; @@ -434,7 +440,7 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, SET_DICT_FROM_SIZE(real_length_key, n_members); SET_DICT_FROM_SIZE(unnamed_fields_key, n_unnamed_members); - // Prepare and set __match_args__ and _fields + // Prepare and set __match_args__ Py_ssize_t i, k; PyObject* keys = PyTuple_New(desc->n_in_sequence); if (keys == NULL) { @@ -461,20 +467,24 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, goto error; } - if (PyDict_SetItemString(dict, named_fields_list_key, keys) < 0) { - goto error; - } + // Set _field and _field_defaults when we have no unnammed members + if (n_unnamed_members == 0) { + if (PyDict_SetItemString(dict, named_fields_list_key, keys) < 0) { + goto error; + } - // Set _fields_defaults to an empty dir, as we don't support defaults - defaults = PyDict_New(); - if (!defaults) - goto error; + // Set _fields_defaults to an empty dir, as we don't support defaults + defaults = PyDict_New(); + if (!defaults) + goto error; - if (PyDict_SetItemString(dict, named_fields_defaults_key, defaults) < 0) { - goto error; + if (PyDict_SetItemString(dict, named_fields_defaults_key, defaults) < 0) { + goto error; + } + + Py_DECREF(defaults); } - Py_DECREF(defaults); Py_DECREF(keys); return 0; From c99456fef42a1c25c9498200e7b2a2b8e386d79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 04:32:45 +0000 Subject: [PATCH 09/20] Fix linting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- .../2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{C API => C_API}/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst (100%) diff --git a/Misc/NEWS.d/next/C API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst b/Misc/NEWS.d/next/C_API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst similarity index 100% rename from Misc/NEWS.d/next/C API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst rename to Misc/NEWS.d/next/C_API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst From 8881c96dcde81fa1d205ff223e95cc6f46f19204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 04:34:50 +0000 Subject: [PATCH 10/20] Fix inconsistent style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 803efbbcb9e01d..e7229e3d4fcc53 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -558,8 +558,9 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, // Set _fields_defaults to an empty dir, as we don't support defaults defaults = PyDict_New(); - if (!defaults) + if (!defaults) { goto error; + } if (PyDict_SetItemString(dict, named_fields_defaults_key, defaults) < 0) { goto error; From 0482b08f83c50a9e835a376e4402cabdf5d9e49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 04:39:26 +0000 Subject: [PATCH 11/20] Fix comment typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index e7229e3d4fcc53..49b370f93ae8ee 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -556,7 +556,7 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, goto error; } - // Set _fields_defaults to an empty dir, as we don't support defaults + // Set _fields_defaults to an empty dict, as we don't support defaults defaults = PyDict_New(); if (!defaults) { goto error; From df1bd45cfeefe1dad9830324eb6a4d442cf215e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 04:39:48 +0000 Subject: [PATCH 12/20] Join code that the comment is referring to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 49b370f93ae8ee..5a186944467b52 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -561,11 +561,9 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, if (!defaults) { goto error; } - if (PyDict_SetItemString(dict, named_fields_defaults_key, defaults) < 0) { goto error; } - Py_DECREF(defaults); } From 7ec1eb6ebc1ff7fe5819a900f1298e31bd31be23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 04:43:30 +0000 Subject: [PATCH 13/20] Simplify error description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 5a186944467b52..2fc0d21cb67132 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -457,8 +457,7 @@ structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored)) n_unnamed_fields = UNNAMED_FIELDS(self); if (n_unnamed_fields != 0) { - PyErr_SetString(PyExc_ValueError, "named tuples with both named and " - "unnamed fields don't support _asdict"); + PyErr_SetString(PyExc_ValueError, "named tuples with unnamed fields don't support _asdict"); return NULL; } From d6582334a29284b9b05eacd9aca775d85144d0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 04:44:29 +0000 Subject: [PATCH 14/20] Put _asdict after dunder methods in structseq_methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 2fc0d21cb67132..8129c8b119a0fd 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -480,10 +480,10 @@ structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored)) static PyMethodDef structseq_methods[] = { {"__reduce__", (PyCFunction)structseq_reduce, METH_NOARGS, NULL}, - {"_asdict", (PyCFunction)structseq_asdict, METH_NOARGS, NULL}, {"__replace__", _PyCFunction_CAST(structseq_replace), METH_VARARGS | METH_KEYWORDS, PyDoc_STR("__replace__($self, /, **changes)\n--\n\n" "Return a copy of the structure with new values for the specified fields.")}, + {"_asdict", (PyCFunction)structseq_asdict, METH_NOARGS, NULL}, {NULL, NULL} // sentinel }; From 81a07f29d9b89ae0a8001d06ff62a90007a3ff0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 04:46:13 +0000 Subject: [PATCH 15/20] Fix comment typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 8129c8b119a0fd..eec7601bbeba86 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -555,7 +555,7 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, goto error; } - // Set _fields_defaults to an empty dict, as we don't support defaults + // Set _field_defaults to an empty dict, as we don't support defaults defaults = PyDict_New(); if (!defaults) { goto error; From de300a670663fa7f46eb2e79a094eb259e0f500a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 08:51:24 +0000 Subject: [PATCH 16/20] Fix news doc references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Doc/c-api/tuple.rst | 4 ++++ .../next/C_API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index 815afddad19df1..09208cf38f3ccc 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -140,6 +140,10 @@ objects, i.e. a sequence whose items can also be accessed through attributes. To create a struct sequence, you first have to create a specific struct sequence type. +.. c:var:: PyTypeObject PyStructSequence + + This instance of :c:type:`PyTypeObject` represents the struct sequence type. + .. c:function:: PyTypeObject* PyStructSequence_NewType(PyStructSequence_Desc *desc) Create a new struct sequence type from the data in *desc*, described below. Instances diff --git a/Misc/NEWS.d/next/C_API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst b/Misc/NEWS.d/next/C_API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst index a04dd45939a46b..792f7bc74f70c8 100644 --- a/Misc/NEWS.d/next/C_API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst +++ b/Misc/NEWS.d/next/C_API/2023-08-29-17-27-01.gh-issue-108647.gyXLmO.rst @@ -1,2 +1,3 @@ -Add :attr:`_fields` and :attr:`_asdict` helpers to :c:type:`PyStructSequence`, +Add :attr:`collections.somenamedtuple._fields` and +:attr:`collections.somenamedtuple._asdict` helpers to :c:var:`PyStructSequence`, improving its compatibility with :class:`collections.namedtuple`. From c11f5441b8fd5e7c8b6c225fe64ef453c2be7e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 11:25:37 +0000 Subject: [PATCH 17/20] Sync structseq_asdict error message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index eec7601bbeba86..d937d5c43651c6 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -457,7 +457,10 @@ structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored)) n_unnamed_fields = UNNAMED_FIELDS(self); if (n_unnamed_fields != 0) { - PyErr_SetString(PyExc_ValueError, "named tuples with unnamed fields don't support _asdict"); + PyErr_Format(PyExc_TypeError, + "_asdict() is not supported for %.500s " + "because it has unnamed field(s)", + Py_TYPE(self)->tp_name); return NULL; } From 00ae6a8a514b36de6036976894716bf6c7d05382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 11:26:56 +0000 Subject: [PATCH 18/20] Add extra checks to _asdict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Objects/structseq.c b/Objects/structseq.c index d937d5c43651c6..d940fedb573166 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -454,7 +454,13 @@ structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored)) Py_ssize_t n_visible_fields, n_unnamed_fields, i; n_visible_fields = VISIBLE_SIZE(self); + if (n_visible_fields < 0) { + return NULL; + } n_unnamed_fields = UNNAMED_FIELDS(self); + if (n_unnamed_fields < 0) { + return NULL; + } if (n_unnamed_fields != 0) { PyErr_Format(PyExc_TypeError, From 7fc188557916ecb42cd2dc26e5fc73469b51374d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 12:21:11 +0000 Subject: [PATCH 19/20] Add _replace as an alias to __replace__ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/structseq.c b/Objects/structseq.c index d940fedb573166..814b8f4207520d 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -492,6 +492,7 @@ static PyMethodDef structseq_methods[] = { {"__replace__", _PyCFunction_CAST(structseq_replace), METH_VARARGS | METH_KEYWORDS, PyDoc_STR("__replace__($self, /, **changes)\n--\n\n" "Return a copy of the structure with new values for the specified fields.")}, + {"_replace", _PyCFunction_CAST(structseq_replace), METH_VARARGS | METH_KEYWORDS}, {"_asdict", (PyCFunction)structseq_asdict, METH_NOARGS, NULL}, {NULL, NULL} // sentinel }; From 684d2b2dc0e4362f2709366ed1e1a60ae9ce7257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Sun, 17 Nov 2024 12:21:44 +0000 Subject: [PATCH 20/20] Add structseq_make MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- Objects/structseq.c | 48 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Objects/structseq.c b/Objects/structseq.c index 814b8f4207520d..e816c8480dd454 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -487,6 +487,53 @@ structseq_asdict(PyStructSequence* self, PyObject *Py_UNUSED(ignored)) return NULL; } +static PyObject * +structseq_make(PyStructSequence *self, PyObject *iterable) +{ + Py_ssize_t field_index = 0; + PyStructSequence *result = NULL; + + Py_ssize_t n_fields = REAL_SIZE(self); + if (n_fields < 0) { + return NULL; + } + + PyObject *values = PySequence_List(iterable); + if (values == NULL) { + return NULL; + } + + Py_ssize_t values_len = PyList_Size(values); + if (values_len != n_fields) { + PyErr_Format(PyExc_TypeError, "Expected %d arguments, got %d", + n_fields, values_len); + goto error; + } + + result = (PyStructSequence *) PyStructSequence_New(Py_TYPE(self)); + if (!result) { + goto error; + } + + for (field_index = 0; field_index < n_fields; ++field_index) { + PyObject *item = PyList_GetItemRef(values, field_index); + if (item == NULL) { + goto error; + } + result->ob_item[field_index] = item; + } + + return (PyObject *)result; + +error: + for (Py_ssize_t i = 0; i < field_index; ++i) { + Py_DECREF(result->ob_item[i]); + } + Py_DECREF(values); + Py_XDECREF(result); + return NULL; +} + static PyMethodDef structseq_methods[] = { {"__reduce__", (PyCFunction)structseq_reduce, METH_NOARGS, NULL}, {"__replace__", _PyCFunction_CAST(structseq_replace), METH_VARARGS | METH_KEYWORDS, @@ -494,6 +541,7 @@ static PyMethodDef structseq_methods[] = { "Return a copy of the structure with new values for the specified fields.")}, {"_replace", _PyCFunction_CAST(structseq_replace), METH_VARARGS | METH_KEYWORDS}, {"_asdict", (PyCFunction)structseq_asdict, METH_NOARGS, NULL}, + {"_make", (PyCFunction)structseq_make, METH_O, NULL}, {NULL, NULL} // sentinel };