From bc7145697c188e6876257c1de0d545a27b6d7b89 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sat, 12 Mar 2022 19:06:52 -0800 Subject: [PATCH 01/14] bpo-46998: Allow subclassing Any at runtime --- Lib/test/test_typing.py | 16 +++------------- Lib/typing.py | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b212b523048809..242a3402d8f47a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -89,12 +89,6 @@ def test_any_instance_type_error(self): with self.assertRaises(TypeError): isinstance(42, Any) - def test_any_subclass_type_error(self): - with self.assertRaises(TypeError): - issubclass(Employee, Any) - with self.assertRaises(TypeError): - issubclass(Any, Employee) - def test_repr(self): self.assertEqual(repr(Any), 'typing.Any') @@ -104,13 +98,9 @@ def test_errors(self): with self.assertRaises(TypeError): Any[int] # Any is not a generic type. - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class A(Any): - pass - with self.assertRaises(TypeError): - class A(type(Any)): - pass + def test_can_subclass(self): + class X(Any): pass + X() def test_cannot_instantiate(self): with self.assertRaises(TypeError): diff --git a/Lib/typing.py b/Lib/typing.py index dd68e71db1558c..d8e30620fe2f1e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -428,8 +428,15 @@ def __getitem__(self, parameters): return self._getitem(self, *parameters) -@_SpecialForm -def Any(self, parameters): +class _AnyMeta(type): + def __instancecheck__(self, obj): + raise TypeError("Any cannot be used with isinstance()") + + def __repr__(self): + return "typing.Any" + + +class Any(metaclass=_AnyMeta): """Special type indicating an unconstrained type. - Any is compatible with every type. @@ -438,9 +445,13 @@ def Any(self, parameters): Note that all the above statements are true from the point of view of static type checkers. At runtime, Any should not be used with instance - or class checks. + checks. """ - raise TypeError(f"{self} is not subscriptable") + def __new__(cls, *args, **kwargs): + if cls is Any: + raise TypeError("Any cannot be instantiated") + return object.__new__(cls, *args, **kwargs) + @_SpecialForm def NoReturn(self, parameters): From abea39a381934b0d50a3be8048edf6367a3ff512 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sat, 12 Mar 2022 19:10:17 -0800 Subject: [PATCH 02/14] make error consistent --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index d8e30620fe2f1e..2c194b5d49d94e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -430,7 +430,7 @@ def __getitem__(self, parameters): class _AnyMeta(type): def __instancecheck__(self, obj): - raise TypeError("Any cannot be used with isinstance()") + raise TypeError("typing.Any cannot be used with isinstance()") def __repr__(self): return "typing.Any" From 771f492f76f72b0451219500f3d71aeb54eb2308 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 13 Mar 2022 00:36:37 -0800 Subject: [PATCH 03/14] remove functools checks --- Lib/test/test_functools.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index abbd50a47f395f..82e73f46a3fbae 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2802,8 +2802,6 @@ def f(arg): f.register(list[int] | str, lambda arg: "types.UnionTypes(types.GenericAlias)") with self.assertRaisesRegex(TypeError, "Invalid first argument to "): f.register(typing.List[float] | bytes, lambda arg: "typing.Union[typing.GenericAlias]") - with self.assertRaisesRegex(TypeError, "Invalid first argument to "): - f.register(typing.Any, lambda arg: "typing.Any") self.assertEqual(f([1]), "default") self.assertEqual(f([1.0]), "default") @@ -2823,8 +2821,6 @@ def f(arg): f.register(list[int] | str) with self.assertRaisesRegex(TypeError, "Invalid first argument to "): f.register(typing.List[int] | str) - with self.assertRaisesRegex(TypeError, "Invalid first argument to "): - f.register(typing.Any) def test_register_genericalias_annotation(self): @functools.singledispatch @@ -2847,10 +2843,6 @@ def _(arg: list[int] | str): @f.register def _(arg: typing.List[float] | bytes): return "typing.Union[typing.GenericAlias]" - with self.assertRaisesRegex(TypeError, "Invalid annotation for 'arg'"): - @f.register - def _(arg: typing.Any): - return "typing.Any" self.assertEqual(f([1]), "default") self.assertEqual(f([1.0]), "default") From 900f2c7ae847231857f290b60a7f472bf85f4957 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 13 Mar 2022 08:53:00 +0000 Subject: [PATCH 04/14] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst diff --git a/Misc/NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst b/Misc/NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst new file mode 100644 index 00000000000000..07c960e4c7d8a1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst @@ -0,0 +1 @@ +Allow subclassing of :class:`typing.Any` From 6e7644165f71c20d0f2ebd38354ec8298d840214 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 13 Mar 2022 14:38:16 -0700 Subject: [PATCH 05/14] more tests --- Lib/test/test_typing.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 242a3402d8f47a..7dc86add1a878f 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -99,8 +99,19 @@ def test_errors(self): Any[int] # Any is not a generic type. def test_can_subclass(self): - class X(Any): pass - X() + class Mock(Any): pass + self.assertTrue(issubclass(Mock, Any)) + self.assertIsInstance(Mock(), Mock) + + class Something: pass + self.assertFalse(issubclass(Something, Any)) + self.assertNotIsInstance(Something(), Any) + + class MockSomething(Something, Any): pass + self.assertTrue(issubclass(MockSomething, Any)) + ms = MockSomething() + self.assertIsInstance(ms, Mock) + self.assertIsInstance(ms, Something) def test_cannot_instantiate(self): with self.assertRaises(TypeError): From b00ab171646efbdc1012f4b80bd6070ce388cc37 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 13 Mar 2022 14:40:30 -0700 Subject: [PATCH 06/14] Update Misc/NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst Co-authored-by: Jelle Zijlstra --- .../next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst b/Misc/NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst index 07c960e4c7d8a1..25b82b53708467 100644 --- a/Misc/NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst +++ b/Misc/NEWS.d/next/Library/2022-03-13-08-52-58.bpo-46998.cHh-9O.rst @@ -1 +1 @@ -Allow subclassing of :class:`typing.Any` +Allow subclassing of :class:`typing.Any`. Patch by Shantanu Jain. From 4d913add2e7f38250e361b8a411d743c790e1839 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 13 Mar 2022 14:42:44 -0700 Subject: [PATCH 07/14] oops, fix test --- Lib/test/test_typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 7dc86add1a878f..83f3ceb88489c8 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -105,7 +105,6 @@ class Mock(Any): pass class Something: pass self.assertFalse(issubclass(Something, Any)) - self.assertNotIsInstance(Something(), Any) class MockSomething(Something, Any): pass self.assertTrue(issubclass(MockSomething, Any)) From 1c1ab38de08ade0454e2dbb3338e84e57f779d95 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 13 Mar 2022 14:44:55 -0700 Subject: [PATCH 08/14] cooperative inheritance --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 2c194b5d49d94e..02b59077cc3c4f 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -450,7 +450,7 @@ class Any(metaclass=_AnyMeta): def __new__(cls, *args, **kwargs): if cls is Any: raise TypeError("Any cannot be instantiated") - return object.__new__(cls, *args, **kwargs) + return super().__new__(cls, *args, **kwargs) @_SpecialForm From 4927e78b9014637ab87f99842f3c3db1cbf458e6 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 13 Mar 2022 14:45:11 -0700 Subject: [PATCH 09/14] fix tests --- Lib/test/test_typing.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 83f3ceb88489c8..ff945ba65fa23b 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -106,11 +106,14 @@ class Mock(Any): pass class Something: pass self.assertFalse(issubclass(Something, Any)) - class MockSomething(Something, Any): pass + class MockSomething(Something, Mock): pass self.assertTrue(issubclass(MockSomething, Any)) ms = MockSomething() - self.assertIsInstance(ms, Mock) + self.assertIsInstance(ms, MockSomething) self.assertIsInstance(ms, Something) + with self.assertRaises(TypeError): + # TODO(address in code review): this seems undesirable, should we abandon instancecheck? + self.assertIsInstance(ms, Mock) def test_cannot_instantiate(self): with self.assertRaises(TypeError): From 68ea3a844f861293fd75c33fe645bdd667f74257 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sun, 13 Mar 2022 14:53:19 -0700 Subject: [PATCH 10/14] okay, make instancecheck work with multiple inheritance --- Lib/test/test_typing.py | 5 ++--- Lib/typing.py | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index ff945ba65fa23b..a005c8f77d1054 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -105,15 +105,14 @@ class Mock(Any): pass class Something: pass self.assertFalse(issubclass(Something, Any)) + self.assertNotIsInstance(Something(), Mock) class MockSomething(Something, Mock): pass self.assertTrue(issubclass(MockSomething, Any)) ms = MockSomething() self.assertIsInstance(ms, MockSomething) self.assertIsInstance(ms, Something) - with self.assertRaises(TypeError): - # TODO(address in code review): this seems undesirable, should we abandon instancecheck? - self.assertIsInstance(ms, Mock) + self.assertIsInstance(ms, Mock) def test_cannot_instantiate(self): with self.assertRaises(TypeError): diff --git a/Lib/typing.py b/Lib/typing.py index 02b59077cc3c4f..dc9712bffdd7e0 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -430,7 +430,9 @@ def __getitem__(self, parameters): class _AnyMeta(type): def __instancecheck__(self, obj): - raise TypeError("typing.Any cannot be used with isinstance()") + if self is Any: + raise TypeError("typing.Any cannot be used with isinstance()") + return super().__instancecheck__(obj) def __repr__(self): return "typing.Any" From f94b7e014987d8ad791d1731d2512bdb1cbbae97 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Tue, 29 Mar 2022 19:35:04 -0700 Subject: [PATCH 11/14] mention in docs --- Doc/library/typing.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 0c23a233c0d7dd..15c54772bf53f0 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -578,6 +578,9 @@ These can be used as types in annotations and do not support ``[]``. * Every type is compatible with :data:`Any`. * :data:`Any` is compatible with every type. + .. versionchanged:: 3.11 + :data:`Any` can now be used as a base class + .. data:: Never The `bottom type `_, From e11f405a4aa3752075815fc248b17c4114da7772 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Tue, 29 Mar 2022 22:14:30 -0700 Subject: [PATCH 12/14] fix pydoc test --- Lib/test/test_pydoc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index 9c900c3e8ee0af..13c77b6fa6822c 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -1066,14 +1066,14 @@ def test_union_type(self): self.assertIn(types.UnionType.__doc__.strip().splitlines()[0], doc) def test_special_form(self): - self.assertEqual(pydoc.describe(typing.Any), '_SpecialForm') - doc = pydoc.render_doc(typing.Any, renderer=pydoc.plaintext) + self.assertEqual(pydoc.describe(typing.NoReturn), '_SpecialForm') + doc = pydoc.render_doc(typing.NoReturn, renderer=pydoc.plaintext) self.assertIn('_SpecialForm in module typing', doc) - if typing.Any.__doc__: - self.assertIn('Any = typing.Any', doc) - self.assertIn(typing.Any.__doc__.strip().splitlines()[0], doc) + if typing.NoReturn.__doc__: + self.assertIn('NoReturn = typing.NoReturn', doc) + self.assertIn(typing.NoReturn.__doc__.strip().splitlines()[0], doc) else: - self.assertIn('Any = class _SpecialForm(_Final)', doc) + self.assertIn('NoReturn = class _SpecialForm(_Final)', doc) def test_typing_pydoc(self): def foo(data: typing.List[typing.Any], From 63b00e41cb466191874763af431ce9e500eeecbc Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Wed, 30 Mar 2022 14:54:25 -0700 Subject: [PATCH 13/14] add a sentence on reasoning --- Doc/library/typing.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 711e33c131a5e8..6c1a11a14473ac 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -579,7 +579,9 @@ These can be used as types in annotations and do not support ``[]``. * :data:`Any` is compatible with every type. .. versionchanged:: 3.11 - :data:`Any` can now be used as a base class + :data:`Any` can now be used as a base class. This can be useful for + avoiding type checker errors in classes that can duck type anywhere or + are highly dynamic. .. data:: Never From ac7b4a894ddaa0f443413fb263f4628ca31b17f4 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Wed, 30 Mar 2022 20:21:39 -0700 Subject: [PATCH 14/14] preposition --- Doc/library/typing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 6c1a11a14473ac..d0031fc5d54584 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -580,7 +580,7 @@ These can be used as types in annotations and do not support ``[]``. .. versionchanged:: 3.11 :data:`Any` can now be used as a base class. This can be useful for - avoiding type checker errors in classes that can duck type anywhere or + avoiding type checker errors with classes that can duck type anywhere or are highly dynamic. .. data:: Never