[Python-checkins] bpo-44794: Merge tests for typing.Callable and collection.abc.Callable (GH-27507)

serhiy-storchaka webhook-mailer at python.org
Sat Jul 31 13:06:10 EDT 2021


https://github.com/python/cpython/commit/be4cb9089aaf58d5f90da5f9fa66dc3c6763b5a2
commit: be4cb9089aaf58d5f90da5f9fa66dc3c6763b5a2
branch: main
author: Serhiy Storchaka <storchaka at gmail.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2021-07-31T20:05:45+03:00
summary:

bpo-44794: Merge tests for typing.Callable and collection.abc.Callable (GH-27507)

files:
M Lib/_collections_abc.py
M Lib/test/test_genericalias.py
M Lib/test/test_typing.py

diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
index bff58ad4a7f6a..33db9b259817c 100644
--- a/Lib/_collections_abc.py
+++ b/Lib/_collections_abc.py
@@ -478,8 +478,7 @@ def __getitem__(self, item):
         # then X[int, str] == X[[int, str]].
         param_len = len(self.__parameters__)
         if param_len == 0:
-            raise TypeError(f'There are no type or parameter specification'
-                            f'variables left in {self}')
+            raise TypeError(f'{self} is not a generic class')
         if (param_len == 1
                 and isinstance(item, (tuple, list))
                 and len(item) > 1) or not isinstance(item, tuple):
diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py
index 9f927392fc874..2e70e751d4eee 100644
--- a/Lib/test/test_genericalias.py
+++ b/Lib/test/test_genericalias.py
@@ -317,96 +317,6 @@ def __new__(cls, *args, **kwargs):
         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])
-
-            # multi chaining
-            C4 = C2[int, V, str]
-            self.assertEqual(repr(C4).split(".")[-1], "Callable[[int, ~V], str]")
-            self.assertEqual(repr(C4[dict]).split(".")[-1], "Callable[[int, dict], str]")
-            self.assertEqual(C4[dict], Callable[[int, dict], str])
-
-            # substitute a nested GenericAlias (both typing and the builtin
-            # version)
-            C5 = Callable[[typing.List[T], tuple[K, T], V], int]
-            self.assertEqual(C5[int, str, float],
-                             Callable[[typing.List[int], tuple[str, int], float], 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__))
-
-        with self.subTest("Testing ParamSpec uses"):
-            P = typing.ParamSpec('P')
-            C1 = Callable[P, T]
-            # substitution
-            self.assertEqual(C1[int, str], Callable[[int], str])
-            self.assertEqual(C1[[int, str], str], Callable[[int, str], str])
-            self.assertEqual(repr(C1).split(".")[-1], "Callable[~P, ~T]")
-            self.assertEqual(repr(C1[int, str]).split(".")[-1], "Callable[[int], str]")
-
-            C2 = Callable[P, int]
-            # special case in PEP 612 where
-            # X[int, str, float] == X[[int, str, float]]
-            self.assertEqual(C2[int, str, float], C2[[int, str, float]])
-            self.assertEqual(repr(C2).split(".")[-1], "Callable[~P, int]")
-            self.assertEqual(repr(C2[int, str]).split(".")[-1], "Callable[[int, str], int]")
-
-        with self.subTest("Testing Concatenate uses"):
-            P = typing.ParamSpec('P')
-            C1 = Callable[typing.Concatenate[int, P], int]
-            self.assertEqual(repr(C1), "collections.abc.Callable"
-                                       "[typing.Concatenate[int, ~P], int]")
-
-        with self.subTest("Testing TypeErrors"):
-            with self.assertRaisesRegex(TypeError, "variables left in"):
-                alias[int]
-            P = typing.ParamSpec('P')
-            C1 = Callable[P, T]
-            with self.assertRaisesRegex(TypeError, "many arguments for"):
-                C1[int, str, str]
-            with self.assertRaisesRegex(TypeError, "few arguments for"):
-                C1[int]
-
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 06bd49b593a77..fbdf634c5c3be 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -406,8 +406,8 @@ def test_basics(self):
             issubclass(tuple, Tuple[int, str])
 
         class TP(tuple): ...
-        self.assertTrue(issubclass(tuple, Tuple))
-        self.assertTrue(issubclass(TP, Tuple))
+        self.assertIsSubclass(tuple, Tuple)
+        self.assertIsSubclass(TP, Tuple)
 
     def test_equality(self):
         self.assertEqual(Tuple[int], Tuple[int])
@@ -418,7 +418,7 @@ def test_equality(self):
     def test_tuple_subclass(self):
         class MyTuple(tuple):
             pass
