Skip to content

Commit 76903ff

Browse files
bpo-44794: Merge tests for typing.Callable and collection.abc.Callable (GH-27507)
(cherry picked from commit be4cb90) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 12073fc commit 76903ff

File tree

3 files changed

+141
-117
lines changed

3 files changed

+141
-117
lines changed

Lib/_collections_abc.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,8 +478,7 @@ def __getitem__(self, item):
478478
# then X[int, str] == X[[int, str]].
479479
param_len = len(self.__parameters__)
480480
if param_len == 0:
481-
raise TypeError(f'There are no type or parameter specification'
482-
f'variables left in {self}')
481+
raise TypeError(f'{self} is not a generic class')
483482
if (param_len == 1
484483
and isinstance(item, (tuple, list))
485484
and len(item) > 1) or not isinstance(item, tuple):

Lib/test/test_genericalias.py

Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -317,96 +317,6 @@ def __new__(cls, *args, **kwargs):
317317
with self.assertRaises(TypeError):
318318
Bad(list, int, bad=int)
319319

320-
def test_abc_callable(self):
321-
# A separate test is needed for Callable since it uses a subclass of
322-
# GenericAlias.
323-
alias = Callable[[int, str], float]
324-
with self.subTest("Testing subscription"):
325-
self.assertIs(alias.__origin__, Callable)
326-
self.assertEqual(alias.__args__, (int, str, float))
327-
self.assertEqual(alias.__parameters__, ())
328-
329-
with self.subTest("Testing instance checks"):
330-
self.assertIsInstance(alias, GenericAlias)
331-
332-
with self.subTest("Testing weakref"):
333-
self.assertEqual(ref(alias)(), alias)
334-
335-
with self.subTest("Testing pickling"):
336-
s = pickle.dumps(alias)
337-
loaded = pickle.loads(s)
338-
self.assertEqual(alias.__origin__, loaded.__origin__)
339-
self.assertEqual(alias.__args__, loaded.__args__)
340-
self.assertEqual(alias.__parameters__, loaded.__parameters__)
341-
342-
with self.subTest("Testing TypeVar substitution"):
343-
C1 = Callable[[int, T], T]
344-
C2 = Callable[[K, T], V]
345-
C3 = Callable[..., T]
346-
self.assertEqual(C1[str], Callable[[int, str], str])
347-
self.assertEqual(C2[int, float, str], Callable[[int, float], str])
348-
self.assertEqual(C3[int], Callable[..., int])
349-
350-
# multi chaining
351-
C4 = C2[int, V, str]
352-
self.assertEqual(repr(C4).split(".")[-1], "Callable[[int, ~V], str]")
353-
self.assertEqual(repr(C4[dict]).split(".")[-1], "Callable[[int, dict], str]")
354-
self.assertEqual(C4[dict], Callable[[int, dict], str])
355-
356-
# substitute a nested GenericAlias (both typing and the builtin
357-
# version)
358-
C5 = Callable[[typing.List[T], tuple[K, T], V], int]
359-
self.assertEqual(C5[int, str, float],
360-
Callable[[typing.List[int], tuple[str, int], float], int])
361-
362-
with self.subTest("Testing type erasure"):
363-
class C1(Callable):
364-
def __call__(self):
365-
return None
366-
a = C1[[int], T]
367-
self.assertIs(a().__class__, C1)
368-
self.assertEqual(a().__orig_class__, C1[[int], T])
369-
370-
# bpo-42195
371-
with self.subTest("Testing collections.abc.Callable's consistency "
372-
"with typing.Callable"):
373-
c1 = typing.Callable[[int, str], dict]
374-
c2 = Callable[[int, str], dict]
375-
self.assertEqual(c1.__args__, c2.__args__)
376-
self.assertEqual(hash(c1.__args__), hash(c2.__args__))
377-
378-
with self.subTest("Testing ParamSpec uses"):
379-
P = typing.ParamSpec('P')
380-
C1 = Callable[P, T]
381-
# substitution
382-
self.assertEqual(C1[int, str], Callable[[int], str])
383-
self.assertEqual(C1[[int, str], str], Callable[[int, str], str])
384-
self.assertEqual(repr(C1).split(".")[-1], "Callable[~P, ~T]")
385-
self.assertEqual(repr(C1[int, str]).split(".")[-1], "Callable[[int], str]")
386-
387-
C2 = Callable[P, int]
388-
# special case in PEP 612 where
389-
# X[int, str, float] == X[[int, str, float]]
390-
self.assertEqual(C2[int, str, float], C2[[int, str, float]])
391-
self.assertEqual(repr(C2).split(".")[-1], "Callable[~P, int]")
392-
self.assertEqual(repr(C2[int, str]).split(".")[-1], "Callable[[int, str], int]")
393-
394-
with self.subTest("Testing Concatenate uses"):
395-
P = typing.ParamSpec('P')
396-
C1 = Callable[typing.Concatenate[int, P], int]
397-
self.assertEqual(repr(C1), "collections.abc.Callable"
398-
"[typing.Concatenate[int, ~P], int]")
399-
400-
with self.subTest("Testing TypeErrors"):
401-
with self.assertRaisesRegex(TypeError, "variables left in"):
402-
alias[int]
403-
P = typing.ParamSpec('P')
404-
C1 = Callable[P, T]
405-
with self.assertRaisesRegex(TypeError, "many arguments for"):
406-
C1[int, str, str]
407-
with self.assertRaisesRegex(TypeError, "few arguments for"):
408-
C1[int]
409-
410320

