diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 4d833dc497f177..d0031fc5d54584 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -578,6 +578,11 @@ 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. This can be useful for + avoiding type checker errors with classes that can duck type anywhere or + are highly dynamic. + .. data:: Never The `bottom type `_, 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") 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], diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 0e28655296d14f..041b6ad9ed6dd1 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,21 @@ 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 Mock(Any): pass + self.assertTrue(issubclass(Mock, Any)) + self.assertIsInstance(Mock(), Mock) + + 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) + self.assertIsInstance(ms, Mock) def test_cannot_instantiate(self): with self.assertRaises(TypeError): diff --git a/Lib/typing.py b/Lib/typing.py index 36f9eceb38c7cf..4636798bd6956e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -429,8 +429,17 @@ def __getitem__(self, parameters): return self._getitem(self, *parameters) -@_SpecialForm -def Any(self, parameters): +class _AnyMeta(type): + def __instancecheck__(self, obj): + if self is Any: + raise TypeError("typing.Any cannot be used with isinstance()") + return super().__instancecheck__(obj) + + def __repr__(self): + return "typing.Any" + + +class Any(metaclass=_AnyMeta): """Special type indicating an unconstrained type. - Any is compatible with every type. @@ -439,9 +448,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 super().__new__(cls, *args, **kwargs) + @_SpecialForm def NoReturn(self, parameters): 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..25b82b53708467 --- /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`. Patch by Shantanu Jain.