bpo-42195: Ensure consistency of Callable's __args__ in collections.abc and typing (GH-23060)
diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py
index c113e53..5de13fe 100644
--- a/Lib/test/test_genericalias.py
+++ b/Lib/test/test_genericalias.py
@@ -62,7 +62,6 @@ class BaseTest(unittest.TestCase):
Iterable, Iterator,
Reversible,
Container, Collection,
- Callable,
Mailbox, _PartialFile,
ContextVar, Token,
Field,
@@ -307,6 +306,63 @@ def test_no_kwargs(self):
with self.assertRaises(TypeError):
GenericAlias(bad=float)
+ def test_subclassing_types_genericalias(self):
+ class SubClass(GenericAlias): ...
+ alias = SubClass(list, int)
+ class Bad(GenericAlias):
+ def __new__(cls, *args, **kwargs):
+ super().__new__(cls, *args, **kwargs)
+
+ self.assertEqual(alias, list[int])
+ with self.assertRaises(TypeError):
+ Bad(list, int, bad=int)
+
+ def test_abc_callable(self):
+ # A separate test is needed for Callable since it uses a subclass of
+ # GenericAlias.
+ alias = Callable[[int, str], float]
+ with self.subTest("Testing subscription"):
+ self.assertIs(alias.__origin__, Callable)
+ self.assertEqual(alias.__args__, (int, str, float))
+ self.assertEqual(alias.__parameters__, ())
+
+ with self.subTest("Testing instance checks"):
+ self.assertIsInstance(alias, GenericAlias)
+
+ with self.subTest("Testing weakref"):
+ self.assertEqual(ref(alias)(), alias)
+
+ with self.subTest("Testing pickling"):
+ s = pickle.dumps(alias)
+ loaded = pickle.loads(s)
+ self.assertEqual(alias.__origin__, loaded.__origin__)
+ self.assertEqual(alias.__args__, loaded.__args__)
+ self.assertEqual(alias.__parameters__, loaded.__parameters__)
+
+ with self.subTest("Testing TypeVar substitution"):
+ C1 = Callable[[int, T], T]
+ C2 = Callable[[K, T], V]
+ C3 = Callable[..., T]
+ self.assertEqual(C1[str], Callable[[int, str], str])
+ self.assertEqual(C2[int, float, str], Callable[[int, float], str])
+ self.assertEqual(C3[int], Callable[..., int])
+
+ with self.subTest("Testing type erasure"):
+ class C1(Callable):
+ def __call__(self):
+ return None
+ a = C1[[int], T]
+ self.assertIs(a().__class__, C1)
+ self.assertEqual(a().__orig_class__, C1[[int], T])
+
+ # bpo-42195
+ with self.subTest("Testing collections.abc.Callable's consistency "
+ "with typing.Callable"):
+ c1 = typing.Callable[[int, str], dict]
+ c2 = Callable[[int, str], dict]
+ self.assertEqual(c1.__args__, c2.__args__)
+ self.assertEqual(hash(c1.__args__), hash(c2.__args__))
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index 3058a02..83196ad 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -717,14 +717,16 @@ def test_or_type_operator_with_genericalias(self):
a = list[int]
b = list[str]
c = dict[float, str]
+ class SubClass(types.GenericAlias): ...
+ d = SubClass(list, float)
# equivalence with typing.Union
- self.assertEqual(a | b | c, typing.Union[a, b, c])
+ self.assertEqual(a | b | c | d, typing.Union[a, b, c, d])
# de-duplicate
- self.assertEqual(a | c | b | b | a | c, a | b | c)
+ self.assertEqual(a | c | b | b | a | c | d | d, a | b | c | d)
# order shouldn't matter
- self.assertEqual(a | b, b | a)
- self.assertEqual(repr(a | b | c),
- "list[int] | list[str] | dict[float, str]")
+ self.assertEqual(a | b | d, b | a | d)
+ self.assertEqual(repr(a | b | c | d),
+ "list[int] | list[str] | dict[float, str] | list[float]")
class BadType(type):
def __eq__(self, other):
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index f3e38b6..8e86e76 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -447,14 +447,6 @@ def test_cannot_instantiate(self):
def test_callable_wrong_forms(self):
with self.assertRaises(TypeError):
- Callable[[...], int]
- with self.assertRaises(TypeError):
- Callable[(), int]
- with self.assertRaises(TypeError):
- Callable[[()], int]
- with self.assertRaises(TypeError):
- Callable[[int, 1], 2]
- with self.assertRaises(TypeError):
Callable[int]
def test_callable_instance_works(self):
@@ -1807,10 +1799,9 @@ def barfoo2(x: CT): ...
def test_extended_generic_rules_subclassing(self):
class T1(Tuple[T, KT]): ...
class T2(Tuple[T, ...]): ...
- class C1(Callable[[T], T]): ...
- class C2(Callable[..., int]):
- def __call__(self):
- return None
+ class C1(typing.Container[T]):
+ def __contains__(self, item):
+ return False
self.assertEqual(T1.__parameters__, (T, KT))
self.assertEqual(T1[int, str].__args__, (int, str))
@@ -1824,10 +1815,9 @@ def __call__(self):
## T2[int, str]
self.assertEqual(repr(C1[int]).split('.')[-1], 'C1[int]')
- self.assertEqual(C2.__parameters__, ())
- self.assertIsInstance(C2(), collections.abc.Callable)
- self.assertIsSubclass(C2, collections.abc.Callable)
- self.assertIsSubclass(C1, collections.abc.Callable)
+ self.assertEqual(C1.__parameters__, (T,))
+ self.assertIsInstance(C1(), collections.abc.Container)
+ self.assertIsSubclass(C1, collections.abc.Container)
self.assertIsInstance(T1(), tuple)
self.assertIsSubclass(T2, tuple)
with self.assertRaises(TypeError):
@@ -1861,10 +1851,6 @@ def test_type_erasure_special(self):
class MyTup(Tuple[T, T]): ...
self.assertIs(MyTup[int]().__class__, MyTup)
self.assertEqual(MyTup[int]().__orig_class__, MyTup[int])
- class MyCall(Callable[..., T]):
- def __call__(self): return None
- self.assertIs(MyCall[T]().__class__, MyCall)
- self.assertEqual(MyCall[T]().__orig_class__, MyCall[T])
class MyDict(typing.Dict[T, T]): ...
self.assertIs(MyDict[int]().__class__, MyDict)
self.assertEqual(MyDict[int]().__orig_class__, MyDict[int])