411321
if __name__ == "__main__":
412322
unittest.main()

Lib/test/test_typing.py

Lines changed: 140 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,8 @@ def test_basics(self):
400400
issubclass(tuple, Tuple[int, str])
401401

402402
class TP(tuple): ...
403-
self.assertTrue(issubclass(tuple, Tuple))
404-
self.assertTrue(issubclass(TP, Tuple))
403+
self.assertIsSubclass(tuple, Tuple)
404+
self.assertIsSubclass(TP, Tuple)
405405

406406
def test_equality(self):
407407
self.assertEqual(Tuple[int], Tuple[int])
@@ -412,7 +412,7 @@ def test_equality(self):
412412
def test_tuple_subclass(self):
413413
class MyTuple(tuple):
414414
pass
415-
self.assertTrue(issubclass(MyTuple, Tuple))
415+
self.assertIsSubclass(MyTuple, Tuple)
416416

417417
def test_tuple_instance_type_error(self):
418418
with self.assertRaises(TypeError):
@@ -433,23 +433,28 @@ def test_errors(self):
433433
issubclass(42, Tuple[int])
434434

435435

436-
class CallableTests(BaseTestCase):
436+
class BaseCallableTests:
437437

438438
def test_self_subclass(self):
439+
Callable = self.Callable
439440
with self.assertRaises(TypeError):
440-
self.assertTrue(issubclass(type(lambda x: x), Callable[[int], int]))
441-
self.assertTrue(issubclass(type(lambda x: x), Callable))
441+
issubclass(types.FunctionType, Callable[[int], int])
442+
self.assertIsSubclass(types.FunctionType, Callable)
442443

443444
def test_eq_hash(self):
444-
self.assertEqual(Callable[[int], int], Callable[[int], int])
445-
self.assertEqual(len({Callable[[int], int], Callable[[int], int]}), 1)
446-
self.assertNotEqual(Callable[[int], int], Callable[[int], str])
447-
self.assertNotEqual(Callable[[int], int], Callable[[str], int])
448-
self.assertNotEqual(Callable[[int], int], Callable[[int, int], int])
449-
self.assertNotEqual(Callable[[int], int], Callable[[], int])
450-
self.assertNotEqual(Callable[[int], int], Callable)
445+
Callable = self.Callable
446+
C = Callable[[int], int]
447+
self.assertEqual(C, Callable[[int], int])
448+
self.assertEqual(len({C, Callable[[int], int]}), 1)
449+
self.assertNotEqual(C, Callable[[int], str])
450+
self.assertNotEqual(C, Callable[[str], int])
451+
self.assertNotEqual(C, Callable[[int, int], int])
452+
self.assertNotEqual(C, Callable[[], int])
453+
self.assertNotEqual(C, Callable[..., int])
454+
self.assertNotEqual(C, Callable)
451455

452456
def test_cannot_instantiate(self):
457+
Callable = self.Callable
453458
with self.assertRaises(TypeError):
454459
Callable()
455460
with self.assertRaises(TypeError):
@@ -461,16 +466,19 @@ def test_cannot_instantiate(self):
461466
type(c)()
462467

463468
def test_callable_wrong_forms(self):
469+
Callable = self.Callable
464470
with self.assertRaises(TypeError):
465471
Callable[int]
466472

467473
def test_callable_instance_works(self):
474+
Callable = self.Callable
468475
def f():
469476
pass
470477
self.assertIsInstance(f, Callable)
471478
self.assertNotIsInstance(None, Callable)
472479

473480
def test_callable_instance_type_error(self):
481+
Callable = self.Callable
474482
def f():
475483
pass
476484
with self.assertRaises(TypeError):
@@ -483,28 +491,142 @@ def f():
483491
self.assertNotIsInstance(None, Callable[[], Any])
484492

485493
def test_repr(self):
494+
Callable = self.Callable
495+
fullname = f'{Callable.__module__}.Callable'
486496
ct0 = Callable[[], bool]
487-
self.assertEqual(repr(ct0), 'typing.Callable[[], bool]')
497+
self.assertEqual(repr(ct0), f'{fullname}[[], bool]')
488498
ct2 = Callable[[str, float], int]
489-
self.assertEqual(repr(ct2), 'typing.Callable[[str, float], int]')
499+
self.assertEqual(repr(ct2), f'{fullname}[[str, float], int]')
490500
ctv = Callable[..., str]
491-
self.assertEqual(repr(ctv), 'typing.Callable[..., str]')
501+
self.assertEqual(repr(ctv), f'{fullname}[..., str]')
492502
ct3 = Callable[[str, float], list[int]]
493-
self.assertEqual(repr(ct3), 'typing.Callable[[str, float], list[int]]')
503+
self.assertEqual(repr(ct3), f'{fullname}[[str, float], list[int]]')
494504

495505
def test_callable_with_ellipsis(self):
496-
506+
Callable = self.Callable
497507
def foo(a: Callable[..., T]):
498508
pass
499509

500510
self.assertEqual(get_type_hints(foo, globals(), locals()),
501511
{'a': Callable[..., T]})
502512

503513
def test_ellipsis_in_generic(self):
514+
Callable = self.Callable
504515
# Shouldn't crash; see https://github.com/python/typing/issues/259
505516
typing.List[Callable[..., str]]
506517

507518