-        self.assertTrue(issubclass(MyTuple, Tuple))
+        self.assertIsSubclass(MyTuple, Tuple)
 
     def test_tuple_instance_type_error(self):
         with self.assertRaises(TypeError):
@@ -439,23 +439,28 @@ def test_errors(self):
             issubclass(42, Tuple[int])
 
 
-class CallableTests(BaseTestCase):
+class BaseCallableTests:
 
     def test_self_subclass(self):
+        Callable = self.Callable
         with self.assertRaises(TypeError):
-            self.assertTrue(issubclass(type(lambda x: x), Callable[[int], int]))
-        self.assertTrue(issubclass(type(lambda x: x), Callable))
+            issubclass(types.FunctionType, Callable[[int], int])
+        self.assertIsSubclass(types.FunctionType, Callable)
 
     def test_eq_hash(self):
-        self.assertEqual(Callable[[int], int], Callable[[int], int])
-        self.assertEqual(len({Callable[[int], int], Callable[[int], int]}), 1)
-        self.assertNotEqual(Callable[[int], int], Callable[[int], str])
-        self.assertNotEqual(Callable[[int], int], Callable[[str], int])
-        self.assertNotEqual(Callable[[int], int], Callable[[int, int], int])
-        self.assertNotEqual(Callable[[int], int], Callable[[], int])
-        self.assertNotEqual(Callable[[int], int], Callable)
+        Callable = self.Callable
+        C = Callable[[int], int]
+        self.assertEqual(C, Callable[[int], int])
+        self.assertEqual(len({C, Callable[[int], int]}), 1)
+        self.assertNotEqual(C, Callable[[int], str])
+        self.assertNotEqual(C, Callable[[str], int])
+        self.assertNotEqual(C, Callable[[int, int], int])
+        self.assertNotEqual(C, Callable[[], int])
+        self.assertNotEqual(C, Callable[..., int])
+        self.assertNotEqual(C, Callable)
 
     def test_cannot_instantiate(self):
+        Callable = self.Callable
         with self.assertRaises(TypeError):
             Callable()
         with self.assertRaises(TypeError):
@@ -467,16 +472,19 @@ def test_cannot_instantiate(self):
             type(c)()
 
     def test_callable_wrong_forms(self):
+        Callable = self.Callable
         with self.assertRaises(TypeError):
             Callable[int]
 
     def test_callable_instance_works(self):
+        Callable = self.Callable
         def f():
             pass
         self.assertIsInstance(f, Callable)
         self.assertNotIsInstance(None, Callable)
 
     def test_callable_instance_type_error(self):
+        Callable = self.Callable
         def f():
             pass
         with self.assertRaises(TypeError):
@@ -489,17 +497,19 @@ def f():
             self.assertNotIsInstance(None, Callable[[], Any])
 
     def test_repr(self):
+        Callable = self.Callable
+        fullname = f'{Callable.__module__}.Callable'
         ct0 = Callable[[], bool]
-        self.assertEqual(repr(ct0), 'typing.Callable[[], bool]')
+        self.assertEqual(repr(ct0), f'{fullname}[[], bool]')
         ct2 = Callable[[str, float], int]
-        self.assertEqual(repr(ct2), 'typing.Callable[[str, float], int]')
+        self.assertEqual(repr(ct2), f'{fullname}[[str, float], int]')
         ctv = Callable[..., str]
-        self.assertEqual(repr(ctv), 'typing.Callable[..., str]')
+        self.assertEqual(repr(ctv), f'{fullname}[..., str]')
         ct3 = Callable[[str, float], list[int]]
-        self.assertEqual(repr(ct3), 'typing.Callable[[str, float], list[int]]')
+        self.assertEqual(repr(ct3), f'{fullname}[[str, float], list[int]]')
 
     def test_callable_with_ellipsis(self):
-
+        Callable = self.Callable
         def foo(a: Callable[..., T]):
             pass
 
@@ -507,10 +517,122 @@ def foo(a: Callable[..., T]):
                          {'a': Callable[..., T]})
 
     def test_ellipsis_in_generic(self):
+        Callable = self.Callable
         # Shouldn't crash; see https://github.com/python/typing/issues/259
         typing.List[Callable[..., str]]
 
 
