mirror of
https://github.com/python/cpython
synced 2026-09-29 12:10:30 +03:00
bpo-46998: Allow subclassing Any at runtime (GH-31841)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
parent
bb86d1d9fb
commit
5a4973e29f
6 changed files with 44 additions and 31 deletions
|
|
@ -580,6 +580,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 <https://en.wikipedia.org/wiki/Bottom_type>`_,
|
||||
|
|
|
|||
|
|
@ -2802,8 +2802,6 @@ class TestSingleDispatch(unittest.TestCase):
|
|||
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 @@ class TestSingleDispatch(unittest.TestCase):
|
|||
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 @@ class TestSingleDispatch(unittest.TestCase):
|
|||
@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")
|
||||
|
|
|
|||
|
|
@ -1066,14 +1066,14 @@ class TestDescriptions(unittest.TestCase):
|
|||
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],
|
||||
|
|
|
|||
|
|
@ -89,12 +89,6 @@ class AnyTests(BaseTestCase):
|
|||
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 @@ class AnyTests(BaseTestCase):
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -429,8 +429,17 @@ class _LiteralSpecialForm(_SpecialForm, _root=True):
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Allow subclassing of :class:`typing.Any`. Patch by Shantanu Jain.
|
||||
Loading…
Add table
Add a link
Reference in a new issue