8000 Refactor Union, Tuple, and Callable by ilevkivskyi · Pull Request #308 · python/typing · GitHub
[go: up one dir, main page]

Skip to content

Refactor Union, Tuple, and Callable #308

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 39 commits into from
Oct 29, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e8c3ad6
Make Tuple[T, S] subcriptable and subclassable
ilevkivskyi Oct 23, 2016
cfcf81f
Make Callable[..., T] subscriptable and subclassable
ilevkivskyi Oct 23, 2016
1368e97
Make Union[T, S] subsctiptable
ilevkivskyi Oct 23, 2016
7ee4a1a
Disable plain Union and Optional as type arguments
ilevkivskyi Oct 23, 2016
5fdceba
Implement __eq__ for GenericMeta
ilevkivskyi Oct 23, 2016
f339586
Implement __eq__ for Unions
ilevkivskyi Oct 23, 2016
26f50a3
Refactor common code in __eq__ and __repr__
ilevkivskyi Oct 23, 2016
65de907
Remove redundant code
ilevkivskyi Oct 23, 2016
897de4e
Fix minor bugs + mini-refactoring
ilevkivskyi Oct 23, 2016
c687ffa
Fix repr of Callable subclasses
ilevkivskyi Oct 23, 2016
582a37f
Don't be too pedantic in GenericMeta._eval_type
ilevkivskyi Oct 23, 2016
02e6b14
Simplify __hash__ and factor out orig_chain
ilevkivskyi Oct 23, 2016
8d802cc
Remove outdated tests + be more pedanctic in _Union._eval_type
ilevkivskyi Oct 23, 2016
4e2527c
Add tests
ilevkivskyi Oct 23, 2016
92da67f
Factor out _subs_tree for Union and Generic
ilevkivskyi Oct 24, 2016
7563c92
Prohibit plain Generic[T] as type argument
ilevkivskyi Oct 24, 2016
f33545b
Correct a comment
ilevkivskyi Oct 24, 2016
4a996b2
Erase but preserve type also for Callable
ilevkivskyi Oct 24, 2016
6564742
Erase but preserve type for all generics
ilevkivskyi Oct 24, 2016
184f089
Restore original __next_in_mro__ and use more precise bases
Oct 24, 2016
b613325
Preserve __qualname__ on subscription
Oct 24, 2016
011b9d8
Add tests and factor common stuff for Union
ilevkivskyi Oct 24, 2016
52712a6
Add even more tests
ilevkivskyi Oct 24, 2016
2641151
One more test + minor code simplification
ilevkivskyi Oct 24, 2016
9f1fb98
Polishing code
ilevkivskyi Oct 25, 2016
279a67e
Minor changes
ilevkivskyi Oct 25, 2016
f173fde
Precalculate hash + refactor + more tests
ilevkivskyi Oct 25, 2016
819d472
Simplify repr for Union
ilevkivskyi Oct 25, 2016
e6f19a8
Add big test for __eq__, __repr__, and Any substitution
ilevkivskyi Oct 25, 2016
deed806
Add test to illustrate substitution
ilevkivskyi Oct 25, 2016
f6382a7
Minor change in tests
ilevkivskyi Oct 25, 2016
9bc7374
Add/expand comments and docstrings
ilevkivskyi Oct 25, 2016
c2724ce
Fix the __qualname__ in PY 3.2
ilevkivskyi Oct 25, 2016
fd3e344
Add tests to Python 2
ilevkivskyi Oct 25, 2016
cd7bbe2
Modify Union in Python 2
ilevkivskyi Oct 25, 2016
068ab5e
Update Python 2 tests
ilevkivskyi Oct 25, 2016
09c4797
Change remaining parts in Python 2
ilevkivskyi Oct 25, 2016
60303f7
Fix final test in PY 2
ilevkivskyi Oct 25, 2016
ea5dd05
Also call super().__init__ in PY 2 Generic
ilevkivskyi Oct 26, 2016
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
Prev Previous commit
Next Next commit
Add tests and factor common stuff for Union
  • Loading branch information
ilevkivskyi committed Oct 24, 2016
commit 011b9d86c7eb65a41a9f2c7a67df3fad25722fef
22 changes: 22 additions & 0 deletions src/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ def test_union_unique(self):
self.assertNotEqual(Union[X, int], Union[X])
self.assertNotEqual(Union[X, int], Union[int])
self.assertEqual(Union[X, int].__args__, (X, int))
self.assertEqual(Union[X, int].__parameters__, (X,))
self.assertIs(Union[X, int].__origin__, Union)

def test_union_constrained(self):
A = TypeVar('A', str, bytes)
Expand Down Expand Up @@ -692,18 +694,27 @@ class D(C, List[T][U][V]): ...
self.assertEqual(D.__orig_bases__, (C, List[T][U][V]))

def test_extended_generic_rules_eq(self):
T = TypeVar('T')
U = TypeVar('U')
self.assertEqual(Tuple[T, T][int], Tuple[int, int])
self.assertEqual(Union[T, int][int], int)
self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str])
class Base: ...
class Derived(Base): ...
self.assertEqual(Union[T, Base][Derived], Base)
self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT])
self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]])

def test_extended_generic_rules_repr(self):
T = TypeVar('T')
self.assertEqual(repr(Union[Tuple, Callable]).replace('typing.', ''),
'Union[Tuple, Callable]')
self.assertEqual(repr(Union[Tuple, Tuple[int]]).replace('typing.', ''),
'Tuple')
self.assertEqual(repr(Callable[..., Optional[T]][int]).replace('typing.', ''),
'Callable[..., Union[int, NoneType]]')
self.assertEqual(repr(Callable[[], List[T]][int]).replace('typing.', ''),
'Callable[[], List[int]]')

def test_extended_generic_rules_subclassing(self):
class T1(Tuple[T, KT]): ...
Expand Down Expand Up @@ -737,6 +748,17 @@ def test_fail_with_bare_union(self):
Tuple[Optional]
with self.assertRaises(TypeError):
ClassVar[ClassVar]
with self.assertRaises(TypeError):
List[ClassVar[int]]

def test_fail_with_bare_generic(self):
T = TypeVar('T')
with self.assertRaises(TypeError):
List[Generic]
with self.assertRaises(TypeError):
Tuple[Generic[T]]
with self.assertRaises(TypeError):
List[typing._Protocol]

def test_pickle(self):
global C # pickle wants to reference the class by name
Expand Down
78 changes: 36 additions & 42 deletions src/typing.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -508,18 +508,16 @@ def __subclasscheck__(self, cls):
AnyStr = TypeVar('AnyStr', bytes, str)


def flat_union(parameters):
def _remove_dups_flatten(parameters):
# Flatten out Union[Union[...], ...].
params = []
for p in parameters:
if isinstance(p, _Union) and p.__origin__ is Union:
params.extend(p.__args__)
elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union:
params.extend(p[1:])
else:
params.append(p)
return params


def remove_dups(params):
# Weed out strict duplicates, preserving the first of each occurrence.
all_params = set(params)
if len(all_params) < len(params):
Expand All @@ -530,7 +528,32 @@ def remove_dups(params):
all_params.remove(t)
params = new_params
assert not all_params, all_params
return params
# Weed out subclasses.
# E.g. Union[int, Employee, Manager] == Union[int, Employee].
# If object is present it will be sole survivor among proper classes.
# Never discard type variables.
# (In particular, Union[str, AnyStr] != AnyStr.)
all_params = set(params)
for t1 in params:
if not isinstance(t1, type):
continue
if any(isinstance(t2, type) and issubclass(t1, t2)
for t2 in all_params - {t1}
if not (isinstance(t2, GenericMeta) and
t2.__origin__ is not None)):
all_params.remove(t1)
return tuple(t for t in params if t in all_params)


def _check_generic(cls, parameters):
# Check correct count for generic parameters for a cls
if not cls.__parameters__:
raise TypeError("%s is not a generic class" % repr(cls))
alen = len(parameters)
elen = len(cls.__parameters__)
if alen != elen:
raise TypeError("Too %s parameters for %s; actual %s, expected %s" %
("many" if alen > elen else "few", repr(cls), alen, elen))


def _tp_cache(func):
Expand Down Expand Up @@ -607,27 +630,12 @@ def __new__(cls, parameters=None, origin=None, *args, _root=False):
self.__args__ = parameters
self.__origin__ = origin
return self
params = flat_union(parameters)
params = remove_dups(params)
# Weed out subclasses.
# E.g. Union[int, Employee, Manager] == Union[int, Employee].
# If object is present it will be sole survivor among proper classes.
# Never discard type variables.
# (In particular, Union[str, AnyStr] != AnyStr.)
all_params = set(params)
for t1 in params:
if not isinstance(t1, type):
continue
if any(isinstance(t2, type) and issubclass(t1, t2)
for t2 in all_params - {t1}
if not (isinstance(t2, GenericMeta) and
t2.__origin__ is not None)):
all_params.remove(t1)
params = _remove_dups_flatten(parameters)
# It's not a union if there's only one type left.
if len(all_params) == 1:
return all_params.pop()
if len(params) == 1:
return params[0]
self.__origin__ = origin
self.__args__ = tuple(t for t in params if t in all_params)
self.__args__ = params
self.__parameters__ = _type_vars(self.__args__)
return self

Expand Down Expand Up @@ -676,21 +684,14 @@ def __getitem__(self, parameters):
msg = "Parameters to generic types must be types."
parameters = tuple(_type_check(p, msg) for p in parameters)
if self is not Union:
if not self.__parameters__:
raise TypeError("%s is not a generic class" % repr(self))
alen = len(parameters)
elen = len(self.__parameters__)
if alen != elen:
raise TypeError(
"Too %s parameters for %s; actual %s, expected %s" %
("many" if alen > elen else "few", repr(self), alen, elen))
_check_generic(self, parameters)
return self.__class__(parameters, origin=self, _root=True)

def _subs_tree(self, tvars=None, args=None):
tree_args = _subs_tree(self, tvars, args)
if tree_args is Union:
return (Union,)
tree_args = tuple(remove_dups(flat_union(tree_args)))
tree_args = _remove_dups_flatten(tree_args)
if len(tree_args) == 1:
return tree_args[0] # Union of a single type is that type
return (Union,) + tree_args
Expand Down Expand Up @@ -1010,14 +1011,7 @@ def __getitem__(self, params):
repr(self))
else:
# Subscripting a regular Generic subclass.
if not self.__parameters__:
raise TypeError("%s is not a generic class" % repr(self))
alen = len(params)
elen = len(self.__parameters__)
if alen != elen:
raise TypeError(
"Too %s parameters for %s; actual %s, expected %s" %
("many" if alen > elen else "few", repr(self), alen, elen))
_check_generic(self, params)
tvars = _type_vars(params)
args = params
return self.__class__(self.__name__,
Expand Down
0