From e6a84cc9e6efdb3846c29265eef1d5a3fd824af4 Mon Sep 17 00:00:00 2001 From: ZeroIntensity Date: Fri, 6 Sep 2024 10:10:52 -0400 Subject: [PATCH 1/4] Allow __orig_class__ to be used in the __init__ of pure-Python types. --- Lib/typing.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index bcb7bec23a9aa1..de30a146a4152f 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1290,13 +1290,30 @@ def __call__(self, *args, **kwargs): if not self._inst: raise TypeError(f"Type {self._name} cannot be instantiated; " f"use {self.__origin__.__name__}() instead") - result = self.__origin__(*args, **kwargs) + clas = self.__origin__ + is_python_type = clas.__new__ is type.__new__ + + if is_python_type: + # GH-123777: Some types try and access __orig_class__ in their __init__ + result = clas.__new__(clas, *args, **kwargs) + else: + # This is a C type with a custom __new__ + # + # I'm worried that trying to set attributes on C types before they've been + # fully initialized will be problematic, so let's just disallow them + # from using the __orig_class__ in the initializer for now. + result = clas(*args, **kwargs) + try: result.__orig_class__ = self # Some objects raise TypeError (or something even more exotic) # if you try to set attributes on them; we guard against that here except Exception: pass + + if is_python_type: + result.__init__(*args, **kwargs) + return result def __mro_entries__(self, bases): From 8cc02feb32aedce5af75adfe91b6361e3f3f471a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 9 Sep 2024 16:21:17 -0400 Subject: [PATCH 2/4] Add NEWS entry. --- .../next/Library/2024-09-09-16-21-12.gh-issue-123777.sdyP3n.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-09-09-16-21-12.gh-issue-123777.sdyP3n.rst diff --git a/Misc/NEWS.d/next/Library/2024-09-09-16-21-12.gh-issue-123777.sdyP3n.rst b/Misc/NEWS.d/next/Library/2024-09-09-16-21-12.gh-issue-123777.sdyP3n.rst new file mode 100644 index 00000000000000..df1412213275f3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-09-16-21-12.gh-issue-123777.sdyP3n.rst @@ -0,0 +1,2 @@ +Allowed the ``__orig_class__`` attribute to be used in the ``__init__`` of +pure-Python types. From 00846014891eec28232fa321947cab2990916e5e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 9 Sep 2024 16:31:44 -0400 Subject: [PATCH 3/4] Switch check to inspect.isfunction rather than checking for PyType_GenericNew --- Lib/typing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index de30a146a4152f..ddc45c78afeabd 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -30,6 +30,7 @@ import sys import types from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType, GenericAlias +import inspect from _typing import ( _idfunc, @@ -1291,7 +1292,7 @@ def __call__(self, *args, **kwargs): raise TypeError(f"Type {self._name} cannot be instantiated; " f"use {self.__origin__.__name__}() instead") clas = self.__origin__ - is_python_type = clas.__new__ is type.__new__ + is_python_type = inspect.isfunction(clas.__new__) if is_python_type: # GH-123777: Some types try and access __orig_class__ in their __init__ From 19c0b1e4af29da3fcb959374241ebcf505ca588d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 9 Sep 2024 17:09:41 -0400 Subject: [PATCH 4/4] Add tests and fix implementation. --- Lib/test/test_typing.py | 8 ++++++++ Lib/typing.py | 15 ++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6e036b600330c1..4ce50a84dcb422 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5621,6 +5621,14 @@ class A: with self.assertRaises(TypeError): a[int] + def test_orig_class_init(self): + # See gh-123777 + T = TypeVar("T") + class OrigClassInit(Generic[T]): + def __init__(self): + self.attr = self.__orig_class__ + self.assertEqual(OrigClassInit[int]().attr, OrigClassInit[int]) + class ClassVarTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index ddc45c78afeabd..a7709414c40979 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -30,7 +30,6 @@ import sys import types from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType, GenericAlias -import inspect from _typing import ( _idfunc, @@ -1292,11 +1291,17 @@ def __call__(self, *args, **kwargs): raise TypeError(f"Type {self._name} cannot be instantiated; " f"use {self.__origin__.__name__}() instead") clas = self.__origin__ - is_python_type = inspect.isfunction(clas.__new__) + safe_new = (clas.__new__ is object.__new__) or isinstance(clas.__new__, types.FunctionType) - if is_python_type: + if safe_new: # GH-123777: Some types try and access __orig_class__ in their __init__ - result = clas.__new__(clas, *args, **kwargs) + try: + result = clas.__new__(clas, *args, **kwargs) + except Exception: + # Something about the type doesn't like calling __new__ + # (For example, _GenericAlias) + safe_new = False + result = clas(*args, **kwargs) else: # This is a C type with a custom __new__ # @@ -1312,7 +1317,7 @@ def __call__(self, *args, **kwargs): except Exception: pass - if is_python_type: + if safe_new: result.__init__(*args, **kwargs) return result