bpo-28556: Various updates to typing (#28)
various updates from upstream python/typing repo: - Added typing.Counter and typing.ChainMap generics - More flexible typing.NamedTuple - Improved generic ABC caching - More tests - Bugfixes - Other updates * Add Misc/NEWS entry * Add issue number
This commit is contained in:
parent
3cdbd68ce8
commit
b692dc8475
|
@ -0,0 +1,14 @@
|
||||||
|
"""Module for testing the behavior of generics across different modules."""
|
||||||
|
|
||||||
|
from typing import TypeVar, Generic
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
class A(Generic[T]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class B(Generic[T]):
|
||||||
|
class A(Generic[T]):
|
||||||
|
pass
|
|
@ -190,6 +190,10 @@ class TypeVarTests(BaseTestCase):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
TypeVar('X', str, float, bound=Employee)
|
TypeVar('X', str, float, bound=Employee)
|
||||||
|
|
||||||
|
def test_no_bivariant(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
TypeVar('T', covariant=True, contravariant=True)
|
||||||
|
|
||||||
|
|
||||||
class UnionTests(BaseTestCase):
|
class UnionTests(BaseTestCase):
|
||||||
|
|
||||||
|
@ -254,6 +258,11 @@ class UnionTests(BaseTestCase):
|
||||||
self.assertEqual(repr(u), 'typing.Union[%s.Employee, int]' % __name__)
|
self.assertEqual(repr(u), 'typing.Union[%s.Employee, int]' % __name__)
|
||||||
u = Union[int, Employee]
|
u = Union[int, Employee]
|
||||||
self.assertEqual(repr(u), 'typing.Union[int, %s.Employee]' % __name__)
|
self.assertEqual(repr(u), 'typing.Union[int, %s.Employee]' % __name__)
|
||||||
|
T = TypeVar('T')
|
||||||
|
u = Union[T, int][int]
|
||||||
|
self.assertEqual(repr(u), repr(int))
|
||||||
|
u = Union[List[int], int]
|
||||||
|
self.assertEqual(repr(u), 'typing.Union[typing.List[int], int]')
|
||||||
|
|
||||||
def test_cannot_subclass(self):
|
def test_cannot_subclass(self):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
|
@ -304,6 +313,15 @@ class UnionTests(BaseTestCase):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
isinstance(42, Union[int, str])
|
isinstance(42, Union[int, str])
|
||||||
|
|
||||||
|
def test_no_eval_union(self):
|
||||||
|
u = Union[int, str]
|
||||||
|
def f(x: u): ...
|
||||||
|
self.assertIs(get_type_hints(f)['x'], u)
|
||||||
|
|
||||||
|
def test_function_repr_union(self):
|
||||||
|
def fun() -> int: ...
|
||||||
|
self.assertEqual(repr(Union[fun, int]), 'typing.Union[fun, int]')
|
||||||
|
|
||||||
def test_union_str_pattern(self):
|
def test_union_str_pattern(self):
|
||||||
# Shouldn't crash; see http://bugs.python.org/issue25390
|
# Shouldn't crash; see http://bugs.python.org/issue25390
|
||||||
A = Union[str, Pattern]
|
A = Union[str, Pattern]
|
||||||
|
@ -401,6 +419,8 @@ class CallableTests(BaseTestCase):
|
||||||
Callable[[()], int]
|
Callable[[()], int]
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
Callable[[int, 1], 2]
|
Callable[[int, 1], 2]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Callable[int]
|
||||||
|
|
||||||
def test_callable_instance_works(self):
|
def test_callable_instance_works(self):
|
||||||
def f():
|
def f():
|
||||||
|
@ -546,15 +566,27 @@ class GenericTests(BaseTestCase):
|
||||||
Y[str]
|
Y[str]
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
Y[str, str]
|
Y[str, str]
|
||||||
|
self.assertIsSubclass(SimpleMapping[str, int], SimpleMapping)
|
||||||
|
|
||||||
def test_generic_errors(self):
|
def test_generic_errors(self):
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
S = TypeVar('S')
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
Generic[T]()
|
Generic[T]()
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Generic[T][T]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Generic[T][S]
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
isinstance([], List[int])
|
isinstance([], List[int])
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
issubclass(list, List[int])
|
issubclass(list, List[int])
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
class NewGeneric(Generic): ...
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
class MyGeneric(Generic[T], Generic[S]): ...
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
class MyGeneric(List[T], Generic[S]): ...
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
@ -738,6 +770,53 @@ class GenericTests(BaseTestCase):
|
||||||
self.assertEqual(Union[T, int][GenericMeta], Union[GenericMeta, int])
|
self.assertEqual(Union[T, int][GenericMeta], Union[GenericMeta, int])
|
||||||
self.assertEqual(Callable[..., GenericMeta].__args__, (Ellipsis, GenericMeta))
|
self.assertEqual(Callable[..., GenericMeta].__args__, (Ellipsis, GenericMeta))
|
||||||
|
|
||||||
|
def test_generic_hashes(self):
|
||||||
|
try:
|
||||||
|
from test import mod_generics_cache
|
||||||
|
except ImportError: # for Python 3.4 and previous versions
|
||||||
|
import mod_generics_cache
|
||||||
|
class A(Generic[T]):
|
||||||
|
...
|
||||||
|
|
||||||
|
class B(Generic[T]):
|
||||||
|
class A(Generic[T]):
|
||||||
|
...
|
||||||
|
|
||||||
|
self.assertEqual(A, A)
|
||||||
|
self.assertEqual(mod_generics_cache.A[str], mod_generics_cache.A[str])
|
||||||
|
self.assertEqual(B.A, B.A)
|
||||||
|
self.assertEqual(mod_generics_cache.B.A[B.A[str]],
|
||||||
|
mod_generics_cache.B.A[B.A[str]])
|
||||||
|
|
||||||
|
self.assertNotEqual(A, B.A)
|
||||||
|
self.assertNotEqual(A, mod_generics_cache.A)
|
||||||
|
self.assertNotEqual(A, mod_generics_cache.B.A)
|
||||||
|
self.assertNotEqual(B.A, mod_generics_cache.A)
|
||||||
|
self.assertNotEqual(B.A, mod_generics_cache.B.A)
|
||||||
|
|
||||||
|
self.assertNotEqual(A[str], B.A[str])
|
||||||
|
self.assertNotEqual(A[List[Any]], B.A[List[Any]])
|
||||||
|
self.assertNotEqual(A[str], mod_generics_cache.A[str])
|
||||||
|
self.assertNotEqual(A[str], mod_generics_cache.B.A[str])
|
||||||
|
self.assertNotEqual(B.A[int], mod_generics_cache.A[int])
|
||||||
|
self.assertNotEqual(B.A[List[Any]], mod_generics_cache.B.A[List[Any]])
|
||||||
|
|
||||||
|
self.assertNotEqual(Tuple[A[str]], Tuple[B.A[str]])
|
||||||
|
self.assertNotEqual(Tuple[A[List[Any]]], Tuple[B.A[List[Any]]])
|
||||||
|
self.assertNotEqual(Union[str, A[str]], Union[str, mod_generics_cache.A[str]])
|
||||||
|
self.assertNotEqual(Union[A[str], A[str]],
|
||||||
|
Union[A[str], mod_generics_cache.A[str]])
|
||||||
|
self.assertNotEqual(typing.FrozenSet[A[str]],
|
||||||
|
typing.FrozenSet[mod_generics_cache.B.A[str]])
|
||||||
|
|
||||||
|
if sys.version_info[:2] > (3, 2):
|
||||||
|
self.assertTrue(repr(Tuple[A[str]]).endswith('<locals>.A[str]]'))
|
||||||
|
self.assertTrue(repr(Tuple[B.A[str]]).endswith('<locals>.B.A[str]]'))
|
||||||
|
self.assertTrue(repr(Tuple[mod_generics_cache.A[str]])
|
||||||
|
.endswith('mod_generics_cache.A[str]]'))
|
||||||
|
self.assertTrue(repr(Tuple[mod_generics_cache.B.A[str]])
|
||||||
|
.endswith('mod_generics_cache.B.A[str]]'))
|
||||||
|
|
||||||
def test_extended_generic_rules_eq(self):
|
def test_extended_generic_rules_eq(self):
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
U = TypeVar('U')
|
U = TypeVar('U')
|
||||||
|
@ -835,6 +914,8 @@ class GenericTests(BaseTestCase):
|
||||||
Tuple[Generic[T]]
|
Tuple[Generic[T]]
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
List[typing._Protocol]
|
List[typing._Protocol]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
isinstance(1, Generic)
|
||||||
|
|
||||||
def test_type_erasure_special(self):
|
def test_type_erasure_special(self):
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
@ -853,6 +934,11 @@ class GenericTests(BaseTestCase):
|
||||||
class MyDef(typing.DefaultDict[str, T]): ...
|
class MyDef(typing.DefaultDict[str, T]): ...
|
||||||
self.assertIs(MyDef[int]().__class__, MyDef)
|
self.assertIs(MyDef[int]().__class__, MyDef)
|
||||||
self.assertIs(MyDef[int]().__orig_class__, MyDef[int])
|
self.assertIs(MyDef[int]().__orig_class__, MyDef[int])
|
||||||
|
# ChainMap was added in 3.3
|
||||||
|
if sys.version_info >= (3, 3):
|
||||||
|
class MyChain(typing.ChainMap[str, T]): ...
|
||||||
|
self.assertIs(MyChain[int]().__class__, MyChain)
|
||||||
|
self.assertIs(MyChain[int]().__orig_class__, MyChain[int])
|
||||||
|
|
||||||
def test_all_repr_eq_any(self):
|
def test_all_repr_eq_any(self):
|
||||||
objs = (getattr(typing, el) for el in typing.__all__)
|
objs = (getattr(typing, el) for el in typing.__all__)
|
||||||
|
@ -1203,6 +1289,19 @@ class ForwardRefTests(BaseTestCase):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
isinstance(42, fr)
|
isinstance(42, fr)
|
||||||
|
|
||||||
|
def test_forwardref_subclass_type_error(self):
|
||||||
|
fr = typing._ForwardRef('int')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
issubclass(int, fr)
|
||||||
|
|
||||||
|
def test_forward_equality(self):
|
||||||
|
fr = typing._ForwardRef('int')
|
||||||
|
self.assertEqual(fr, typing._ForwardRef('int'))
|
||||||
|
self.assertNotEqual(List['int'], List[int])
|
||||||
|
|
||||||
|
def test_forward_repr(self):
|
||||||
|
self.assertEqual(repr(List['int']), "typing.List[_ForwardRef('int')]")
|
||||||
|
|
||||||
def test_union_forward(self):
|
def test_union_forward(self):
|
||||||
|
|
||||||
def foo(a: Union['T']):
|
def foo(a: Union['T']):
|
||||||
|
@ -1285,6 +1384,15 @@ class ForwardRefTests(BaseTestCase):
|
||||||
ith = get_type_hints(C().foo)
|
ith = get_type_hints(C().foo)
|
||||||
self.assertEqual(ith, {})
|
self.assertEqual(ith, {})
|
||||||
|
|
||||||
|
def test_no_type_check_no_bases(self):
|
||||||
|
class C:
|
||||||
|
def meth(self, x: int): ...
|
||||||
|
@no_type_check
|
||||||
|
class D(C):
|
||||||
|
c = C
|
||||||
|
# verify that @no_type_check never affects bases
|
||||||
|
self.assertEqual(get_type_hints(C.meth), {'x': int})
|
||||||
|
|
||||||
def test_meta_no_type_check(self):
|
def test_meta_no_type_check(self):
|
||||||
|
|
||||||
@no_type_check_decorator
|
@no_type_check_decorator
|
||||||
|
@ -1401,11 +1509,16 @@ class A:
|
||||||
class B(A):
|
class B(A):
|
||||||
x: ClassVar[Optional['B']] = None
|
x: ClassVar[Optional['B']] = None
|
||||||
y: int
|
y: int
|
||||||
|
b: int
|
||||||
class CSub(B):
|
class CSub(B):
|
||||||
z: ClassVar['CSub'] = B()
|
z: ClassVar['CSub'] = B()
|
||||||
class G(Generic[T]):
|
class G(Generic[T]):
|
||||||
lst: ClassVar[List[T]] = []
|
lst: ClassVar[List[T]] = []
|
||||||
|
|
||||||
|
class NoneAndForward:
|
||||||
|
parent: 'NoneAndForward'
|
||||||
|
meaning: None
|
||||||
|
|
||||||
class CoolEmployee(NamedTuple):
|
class CoolEmployee(NamedTuple):
|
||||||
name: str
|
name: str
|
||||||
cool: int
|
cool: int
|
||||||
|
@ -1419,10 +1532,13 @@ class XMeth(NamedTuple):
|
||||||
def double(self):
|
def double(self):
|
||||||
return 2 * self.x
|
return 2 * self.x
|
||||||
|
|
||||||
class XMethBad(NamedTuple):
|
class XRepr(NamedTuple):
|
||||||
x: int
|
x: int
|
||||||
def _fields(self):
|
y: int = 1
|
||||||
return 'no chance for this'
|
def __str__(self):
|
||||||
|
return f'{self.x} -> {self.y}'
|
||||||
|
def __add__(self, other):
|
||||||
|
return 0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if PY36:
|
if PY36:
|
||||||
|
@ -1431,7 +1547,7 @@ else:
|
||||||
# fake names for the sake of static analysis
|
# fake names for the sake of static analysis
|
||||||
ann_module = ann_module2 = ann_module3 = None
|
ann_module = ann_module2 = ann_module3 = None
|
||||||
A = B = CSub = G = CoolEmployee = CoolEmployeeWithDefault = object
|
A = B = CSub = G = CoolEmployee = CoolEmployeeWithDefault = object
|
||||||
XMeth = XMethBad = object
|
XMeth = XRepr = NoneAndForward = object
|
||||||
|
|
||||||
gth = get_type_hints
|
gth = get_type_hints
|
||||||
|
|
||||||
|
@ -1466,6 +1582,8 @@ class GetTypeHintTests(BaseTestCase):
|
||||||
{'y': Optional[ann_module.C]})
|
{'y': Optional[ann_module.C]})
|
||||||
self.assertEqual(gth(ann_module.S), {'x': str, 'y': str})
|
self.assertEqual(gth(ann_module.S), {'x': str, 'y': str})
|
||||||
self.assertEqual(gth(ann_module.foo), {'x': int})
|
self.assertEqual(gth(ann_module.foo), {'x': int})
|
||||||
|
self.assertEqual(gth(NoneAndForward, globals()),
|
||||||
|
{'parent': NoneAndForward, 'meaning': type(None)})
|
||||||
|
|
||||||
@skipUnless(PY36, 'Python 3.6 required')
|
@skipUnless(PY36, 'Python 3.6 required')
|
||||||
def test_respect_no_type_check(self):
|
def test_respect_no_type_check(self):
|
||||||
|
@ -1482,17 +1600,22 @@ class GetTypeHintTests(BaseTestCase):
|
||||||
class Der(ABase): ...
|
class Der(ABase): ...
|
||||||
self.assertEqual(gth(ABase.meth), {'x': int})
|
self.assertEqual(gth(ABase.meth), {'x': int})
|
||||||
|
|
||||||
def test_get_type_hints_for_builins(self):
|
def test_get_type_hints_for_builtins(self):
|
||||||
# Should not fail for built-in classes and functions.
|
# Should not fail for built-in classes and functions.
|
||||||
self.assertEqual(gth(int), {})
|
self.assertEqual(gth(int), {})
|
||||||
self.assertEqual(gth(type), {})
|
self.assertEqual(gth(type), {})
|
||||||
self.assertEqual(gth(dir), {})
|
self.assertEqual(gth(dir), {})
|
||||||
self.assertEqual(gth(len), {})
|
self.assertEqual(gth(len), {})
|
||||||
|
self.assertEqual(gth(object.__str__), {})
|
||||||
|
self.assertEqual(gth(object().__str__), {})
|
||||||
|
self.assertEqual(gth(str.join), {})
|
||||||
|
|
||||||
def test_previous_behavior(self):
|
def test_previous_behavior(self):
|
||||||
def testf(x, y): ...
|
def testf(x, y): ...
|
||||||
testf.__annotations__['x'] = 'int'
|
testf.__annotations__['x'] = 'int'
|
||||||
self.assertEqual(gth(testf), {'x': int})
|
self.assertEqual(gth(testf), {'x': int})
|
||||||
|
def testg(x: None): ...
|
||||||
|
self.assertEqual(gth(testg), {'x': type(None)})
|
||||||
|
|
||||||
def test_get_type_hints_for_object_with_annotations(self):
|
def test_get_type_hints_for_object_with_annotations(self):
|
||||||
class A: ...
|
class A: ...
|
||||||
|
@ -1506,9 +1629,10 @@ class GetTypeHintTests(BaseTestCase):
|
||||||
self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__),
|
self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__),
|
||||||
{'var': typing.ClassVar[ann_module2.CV]})
|
{'var': typing.ClassVar[ann_module2.CV]})
|
||||||
self.assertEqual(gth(B, globals()),
|
self.assertEqual(gth(B, globals()),
|
||||||
{'y': int, 'x': ClassVar[Optional[B]]})
|
{'y': int, 'x': ClassVar[Optional[B]], 'b': int})
|
||||||
self.assertEqual(gth(CSub, globals()),
|
self.assertEqual(gth(CSub, globals()),
|
||||||
{'z': ClassVar[CSub], 'y': int, 'x': ClassVar[Optional[B]]})
|
{'z': ClassVar[CSub], 'y': int, 'b': int,
|
||||||
|
'x': ClassVar[Optional[B]]})
|
||||||
self.assertEqual(gth(G), {'lst': ClassVar[List[T]]})
|
self.assertEqual(gth(G), {'lst': ClassVar[List[T]]})
|
||||||
|
|
||||||
|
|
||||||
|
@ -1628,6 +1752,11 @@ class CollectionsAbcTests(BaseTestCase):
|
||||||
|
|
||||||
def test_deque(self):
|
def test_deque(self):
|
||||||
self.assertIsSubclass(collections.deque, typing.Deque)
|
self.assertIsSubclass(collections.deque, typing.Deque)
|
||||||
|
class MyDeque(typing.Deque[int]): ...
|
||||||
|
self.assertIsInstance(MyDeque(), collections.deque)
|
||||||
|
|
||||||
|
def test_counter(self):
|
||||||
|
self.assertIsSubclass(collections.Counter, typing.Counter)
|
||||||
|
|
||||||
def test_set(self):
|
def test_set(self):
|
||||||
self.assertIsSubclass(set, typing.Set)
|
self.assertIsSubclass(set, typing.Set)
|
||||||
|
@ -1680,13 +1809,10 @@ class CollectionsAbcTests(BaseTestCase):
|
||||||
self.assertIsSubclass(MyDict, dict)
|
self.assertIsSubclass(MyDict, dict)
|
||||||
self.assertNotIsSubclass(dict, MyDict)
|
self.assertNotIsSubclass(dict, MyDict)
|
||||||
|
|
||||||
def test_no_defaultdict_instantiation(self):
|
def test_defaultdict_instantiation(self):
|
||||||
with self.assertRaises(TypeError):
|
self.assertIs(type(typing.DefaultDict()), collections.defaultdict)
|
||||||
typing.DefaultDict()
|
self.assertIs(type(typing.DefaultDict[KT, VT]()), collections.defaultdict)
|
||||||
with self.assertRaises(TypeError):
|
self.assertIs(type(typing.DefaultDict[str, int]()), collections.defaultdict)
|
||||||
typing.DefaultDict[KT, VT]()
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
typing.DefaultDict[str, int]()
|
|
||||||
|
|
||||||
def test_defaultdict_subclass(self):
|
def test_defaultdict_subclass(self):
|
||||||
|
|
||||||
|
@ -1699,13 +1825,49 @@ class CollectionsAbcTests(BaseTestCase):
|
||||||
self.assertIsSubclass(MyDefDict, collections.defaultdict)
|
self.assertIsSubclass(MyDefDict, collections.defaultdict)
|
||||||
self.assertNotIsSubclass(collections.defaultdict, MyDefDict)
|
self.assertNotIsSubclass(collections.defaultdict, MyDefDict)
|
||||||
|
|
||||||
def test_no_deque_instantiation(self):
|
@skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3')
|
||||||
with self.assertRaises(TypeError):
|
def test_chainmap_instantiation(self):
|
||||||
typing.Deque()
|
self.assertIs(type(typing.ChainMap()), collections.ChainMap)
|
||||||
with self.assertRaises(TypeError):
|
self.assertIs(type(typing.ChainMap[KT, VT]()), collections.ChainMap)
|
||||||
typing.Deque[T]()
|
self.assertIs(type(typing.ChainMap[str, int]()), collections.ChainMap)
|
||||||
with self.assertRaises(TypeError):
|
class CM(typing.ChainMap[KT, VT]): ...
|
||||||
typing.Deque[int]()
|
self.assertIs(type(CM[int, str]()), CM)
|
||||||
|
|
||||||
|
@skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3')
|
||||||
|
def test_chainmap_subclass(self):
|
||||||
|
|
||||||
|
class MyChainMap(typing.ChainMap[str, int]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
cm = MyChainMap()
|
||||||
|
self.assertIsInstance(cm, MyChainMap)
|
||||||
|
|
||||||
|
self.assertIsSubclass(MyChainMap, collections.ChainMap)
|
||||||
|
self.assertNotIsSubclass(collections.ChainMap, MyChainMap)
|
||||||
|
|
||||||
|
def test_deque_instantiation(self):
|
||||||
|
self.assertIs(type(typing.Deque()), collections.deque)
|
||||||
|
self.assertIs(type(typing.Deque[T]()), collections.deque)
|
||||||
|
self.assertIs(type(typing.Deque[int]()), collections.deque)
|
||||||
|
class D(typing.Deque[T]): ...
|
||||||
|
self.assertIs(type(D[int]()), D)
|
||||||
|
|
||||||
|
def test_counter_instantiation(self):
|
||||||
|
self.assertIs(type(typing.Counter()), collections.Counter)
|
||||||
|
self.assertIs(type(typing.Counter[T]()), collections.Counter)
|
||||||
|
self.assertIs(type(typing.Counter[int]()), collections.Counter)
|
||||||
|
class C(typing.Counter[T]): ...
|
||||||
|
self.assertIs(type(C[int]()), C)
|
||||||
|
|
||||||
|
def test_counter_subclass_instantiation(self):
|
||||||
|
|
||||||
|
class MyCounter(typing.Counter[int]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
d = MyCounter()
|
||||||
|
self.assertIsInstance(d, MyCounter)
|
||||||
|
self.assertIsInstance(d, typing.Counter)
|
||||||
|
self.assertIsInstance(d, collections.Counter)
|
||||||
|
|
||||||
def test_no_set_instantiation(self):
|
def test_no_set_instantiation(self):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
|
@ -2018,6 +2180,14 @@ class NamedTupleTests(BaseTestCase):
|
||||||
collections.OrderedDict([('name', str), ('id', int)]))
|
collections.OrderedDict([('name', str), ('id', int)]))
|
||||||
self.assertIs(Emp._field_types, Emp.__annotations__)
|
self.assertIs(Emp._field_types, Emp.__annotations__)
|
||||||
|
|
||||||
|
def test_namedtuple_pyversion(self):
|
||||||
|
if sys.version_info[:2] < (3, 6):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
NamedTuple('Name', one=int, other=str)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
class NotYet(NamedTuple):
|
||||||
|
whatever = 0
|
||||||
|
|
||||||
@skipUnless(PY36, 'Python 3.6 required')
|
@skipUnless(PY36, 'Python 3.6 required')
|
||||||
def test_annotation_usage(self):
|
def test_annotation_usage(self):
|
||||||
tim = CoolEmployee('Tim', 9000)
|
tim = CoolEmployee('Tim', 9000)
|
||||||
|
@ -2055,10 +2225,18 @@ class NonDefaultAfterDefault(NamedTuple):
|
||||||
|
|
||||||
@skipUnless(PY36, 'Python 3.6 required')
|
@skipUnless(PY36, 'Python 3.6 required')
|
||||||
def test_annotation_usage_with_methods(self):
|
def test_annotation_usage_with_methods(self):
|
||||||
self.assertEquals(XMeth(1).double(), 2)
|
self.assertEqual(XMeth(1).double(), 2)
|
||||||
self.assertEquals(XMeth(42).x, XMeth(42)[0])
|
self.assertEqual(XMeth(42).x, XMeth(42)[0])
|
||||||
self.assertEquals(XMethBad(1)._fields, ('x',))
|
self.assertEqual(str(XRepr(42)), '42 -> 1')
|
||||||
self.assertEquals(XMethBad(1).__annotations__, {'x': int})
|
self.assertEqual(XRepr(1, 2) + XRepr(3), 0)
|
||||||
|
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
exec("""
|
||||||
|
class XMethBad(NamedTuple):
|
||||||
|
x: int
|
||||||
|
def _fields(self):
|
||||||
|
return 'no chance for this'
|
||||||
|
""")
|
||||||
|
|
||||||
@skipUnless(PY36, 'Python 3.6 required')
|
@skipUnless(PY36, 'Python 3.6 required')
|
||||||
def test_namedtuple_keyword_usage(self):
|
def test_namedtuple_keyword_usage(self):
|
||||||
|
@ -2138,6 +2316,12 @@ class RETests(BaseTestCase):
|
||||||
Pattern[Union[str, bytes]]
|
Pattern[Union[str, bytes]]
|
||||||
Match[Union[bytes, str]]
|
Match[Union[bytes, str]]
|
||||||
|
|
||||||
|
def test_alias_equality(self):
|
||||||
|
self.assertEqual(Pattern[str], Pattern[str])
|
||||||
|
self.assertNotEqual(Pattern[str], Pattern[bytes])
|
||||||
|
self.assertNotEqual(Pattern[str], Match[str])
|
||||||
|
self.assertNotEqual(Pattern[str], str)
|
||||||
|
|
||||||
def test_errors(self):
|
def test_errors(self):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
# Doesn't fit AnyStr.
|
# Doesn't fit AnyStr.
|
||||||
|
@ -2152,6 +2336,9 @@ class RETests(BaseTestCase):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
# We don't support isinstance().
|
# We don't support isinstance().
|
||||||
isinstance(42, Pattern[str])
|
isinstance(42, Pattern[str])
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
# We don't support issubclass().
|
||||||
|
issubclass(Pattern[bytes], Pattern[str])
|
||||||
|
|
||||||
def test_repr(self):
|
def test_repr(self):
|
||||||
self.assertEqual(repr(Pattern), 'Pattern[~AnyStr]')
|
self.assertEqual(repr(Pattern), 'Pattern[~AnyStr]')
|
||||||
|
|
138
Lib/typing.py
138
Lib/typing.py
|
@ -10,6 +10,12 @@ try:
|
||||||
import collections.abc as collections_abc
|
import collections.abc as collections_abc
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import collections as collections_abc # Fallback for PY3.2.
|
import collections as collections_abc # Fallback for PY3.2.
|
||||||
|
try:
|
||||||
|
from types import SlotWrapperType, MethodWrapperType, MethodDescriptorType
|
||||||
|
except ImportError:
|
||||||
|
SlotWrapperType = type(object.__init__)
|
||||||
|
MethodWrapperType = type(object().__str__)
|
||||||
|
MethodDescriptorType = type(str.join)
|
||||||
|
|
||||||
|
|
||||||
# Please keep __all__ alphabetized within each category.
|
# Please keep __all__ alphabetized within each category.
|
||||||
|
@ -62,6 +68,7 @@ __all__ = [
|
||||||
'SupportsRound',
|
'SupportsRound',
|
||||||
|
|
||||||
# Concrete collection types.
|
# Concrete collection types.
|
||||||
|
'Counter',
|
||||||
'Deque',
|
'Deque',
|
||||||
'Dict',
|
'Dict',
|
||||||
'DefaultDict',
|
'DefaultDict',
|
||||||
|
@ -849,19 +856,6 @@ def _next_in_mro(cls):
|
||||||
return next_in_mro
|
return next_in_mro
|
||||||
|
|
||||||
|
|
||||||
def _valid_for_check(cls):
|
|
||||||
"""An internal helper to prohibit isinstance([1], List[str]) etc."""
|
|
||||||
if cls is Generic:
|
|
||||||
raise TypeError("Class %r cannot be used with class "
|
|
||||||
"or instance checks" % cls)
|
|
||||||
if (
|
|
||||||
cls.__origin__ is not None and
|
|
||||||
sys._getframe(3).f_globals['__name__'] not in ['abc', 'functools']
|
|
||||||
):
|
|
||||||
raise TypeError("Parameterized generics cannot be used with class "
|
|
||||||
"or instance checks")
|
|
||||||
|
|
||||||
|
|
||||||
def _make_subclasshook(cls):
|
def _make_subclasshook(cls):
|
||||||
"""Construct a __subclasshook__ callable that incorporates
|
"""Construct a __subclasshook__ callable that incorporates
|
||||||
the associated __extra__ class in subclass checks performed
|
the associated __extra__ class in subclass checks performed
|
||||||
|
@ -872,7 +866,6 @@ def _make_subclasshook(cls):
|
||||||
# Registered classes need not be checked here because
|
# Registered classes need not be checked here because
|
||||||
# cls and its extra share the same _abc_registry.
|
# cls and its extra share the same _abc_registry.
|
||||||
def __extrahook__(subclass):
|
def __extrahook__(subclass):
|
||||||
_valid_for_check(cls)
|
|
||||||
res = cls.__extra__.__subclasshook__(subclass)
|
res = cls.__extra__.__subclasshook__(subclass)
|
||||||
if res is not NotImplemented:
|
if res is not NotImplemented:
|
||||||
return res
|
return res
|
||||||
|
@ -887,7 +880,6 @@ def _make_subclasshook(cls):
|
||||||
else:
|
else:
|
||||||
# For non-ABC extras we'll just call issubclass().
|
# For non-ABC extras we'll just call issubclass().
|
||||||
def __extrahook__(subclass):
|
def __extrahook__(subclass):
|
||||||
_valid_for_check(cls)
|
|
||||||
if cls.__extra__ and issubclass(subclass, cls.__extra__):
|
if cls.__extra__ and issubclass(subclass, cls.__extra__):
|
||||||
return True
|
return True
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
@ -974,6 +966,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
||||||
# remove bare Generic from bases if there are other generic bases
|
# remove bare Generic from bases if there are other generic bases
|
||||||
if any(isinstance(b, GenericMeta) and b is not Generic for b in bases):
|
if any(isinstance(b, GenericMeta) and b is not Generic for b in bases):
|
||||||
bases = tuple(b for b in bases if b is not Generic)
|
bases = tuple(b for b in bases if b is not Generic)
|
||||||
|
namespace.update({'__origin__': origin, '__extra__': extra})
|
||||||
self = super().__new__(cls, name, bases, namespace, _root=True)
|
self = super().__new__(cls, name, bases, namespace, _root=True)
|
||||||
|
|
||||||
self.__parameters__ = tvars
|
self.__parameters__ = tvars
|
||||||
|
@ -982,8 +975,6 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
||||||
self.__args__ = tuple(... if a is _TypingEllipsis else
|
self.__args__ = tuple(... if a is _TypingEllipsis else
|
||||||
() if a is _TypingEmpty else
|
() if a is _TypingEmpty else
|
||||||
a for a in args) if args else None
|
a for a in args) if args else None
|
||||||
self.__origin__ = origin
|
|
||||||
self.__extra__ = extra
|
|
||||||
# Speed hack (https://github.com/python/typing/issues/196).
|
# Speed hack (https://github.com/python/typing/issues/196).
|
||||||
self.__next_in_mro__ = _next_in_mro(self)
|
self.__next_in_mro__ = _next_in_mro(self)
|
||||||
# Preserve base classes on subclassing (__bases__ are type erased now).
|
# Preserve base classes on subclassing (__bases__ are type erased now).
|
||||||
|
@ -994,20 +985,56 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
||||||
# with issubclass() and isinstance() in the same way as their
|
# with issubclass() and isinstance() in the same way as their
|
||||||
# collections.abc counterparts (e.g., isinstance([], Iterable)).
|
# collections.abc counterparts (e.g., isinstance([], Iterable)).
|
||||||
if (
|
if (
|
||||||
# allow overriding
|
|
||||||
'__subclasshook__' not in namespace and extra or
|
'__subclasshook__' not in namespace and extra or
|
||||||
hasattr(self.__subclasshook__, '__name__') and
|
# allow overriding
|
||||||
self.__subclasshook__.__name__ == '__extrahook__'
|
getattr(self.__subclasshook__, '__name__', '') == '__extrahook__'
|
||||||
):
|
):
|
||||||
self.__subclasshook__ = _make_subclasshook(self)
|
self.__subclasshook__ = _make_subclasshook(self)
|
||||||
if isinstance(extra, abc.ABCMeta):
|
if isinstance(extra, abc.ABCMeta):
|
||||||
self._abc_registry = extra._abc_registry
|
self._abc_registry = extra._abc_registry
|
||||||
|
self._abc_cache = extra._abc_cache
|
||||||
|
elif origin is not None:
|
||||||
|
self._abc_registry = origin._abc_registry
|
||||||
|
self._abc_cache = origin._abc_cache
|
||||||
|
|
||||||
if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2.
|
if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2.
|
||||||
self.__qualname__ = origin.__qualname__
|
self.__qualname__ = origin.__qualname__
|
||||||
self.__tree_hash__ = hash(self._subs_tree()) if origin else hash((self.__name__,))
|
self.__tree_hash__ = (hash(self._subs_tree()) if origin else
|
||||||
|
super(GenericMeta, self).__hash__())
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
# _abc_negative_cache and _abc_negative_cache_version
|
||||||
|
# realised as descriptors, since GenClass[t1, t2, ...] always
|
||||||
|
# share subclass info with GenClass.
|
||||||
|
# This is an important memory optimization.
|
||||||
|
@property
|
||||||
|
def _abc_negative_cache(self):
|
||||||
|
if isinstance(self.__extra__, abc.ABCMeta):
|
||||||
|
return self.__extra__._abc_negative_cache
|
||||||
|
return _gorg(self)._abc_generic_negative_cache
|
||||||
|
|
||||||
|
@_abc_negative_cache.setter
|
||||||
|
def _abc_negative_cache(self, value):
|
||||||
|
if self.__origin__ is None:
|
||||||
|
if isinstance(self.__extra__, abc.ABCMeta):
|
||||||
|
self.__extra__._abc_negative_cache = value
|
||||||
|
else:
|
||||||
|
self._abc_generic_negative_cache = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _abc_negative_cache_version(self):
|
||||||
|
if isinstance(self.__extra__, abc.ABCMeta):
|
||||||
|
return self.__extra__._abc_negative_cache_version
|
||||||
|
return _gorg(self)._abc_generic_negative_cache_version
|
||||||
|
|
||||||
|
@_abc_negative_cache_version.setter
|
||||||
|
def _abc_negative_cache_version(self, value):
|
||||||
|
if self.__origin__ is None:
|
||||||
|
if isinstance(self.__extra__, abc.ABCMeta):
|
||||||
|
self.__extra__._abc_negative_cache_version = value
|
||||||
|
else:
|
||||||
|
self._abc_generic_negative_cache_version = value
|
||||||
|
|
||||||
def _get_type_vars(self, tvars):
|
def _get_type_vars(self, tvars):
|
||||||
if self.__origin__ and self.__parameters__:
|
if self.__origin__ and self.__parameters__:
|
||||||
_get_type_vars(self.__parameters__, tvars)
|
_get_type_vars(self.__parameters__, tvars)
|
||||||
|
@ -1095,8 +1122,10 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
||||||
_check_generic(self, params)
|
_check_generic(self, params)
|
||||||
tvars = _type_vars(params)
|
tvars = _type_vars(params)
|
||||||
args = params
|
args = params
|
||||||
|
|
||||||
|
prepend = (self,) if self.__origin__ is None else ()
|
||||||
return self.__class__(self.__name__,
|
return self.__class__(self.__name__,
|
||||||
self.__bases__,
|
prepend + self.__bases__,
|
||||||
_no_slots_copy(self.__dict__),
|
_no_slots_copy(self.__dict__),
|
||||||
tvars=tvars,
|
tvars=tvars,
|
||||||
args=args,
|
args=args,
|
||||||
|
@ -1104,6 +1133,17 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
||||||
extra=self.__extra__,
|
extra=self.__extra__,
|
||||||
orig_bases=self.__orig_bases__)
|
orig_bases=self.__orig_bases__)
|
||||||
|
|
||||||
|
def __subclasscheck__(self, cls):
|
||||||
|
if self.__origin__ is not None:
|
||||||
|
if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']:
|
||||||
|
raise TypeError("Parameterized generics cannot be used with class "
|
||||||
|
"or instance checks")
|
||||||
|
return False
|
||||||
|
if self is Generic:
|
||||||
|
raise TypeError("Class %r cannot be used with class "
|
||||||
|
"or instance checks" % self)
|
||||||
|
return super().__subclasscheck__(cls)
|
||||||
|
|
||||||
def __instancecheck__(self, instance):
|
def __instancecheck__(self, instance):
|
||||||
# Since we extend ABC.__subclasscheck__ and
|
# Since we extend ABC.__subclasscheck__ and
|
||||||
# ABC.__instancecheck__ inlines the cache checking done by the
|
# ABC.__instancecheck__ inlines the cache checking done by the
|
||||||
|
@ -1398,6 +1438,11 @@ def _get_defaults(func):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
_allowed_types = (types.FunctionType, types.BuiltinFunctionType,
|
||||||
|
types.MethodType, types.ModuleType,
|
||||||
|
SlotWrapperType, MethodWrapperType, MethodDescriptorType)
|
||||||
|
|
||||||
|
|
||||||
def get_type_hints(obj, globalns=None, localns=None):
|
def get_type_hints(obj, globalns=None, localns=None):
|
||||||
"""Return type hints for an object.
|
"""Return type hints for an object.
|
||||||
|
|
||||||
|
@ -1452,12 +1497,7 @@ def get_type_hints(obj, globalns=None, localns=None):
|
||||||
hints = getattr(obj, '__annotations__', None)
|
hints = getattr(obj, '__annotations__', None)
|
||||||
if hints is None:
|
if hints is None:
|
||||||
# Return empty annotations for something that _could_ have them.
|
# Return empty annotations for something that _could_ have them.
|
||||||
if (
|
if isinstance(obj, _allowed_types):
|
||||||
isinstance(obj, types.FunctionType) or
|
|
||||||
isinstance(obj, types.BuiltinFunctionType) or
|
|
||||||
isinstance(obj, types.MethodType) or
|
|
||||||
isinstance(obj, types.ModuleType)
|
|
||||||
):
|
|
||||||
return {}
|
return {}
|
||||||
else:
|
else:
|
||||||
raise TypeError('{!r} is not a module, class, method, '
|
raise TypeError('{!r} is not a module, class, method, '
|
||||||
|
@ -1824,8 +1864,7 @@ class Deque(collections.deque, MutableSequence[T], extra=collections.deque):
|
||||||
|
|
||||||
def __new__(cls, *args, **kwds):
|
def __new__(cls, *args, **kwds):
|
||||||
if _geqv(cls, Deque):
|
if _geqv(cls, Deque):
|
||||||
raise TypeError("Type Deque cannot be instantiated; "
|
return collections.deque(*args, **kwds)
|
||||||
"use deque() instead")
|
|
||||||
return _generic_new(collections.deque, cls, *args, **kwds)
|
return _generic_new(collections.deque, cls, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1894,11 +1933,35 @@ class DefaultDict(collections.defaultdict, MutableMapping[KT, VT],
|
||||||
|
|
||||||
def __new__(cls, *args, **kwds):
|
def __new__(cls, *args, **kwds):
|
||||||
if _geqv(cls, DefaultDict):
|
if _geqv(cls, DefaultDict):
|
||||||
raise TypeError("Type DefaultDict cannot be instantiated; "
|
return collections.defaultdict(*args, **kwds)
|
||||||
"use collections.defaultdict() instead")
|
|
||||||
return _generic_new(collections.defaultdict, cls, *args, **kwds)
|
return _generic_new(collections.defaultdict, cls, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
|
class Counter(collections.Counter, Dict[T, int], extra=collections.Counter):
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwds):
|
||||||
|
if _geqv(cls, Counter):
|
||||||
|
return collections.Counter(*args, **kwds)
|
||||||
|
return _generic_new(collections.Counter, cls, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(collections, 'ChainMap'):
|
||||||
|
# ChainMap only exists in 3.3+
|
||||||
|
__all__.append('ChainMap')
|
||||||
|
|
||||||
|
class ChainMap(collections.ChainMap, MutableMapping[KT, VT],
|
||||||
|
extra=collections.ChainMap):
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwds):
|
||||||
|
if _geqv(cls, ChainMap):
|
||||||
|
return collections.ChainMap(*args, **kwds)
|
||||||
|
return _generic_new(collections.ChainMap, cls, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
# Determine what base class to use for Generator.
|
# Determine what base class to use for Generator.
|
||||||
if hasattr(collections_abc, 'Generator'):
|
if hasattr(collections_abc, 'Generator'):
|
||||||
# Sufficiently recent versions of 3.5 have a Generator ABC.
|
# Sufficiently recent versions of 3.5 have a Generator ABC.
|
||||||
|
@ -1975,6 +2038,13 @@ def _make_nmtuple(name, types):
|
||||||
|
|
||||||
_PY36 = sys.version_info[:2] >= (3, 6)
|
_PY36 = sys.version_info[:2] >= (3, 6)
|
||||||
|
|
||||||
|
# attributes prohibited to set in NamedTuple class syntax
|
||||||
|
_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__',
|
||||||
|
'_fields', '_field_defaults', '_field_types',
|
||||||
|
'_make', '_replace', '_asdict')
|
||||||
|
|
||||||
|
_special = ('__module__', '__name__', '__qualname__', '__annotations__')
|
||||||
|
|
||||||
|
|
||||||
class NamedTupleMeta(type):
|
class NamedTupleMeta(type):
|
||||||
|
|
||||||
|
@ -2002,7 +2072,9 @@ class NamedTupleMeta(type):
|
||||||
nm_tpl._field_defaults = defaults_dict
|
nm_tpl._field_defaults = defaults_dict
|
||||||
# update from user namespace without overriding special namedtuple attributes
|
# update from user namespace without overriding special namedtuple attributes
|
||||||
for key in ns:
|
for key in ns:
|
||||||
if not hasattr(nm_tpl, key):
|
if key in _prohibited:
|
||||||
|
raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
|
||||||
|
elif key not in _special and key not in nm_tpl._fields:
|
||||||
setattr(nm_tpl, key, ns[key])
|
setattr(nm_tpl, key, ns[key])
|
||||||
return nm_tpl
|
return nm_tpl
|
||||||
|
|
||||||
|
|
|
@ -227,6 +227,10 @@ Extension Modules
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #28556: Various updates to typing module: typing.Counter, typing.ChainMap,
|
||||||
|
improved ABC caching, etc. Original PRs by Jelle Zijlstra, Ivan Levkivskyi,
|
||||||
|
Manuel Krebber, and Łukasz Langa.
|
||||||
|
|
||||||
- Issue #29100: Fix datetime.fromtimestamp() regression introduced in Python
|
- Issue #29100: Fix datetime.fromtimestamp() regression introduced in Python
|
||||||
3.6.0: check minimum and maximum years.
|
3.6.0: check minimum and maximum years.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue