Skip to content
Next Next commit
bpo-46998: Allow subclassing Any at runtime
  • Loading branch information
hauntsaninja committed Mar 13, 2022
commit bc7145697c188e6876257c1de0d545a27b6d7b89
16 changes: 3 additions & 13 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand All @@ -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()
Comment thread
hauntsaninja marked this conversation as resolved.
Outdated

def test_cannot_instantiate(self):
with self.assertRaises(TypeError):
Expand Down
19 changes: 15 additions & 4 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,15 @@ def __getitem__(self, parameters):
return self._getitem(self, *parameters)


@_SpecialForm
def Any(self, parameters):
class _AnyMeta(type):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The metaclass is unfortunate because it restricts what classes can double-inherit from Any (due to metaclass conflicts). Seems unavoidable though.

Copy link
Copy Markdown
Contributor Author

@hauntsaninja hauntsaninja Mar 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not entirely unavoidable, if we were willing to give up on instancecheck (and repr), I'd say we could just remove the metaclass entirely

def __instancecheck__(self, obj):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we even have this? isinstance(X, Any) is now a meaningful operation.

Copy link
Copy Markdown
Contributor Author

@hauntsaninja hauntsaninja Mar 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with removing it (as this PR currently does for issubclass). Doing so would also allow us to get rid of the metaclass, which will help remove restrictions on what classes can inherit from Any.

My reasoning for keeping it is that isinstance is very commonly used, potentially by typing / Python novices, and isinstance(..., Any) doesn't correspond well to the notion of Any at type check time. Sophisticated users have workarounds available to them for the equivalent isinstance check.

raise TypeError("Any cannot be used with isinstance()")

def __repr__(self):
Comment thread
JelleZijlstra marked this conversation as resolved.
return "typing.Any"


class Any(metaclass=_AnyMeta):
"""Special type indicating an unconstrained type.

- Any is compatible with every type.
Expand All @@ -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)
Comment thread
hauntsaninja marked this conversation as resolved.
Outdated


@_SpecialForm
def NoReturn(self, parameters):
Expand Down