519+
def test_basic(self):
520+
Callable = self.Callable
521+
alias = Callable[[int, str], float]
522+
if Callable is collections.abc.Callable:
523+
self.assertIsInstance(alias, types.GenericAlias)
524+
self.assertIs(alias.__origin__, collections.abc.Callable)
525+
self.assertEqual(alias.__args__, (int, str, float))
526+
self.assertEqual(alias.__parameters__, ())
527+
528+
def test_weakref(self):
529+
Callable = self.Callable
530+
alias = Callable[[int, str], float]
531+
self.assertEqual(weakref.ref(alias)(), alias)
532+
533+
def test_pickle(self):
534+
Callable = self.Callable
535+
alias = Callable[[int, str], float]
536+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
537+
s = pickle.dumps(alias, proto)
538+
loaded = pickle.loads(s)
539+
self.assertEqual(alias.__origin__, loaded.__origin__)
540+
self.assertEqual(alias.__args__, loaded.__args__)
541+
self.assertEqual(alias.__parameters__, loaded.__parameters__)
542+
543+
def test_var_substitution(self):
544+
Callable = self.Callable
545+
fullname = f"{Callable.__module__}.Callable"
546+
C1 = Callable[[int, T], T]
547+
C2 = Callable[[KT, T], VT]
548+
C3 = Callable[..., T]
549+
self.assertEqual(C1[str], Callable[[int, str], str])
550+
self.assertEqual(C2[int, float, str], Callable[[int, float], str])
551+
self.assertEqual(C3[int], Callable[..., int])
552+
553+
# multi chaining
554+
C4 = C2[int, VT, str]
555+
self.assertEqual(repr(C4), f"{fullname}[[int, ~VT], str]")
556+
self.assertEqual(repr(C4[dict]), f"{fullname}[[int, dict], str]")
557+
self.assertEqual(C4[dict], Callable[[int, dict], str])
558+
559+
# substitute a nested GenericAlias (both typing and the builtin
560+
# version)
561+
C5 = Callable[[typing.List[T], tuple[KT, T], VT], int]
562+
self.assertEqual(C5[int, str, float],
563+
Callable[[typing.List[int], tuple[str, int], float], int])
564+
565+
def test_type_erasure(self):
566+
Callable = self.Callable
567+
class C1(Callable):
568+
def __call__(self):
569+
return None
570+
a = C1[[int], T]
571+
self.assertIs(a().__class__, C1)
572+
self.assertEqual(a().__orig_class__, C1[[int], T])
573+
574+
def test_paramspec(self):
575+
Callable = self.Callable
576+
fullname = f"{Callable.__module__}.Callable"
577+
P = ParamSpec('P')
578+
C1 = Callable[P, T]
579+
# substitution
580+
self.assertEqual(C1[int, str], Callable[[int], str])
581+
self.assertEqual(C1[[int, str], str], Callable[[int, str], str])
582+
self.assertEqual(repr(C1), f"{fullname}[~P, ~T]")
583+
self.assertEqual(repr(C1[int, str]), f"{fullname}[[int], str]")
584+
585+
C2 = Callable[P, int]
586+
# special case in PEP 612 where
587+
# X[int, str, float] == X[[int, str, float]]
588+
self.assertEqual(C2[int, str, float], C2[[int, str, float]])
589+
self.assertEqual(repr(C2), f"{fullname}[~P, int]")
590+
self.assertEqual(repr(C2[int, str]), f"{fullname}[[int, str], int]")
591+
592+
def test_concatenate(self):
593+
Callable = self.Callable
594+
fullname = f"{Callable.__module__}.Callable"
595+
P = ParamSpec('P')
596+
C1 = Callable[typing.Concatenate[int, P], int]
597+
self.assertEqual(repr(C1),
598+
f"{fullname}[typing.Concatenate[int, ~P], int]")
599+
600+
def test_errors(self):
601+
Callable = self.Callable
602+
alias = Callable[[int, str], float]
603+
with self.assertRaisesRegex(TypeError, "is not a generic class"):
604+
alias[int]
605+
P = ParamSpec('P')
606+
C1 = Callable[P, T]
607+
with self.assertRaisesRegex(TypeError, "many arguments for"):
608+
C1[int, str, str]
609+
with self.assertRaisesRegex(TypeError, "few arguments for"):
610+
C1[int]
611+
612+
class TypingCallableTests(BaseCallableTests, BaseTestCase):
613+
Callable = typing.Callable
614+
615+
def test_consistency(self):
616+
# bpo-42195
617+
# Testing collections.abc.Callable's consistency with typing.Callable
618+
c1 = typing.Callable[[int, str], dict]
619+
c2 = collections.abc.Callable[[int, str], dict]
620+
self.assertEqual(c1.__args__, c2.__args__)
621+
self.assertEqual(hash(c1.__args__), hash(c2.__args__))
622+
623+
test_errors = skip("known bug #44793")(BaseCallableTests.test_errors)
624+
625+
626+
class CollectionsCallableTests(BaseCallableTests, BaseTestCase):
627+
Callable = collections.abc.Callable
628+
629+
508630
class LiteralTests(BaseTestCase):
509631
def test_basics(self):
510632
# All of these are allowed.
@@ -4456,13 +4578,6 @@ class Z(Generic[P]):
44564578
self.assertEqual(G5.__parameters__, G6.__parameters__)
44574579
self.assertEqual(G5, G6)
44584580

4459-
def test_var_substitution(self):
4460-
T = TypeVar("T")
4461-
P = ParamSpec("P")
4462-
C1 = Callable[P, T]
4463-
self.assertEqual(C1[int, str], Callable[[int], str])
4464-
self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float])
4465-
44664581
def test_no_paramspec_in__parameters__(self):
44674582
# ParamSpec should not be found in __parameters__
44684583
# of generics. Usages outside Callable, Concatenate

0 commit comments

Comments
 (0)