From 95288d93c7e7f2c94aaff2d14894d6cb7e1eb410 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 3 Mar 2021 19:04:53 +0000 Subject: [PATCH 1/4] bpo-42128: Add __match_args__ to structseq-based classes --- Lib/test/test_structseq.py | 8 ++++++ .../2021-03-03-19-04-23.bpo-42128.VouZjn.rst | 2 ++ Objects/structseq.c | 26 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-03-03-19-04-23.bpo-42128.VouZjn.rst diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py index 36630a17367fff..fb0c848ca9da14 100644 --- a/Lib/test/test_structseq.py +++ b/Lib/test/test_structseq.py @@ -122,5 +122,13 @@ def test_extended_getslice(self): self.assertEqual(list(t[start:stop:step]), L[start:stop:step]) + def test_match_args(self): + t = time.gmtime() + expected_args = ('tm_year', 'tm_mon', 'tm_mday', 'tm_hour', 'tm_min', + 'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst', 'tm_zone', + 'tm_gmtoff') + self.assertEqual(t.__match_args__, expected_args) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-03-03-19-04-23.bpo-42128.VouZjn.rst b/Misc/NEWS.d/next/Core and Builtins/2021-03-03-19-04-23.bpo-42128.VouZjn.rst new file mode 100644 index 00000000000000..7c4733a9b0d70d --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-03-03-19-04-23.bpo-42128.VouZjn.rst @@ -0,0 +1,2 @@ +Add ``__match_args__`` to :c:type:`structsequence` based classes. Patch by +Pablo Galindo. diff --git a/Objects/structseq.c b/Objects/structseq.c index 4222afa599c809..0a0df4f236d851 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -15,6 +15,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__"; /* Fields with this name have only a field index, not a field name. They are only allowed for indices < n_visible_fields. */ @@ -399,6 +400,31 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, SET_DICT_FROM_SIZE(visible_length_key, desc->n_in_sequence); SET_DICT_FROM_SIZE(real_length_key, n_members); SET_DICT_FROM_SIZE(unnamed_fields_key, n_unnamed_members); + + // Prepare and set __match_args__ + PyObject* keys = PyTuple_New(n_members - n_unnamed_members); + if (keys == NULL) { + return -1; + } + + Py_ssize_t i, k; + for (i = k = 0; i < n_members; ++i) { + if (desc->fields[i].name == PyStructSequence_UnnamedField) { + continue; + } + PyObject* new_member = PyUnicode_FromString(desc->fields[i].name); + if (new_member == NULL) { + Py_DECREF(keys); + return -1; \ + } + PyTuple_SET_ITEM(keys, k, new_member); + k++; + } + if (PyDict_SetItemString(dict, match_args_key, keys) < 0) { + Py_DECREF(keys); + return -1; + } + Py_DECREF(keys); return 0; } From 40bf3b7454775b0b75a650f4509444ffa56b102d Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 3 Mar 2021 19:26:33 +0000 Subject: [PATCH 2/4] Add a test for unnamed fields --- Lib/test/test_structseq.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py index fb0c848ca9da14..57571fd8b05d97 100644 --- a/Lib/test/test_structseq.py +++ b/Lib/test/test_structseq.py @@ -123,11 +123,18 @@ def test_extended_getslice(self): L[start:stop:step]) def test_match_args(self): - t = time.gmtime() expected_args = ('tm_year', 'tm_mon', 'tm_mday', 'tm_hour', 'tm_min', 'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst', 'tm_zone', 'tm_gmtoff') - self.assertEqual(t.__match_args__, expected_args) + self.assertEqual(time.struct_time.__match_args__, expected_args) + + def test_match_args_with_unnamed_fields(self): + expected_args = ('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid', + 'st_gid', 'st_size', 'st_atime', 'st_mtime', 'st_ctime', + 'st_atime_ns', 'st_mtime_ns', 'st_ctime_ns', 'st_blksize', + 'st_blocks', 'st_rdev') + self.assertEqual(os.stat_result.n_unnamed_fields, 3) + self.assertEqual(os.stat_result.__match_args__, expected_args) if __name__ == "__main__": From fca240c8112ef79c47110bea6c10c7bee860f4ae Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 3 Mar 2021 19:52:50 +0000 Subject: [PATCH 3/4] Restrict the __match_args__ fields to the visible ones --- Lib/test/test_structseq.py | 7 ++----- Objects/structseq.c | 13 ++++++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py index 57571fd8b05d97..a9fe193028ebe4 100644 --- a/Lib/test/test_structseq.py +++ b/Lib/test/test_structseq.py @@ -124,15 +124,12 @@ def test_extended_getslice(self): def test_match_args(self): expected_args = ('tm_year', 'tm_mon', 'tm_mday', 'tm_hour', 'tm_min', - 'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst', 'tm_zone', - 'tm_gmtoff') + 'tm_sec', 'tm_wday', 'tm_yday', 'tm_isdst') self.assertEqual(time.struct_time.__match_args__, expected_args) def test_match_args_with_unnamed_fields(self): expected_args = ('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid', - 'st_gid', 'st_size', 'st_atime', 'st_mtime', 'st_ctime', - 'st_atime_ns', 'st_mtime_ns', 'st_ctime_ns', 'st_blksize', - 'st_blocks', 'st_rdev') + 'st_gid', 'st_size') self.assertEqual(os.stat_result.n_unnamed_fields, 3) self.assertEqual(os.stat_result.__match_args__, expected_args) diff --git a/Objects/structseq.c b/Objects/structseq.c index 0a0df4f236d851..1889412f7a29cd 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -402,13 +402,20 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, SET_DICT_FROM_SIZE(unnamed_fields_key, n_unnamed_members); // Prepare and set __match_args__ - PyObject* keys = PyTuple_New(n_members - n_unnamed_members); + Py_ssize_t i, k; + Py_ssize_t n_match_args = 0; + for (i = k = 0; i < desc->n_in_sequence; ++i) { + if (desc->fields[i].name == PyStructSequence_UnnamedField) { + continue; + } + n_match_args++; + } + PyObject* keys = PyTuple_New(n_match_args); if (keys == NULL) { return -1; } - Py_ssize_t i, k; - for (i = k = 0; i < n_members; ++i) { + for (i = k = 0; i < desc->n_in_sequence; ++i) { if (desc->fields[i].name == PyStructSequence_UnnamedField) { continue; } From b41c2f0117636ef46d91880c48500e2b6287fac4 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 3 Mar 2021 22:54:31 +0000 Subject: [PATCH 4/4] Use _PyTuple_Resize instead of iterating twice --- Objects/structseq.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 1889412f7a29cd..8a92bdbec08d1c 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -403,14 +403,7 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, // Prepare and set __match_args__ Py_ssize_t i, k; - Py_ssize_t n_match_args = 0; - for (i = k = 0; i < desc->n_in_sequence; ++i) { - if (desc->fields[i].name == PyStructSequence_UnnamedField) { - continue; - } - n_match_args++; - } - PyObject* keys = PyTuple_New(n_match_args); + PyObject* keys = PyTuple_New(desc->n_in_sequence); if (keys == NULL) { return -1; } @@ -421,18 +414,26 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, } PyObject* new_member = PyUnicode_FromString(desc->fields[i].name); if (new_member == NULL) { - Py_DECREF(keys); - return -1; \ + goto error; } PyTuple_SET_ITEM(keys, k, new_member); k++; } + + if (_PyTuple_Resize(&keys, k) == -1) { + goto error; + } + if (PyDict_SetItemString(dict, match_args_key, keys) < 0) { - Py_DECREF(keys); - return -1; + goto error; } + Py_DECREF(keys); return 0; + +error: + Py_DECREF(keys); + return -1; } static void