From 1eac463c1a29fc9a7f02bbed50fb7b43917876e8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 6 Sep 2025 13:54:41 +0300 Subject: [PATCH 1/2] gh-105487: Fix `__dir__` entries of `GenericAlias` --- Lib/test/test_genericalias.py | 27 ++++++++++++++++--- ...-09-06-13-53-33.gh-issue-105487.a43YaY.rst | 1 + Objects/genericaliasobject.c | 22 +++++++++++++-- 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 7601cb00ff6685..4e08adaca05cdd 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -402,7 +402,10 @@ def __deepcopy__(self, memo): aliases = [ GenericAlias(list, T), GenericAlias(deque, T), - GenericAlias(X, T) + GenericAlias(X, T), + X[T], + list[T], + deque[T], ] + _UNPACKED_TUPLES for alias in aliases: with self.subTest(alias=alias): @@ -432,10 +435,26 @@ def test_union_generic(self): self.assertEqual(a.__parameters__, (T,)) def test_dir(self): - dir_of_gen_alias = set(dir(list[int])) + ga = list[int] + dir_of_gen_alias = set(dir(ga)) self.assertTrue(dir_of_gen_alias.issuperset(dir(list))) - for generic_alias_property in ("__origin__", "__args__", "__parameters__"): - self.assertIn(generic_alias_property, dir_of_gen_alias) + for generic_alias_property in ( + "__origin__", "__args__", "__parameters__", + "__unpacked__", + ): + with self.subTest(generic_alias_property=generic_alias_property): + self.assertIn(generic_alias_property, dir_of_gen_alias) + for blocked in ( + "__bases__", + "__copy__", + "__deepcopy__", + ): + with self.subTest(blocked=blocked): + self.assertNotIn(blocked, dir_of_gen_alias) + + for entry in dir_of_gen_alias: + with self.subTest(entry=entry): + getattr(ga, entry) # must not raise `AttributeError` def test_weakref(self): for t in self.generic_types: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst new file mode 100644 index 00000000000000..4b00564ea264d1 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst @@ -0,0 +1 @@ +Fix :meth:`~object.__dir__` entries of :class:`types.GenericAlias`. diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 3bb961aa2b619d..b3ff933c9b584e 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -636,7 +636,6 @@ ga_vectorcall(PyObject *self, PyObject *const *args, static const char* const attr_exceptions[] = { "__class__", - "__bases__", "__origin__", "__args__", "__unpacked__", @@ -645,6 +644,11 @@ static const char* const attr_exceptions[] = { "__mro_entries__", "__reduce_ex__", // needed so we don't look up object.__reduce_ex__ "__reduce__", + NULL, +}; + +static const char* const attr_blocked[] = { + "__bases__", "__copy__", "__deepcopy__", NULL, @@ -655,15 +659,29 @@ ga_getattro(PyObject *self, PyObject *name) { gaobject *alias = (gaobject *)self; if (PyUnicode_Check(name)) { + // When we check blocked attrs, we don't allow to proxy them to `__origin__`. + // Otherwise, we can break existing code. + for (const char * const *p = attr_blocked; ; p++) { + if (*p == NULL) { + break; + } + if (_PyUnicode_EqualToASCIIString(name, *p)) { + goto generic_getattr; + } + } + + // When we see own attrs, it has a priority over `__origin__`'s attr. for (const char * const *p = attr_exceptions; ; p++) { if (*p == NULL) { return PyObject_GetAttr(alias->origin, name); } if (_PyUnicode_EqualToASCIIString(name, *p)) { - break; + goto generic_getattr; } } } + +generic_getattr: return PyObject_GenericGetAttr(self, name); } From 14674d5bdaba90b8d302c761ef85df6f599c1ade Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 7 Sep 2025 22:24:02 +0300 Subject: [PATCH 2/2] Update Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst Co-authored-by: Emma Smith --- .../2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst index 4b00564ea264d1..968e92c94c1ffc 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst @@ -1 +1 @@ -Fix :meth:`~object.__dir__` entries of :class:`types.GenericAlias`. +Remove non-existent :meth:`~object.__copy__`, :meth:`~object.__deepcopy__`, and :attr:`~type.__bases__` from the :meth:`~object.__dir__` entries of :class:`types.GenericAlias`.