Skip to content

gh-88834: Unify the instance check for typing.Union and types.UnionType #128363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class Sub(Any): pass

def test_errors(self):
with self.assertRaises(TypeError):
issubclass(42, Any)
isinstance(42, Any)
with self.assertRaises(TypeError):
Any[int] # Any is not a generic type.

Expand All @@ -137,6 +137,9 @@ class Something: pass

class MockSomething(Something, Mock): pass
self.assertTrue(issubclass(MockSomething, Any))
self.assertTrue(issubclass(MockSomething, MockSomething))
self.assertTrue(issubclass(MockSomething, Something))
self.assertTrue(issubclass(MockSomething, Mock))
ms = MockSomething()
self.assertIsInstance(ms, MockSomething)
self.assertIsInstance(ms, Something)
Expand Down Expand Up @@ -2010,13 +2013,81 @@ def test_basics(self):
u = Union[int, float]
self.assertNotEqual(u, Union)

def test_subclass_error(self):
def test_union_isinstance(self):
self.assertTrue(isinstance(42, Union[int, str]))
self.assertTrue(isinstance('abc', Union[int, str]))
self.assertFalse(isinstance(3.14, Union[int, str]))
self.assertTrue(isinstance(42, Union[int, list[int]]))
self.assertTrue(isinstance(42, Union[int, Any]))

def test_union_isinstance_type_error(self):
with self.assertRaises(TypeError):
isinstance(42, Union[str, list[int]])
with self.assertRaises(TypeError):
isinstance(42, Union[list[int], int])
with self.assertRaises(TypeError):
isinstance(42, Union[list[int], str])
with self.assertRaises(TypeError):
isinstance(42, Union[str, Any])
with self.assertRaises(TypeError):
isinstance(42, Union[Any, int])
with self.assertRaises(TypeError):
isinstance(42, Union[Any, str])

def test_optional_isinstance(self):
self.assertTrue(isinstance(42, Optional[int]))
self.assertTrue(isinstance(None, Optional[int]))
self.assertFalse(isinstance('abc', Optional[int]))

def test_optional_isinstance_type_error(self):
with self.assertRaises(TypeError):
isinstance(42, Optional[list[int]])
with self.assertRaises(TypeError):
isinstance(None, Optional[list[int]])
with self.assertRaises(TypeError):
isinstance(42, Optional[Any])
with self.assertRaises(TypeError):
isinstance(None, Optional[Any])

def test_union_issubclass(self):
self.assertTrue(issubclass(int, Union[int, str]))
self.assertTrue(issubclass(str, Union[int, str]))
self.assertFalse(issubclass(float, Union[int, str]))
self.assertTrue(issubclass(int, Union[int, list[int]]))
self.assertTrue(issubclass(int, Union[int, Any]))
self.assertFalse(issubclass(int, Union[str, Any]))
self.assertTrue(issubclass(int, Union[Any, int]))
self.assertFalse(issubclass(int, Union[Any, str]))

def test_union_issubclass_type_error(self):
with self.assertRaises(TypeError):
issubclass(int, Union)
with self.assertRaises(TypeError):
issubclass(Union, int)
with self.assertRaises(TypeError):
issubclass(Union[int, str], int)
with self.assertRaises(TypeError):
issubclass(int, Union[str, list[int]])
with self.assertRaises(TypeError):
issubclass(int, Union[list[int], int])
with self.assertRaises(TypeError):
issubclass(int, Union[list[int], str])

def test_optional_issubclass(self):
self.assertTrue(issubclass(int, Optional[int]))
self.assertTrue(issubclass(type(None), Optional[int]))
self.assertFalse(issubclass(str, Optional[int]))
self.assertTrue(issubclass(Any, Optional[Any]))
self.assertTrue(issubclass(type(None), Optional[Any]))
self.assertFalse(issubclass(int, Optional[Any]))

def test_optional_issubclass_type_error(self):
with self.assertRaises(TypeError):
issubclass(list[int], Optional[list[int]])
with self.assertRaises(TypeError):
issubclass(type(None), Optional[list[int]])
with self.assertRaises(TypeError):
issubclass(int, Optional[list[int]])

def test_union_any(self):
u = Union[Any]
Expand Down
6 changes: 5 additions & 1 deletion Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1733,12 +1733,16 @@ def __repr__(self):
return super().__repr__()

def __instancecheck__(self, obj):
return self.__subclasscheck__(type(obj))
for arg in self.__args__:
if isinstance(obj, arg):
return True
return False

def __subclasscheck__(self, cls):
for arg in self.__args__:
if issubclass(cls, arg):
return True
return False

def __reduce__(self):
func, (origin, args) = super().__reduce__()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Unify the instance check for :class:`typing.Union` and
:class:`types.UnionType`: :class:`!Union` now uses the instance checks
against its parameters instead of the subclass checks.
Loading