+    def test_basic(self):
+        Callable = self.Callable
+        alias = Callable[[int, str], float]
+        if Callable is collections.abc.Callable:
+            self.assertIsInstance(alias, types.GenericAlias)
+        self.assertIs(alias.__origin__, collections.abc.Callable)
+        self.assertEqual(alias.__args__, (int, str, float))
+        self.assertEqual(alias.__parameters__, ())
+
+    def test_weakref(self):
+        Callable = self.Callable
+        alias = Callable[[int, str], float]
+        self.assertEqual(weakref.ref(alias)(), alias)
+
+    def test_pickle(self):
+        Callable = self.Callable
+        alias = Callable[[int, str], float]
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            s = pickle.dumps(alias, proto)
+            loaded = pickle.loads(s)
+            self.assertEqual(alias.__origin__, loaded.__origin__)
+            self.assertEqual(alias.__args__, loaded.__args__)
+            self.assertEqual(alias.__parameters__, loaded.__parameters__)
+
+    def test_var_substitution(self):
+        Callable = self.Callable
+        fullname = f"{Callable.__module__}.Callable"
+        C1 = Callable[[int, T], T]
+        C2 = Callable[[KT, T], VT]
+        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])
+
+        # multi chaining
+        C4 = C2[int, VT, str]
+        self.assertEqual(repr(C4), f"{fullname}[[int, ~VT], str]")
+        self.assertEqual(repr(C4[dict]), f"{fullname}[[int, dict], str]")
+        self.assertEqual(C4[dict], Callable[[int, dict], str])
+
+        # substitute a nested GenericAlias (both typing and the builtin
+        # version)
+        C5 = Callable[[typing.List[T], tuple[KT, T], VT], int]
+        self.assertEqual(C5[int, str, float],
+                         Callable[[typing.List[int], tuple[str, int], float], int])
+
+    def test_type_erasure(self):
+        Callable = self.Callable
+        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])
+
+    def test_paramspec(self):
+        Callable = self.Callable
+        fullname = f"{Callable.__module__}.Callable"
+        P = ParamSpec('P')
+        C1 = Callable[P, T]
+        # substitution
+        self.assertEqual(C1[int, str], Callable[[int], str])
+        self.assertEqual(C1[[int, str], str], Callable[[int, str], str])
+        self.assertEqual(repr(C1), f"{fullname}[~P, ~T]")
+        self.assertEqual(repr(C1[int, str]), f"{fullname}[[int], str]")
+
+        C2 = Callable[P, int]
+        # special case in PEP 612 where
+        # X[int, str, float] == X[[int, str, float]]
+        self.assertEqual(C2[int, str, float], C2[[int, str, float]])
+        self.assertEqual(repr(C2), f"{fullname}[~P, int]")
+        self.assertEqual(repr(C2[int, str]), f"{fullname}[[int, str], int]")
+
+    def test_concatenate(self):
+        Callable = self.Callable
+        fullname = f"{Callable.__module__}.Callable"
+        P = ParamSpec('P')
+        C1 = Callable[typing.Concatenate[int, P], int]
+        self.assertEqual(repr(C1),
+                            f"{fullname}[typing.Concatenate[int, ~P], int]")
+
+    def test_errors(self):
+        Callable = self.Callable
+        alias = Callable[[int, str], float]
+        with self.assertRaisesRegex(TypeError, "is not a generic class"):
+            alias[int]
+        P = ParamSpec('P')
+        C1 = Callable[P, T]
+        with self.assertRaisesRegex(TypeError, "many arguments for"):
+            C1[int, str, str]
+        with self.assertRaisesRegex(TypeError, "few arguments for"):
+            C1[int]
+
+class TypingCallableTests(BaseCallableTests, BaseTestCase):
+    Callable = typing.Callable
+
+    def test_consistency(self):
+        # bpo-42195
+        # Testing collections.abc.Callable's consistency with typing.Callable
+        c1 = typing.Callable[[int, str], dict]
+        c2 = collections.abc.Callable[[int, str], dict]
+        self.assertEqual(c1.__args__, c2.__args__)
+        self.assertEqual(hash(c1.__args__), hash(c2.__args__))
+
+    test_errors = skip("known bug #44793")(BaseCallableTests.test_errors)
+
+
+class CollectionsCallableTests(BaseCallableTests, BaseTestCase):
+    Callable = collections.abc.Callable
+
+
 class LiteralTests(BaseTestCase):
     def test_basics(self):
         # All of these are allowed.
@@ -4496,13 +4618,6 @@ class Z(Generic[P]):
         self.assertEqual(G5.__parameters__, G6.__parameters__)
         self.assertEqual(G5, G6)
 
-    def test_var_substitution(self):
-        T = TypeVar("T")
-        P = ParamSpec("P")
-        C1 = Callable[P, T]
-        self.assertEqual(C1[int, str], Callable[[int], str])
-        self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float])
-
     def test_no_paramspec_in__parameters__(self):
         # ParamSpec should not be found in __parameters__
         # of generics. Usages outside Callable, Concatenate



More information about the Python-checkins mailing list