bpo-28556: Don't simplify unions at runtime (GH-6841) (GH-6979)

(cherry picked from commit f65e31fee3)

Co-authored-by: Ivan Levkivskyi <levkivskyi@gmail.com>
This commit is contained in:
Miss Islington (bot) 2018-05-18 16:27:14 -07:00 committed by Ivan Levkivskyi
parent abde17e663
commit 09ca5906b7
4 changed files with 21 additions and 45 deletions

View file

@ -961,16 +961,15 @@ The module defines the following classes, functions and decorators:
Union[int, str] == Union[str, int] Union[int, str] == Union[str, int]
* When a class and its subclass are present, the latter is skipped, e.g.::
Union[int, object] == object
* You cannot subclass or instantiate a union. * You cannot subclass or instantiate a union.
* You cannot write ``Union[X][Y]``. * You cannot write ``Union[X][Y]``.
* You can use ``Optional[X]`` as a shorthand for ``Union[X, None]``. * You can use ``Optional[X]`` as a shorthand for ``Union[X, None]``.
.. versionchanged:: 3.7
Don't remove explicit subclasses from unions at runtime.
.. data:: Optional .. data:: Optional
Optional type. Optional type.

View file

@ -253,10 +253,11 @@ class UnionTests(BaseTestCase):
def test_union_object(self): def test_union_object(self):
u = Union[object] u = Union[object]
self.assertEqual(u, object) self.assertEqual(u, object)
u = Union[int, object] u1 = Union[int, object]
self.assertEqual(u, object) u2 = Union[object, int]
u = Union[object, int] self.assertEqual(u1, u2)
self.assertEqual(u, object) self.assertNotEqual(u1, object)
self.assertNotEqual(u2, object)
def test_unordered(self): def test_unordered(self):
u1 = Union[int, float] u1 = Union[int, float]
@ -267,13 +268,11 @@ class UnionTests(BaseTestCase):
t = Union[Employee] t = Union[Employee]
self.assertIs(t, Employee) self.assertIs(t, Employee)
def test_base_class_disappears(self): def test_base_class_kept(self):
u = Union[Employee, Manager, int]
self.assertEqual(u, Union[int, Employee])
u = Union[Manager, int, Employee]
self.assertEqual(u, Union[int, Employee])
u = Union[Employee, Manager] u = Union[Employee, Manager]
self.assertIs(u, Employee) self.assertNotEqual(u, Employee)
self.assertIn(Employee, u.__args__)
self.assertIn(Manager, u.__args__)
def test_union_union(self): def test_union_union(self):
u = Union[int, float] u = Union[int, float]
@ -317,7 +316,8 @@ class UnionTests(BaseTestCase):
def test_union_generalization(self): def test_union_generalization(self):
self.assertFalse(Union[str, typing.Iterable[int]] == str) self.assertFalse(Union[str, typing.Iterable[int]] == str)
self.assertFalse(Union[str, typing.Iterable[int]] == typing.Iterable[int]) self.assertFalse(Union[str, typing.Iterable[int]] == typing.Iterable[int])
self.assertTrue(Union[str, typing.Iterable] == typing.Iterable) self.assertIn(str, Union[str, typing.Iterable[int]].__args__)
self.assertIn(typing.Iterable[int], Union[str, typing.Iterable[int]].__args__)
def test_union_compare_other(self): def test_union_compare_other(self):
self.assertNotEqual(Union, object) self.assertNotEqual(Union, object)
@ -917,7 +917,7 @@ class GenericTests(BaseTestCase):
self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str]) self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str])
class Base: ... class Base: ...
class Derived(Base): ... class Derived(Base): ...
self.assertEqual(Union[T, Base][Derived], Base) self.assertEqual(Union[T, Base][Union[Base, Derived]], Union[Base, Derived])
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
Union[T, int][1] Union[T, int][1]

View file

@ -206,8 +206,8 @@ def _check_generic(cls, parameters):
def _remove_dups_flatten(parameters): def _remove_dups_flatten(parameters):
"""An internal helper for Union creation and substitution: flatten Union's """An internal helper for Union creation and substitution: flatten Unions
among parameters, then remove duplicates and strict subclasses. among parameters, then remove duplicates.
""" """
# Flatten out Union[Union[...], ...]. # Flatten out Union[Union[...], ...].
params = [] params = []
@ -228,20 +228,7 @@ def _remove_dups_flatten(parameters):
all_params.remove(t) all_params.remove(t)
params = new_params params = new_params
assert not all_params, all_params assert not all_params, all_params
# Weed out subclasses. return tuple(params)
# 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) or
isinstance(t2, _GenericAlias) and t2._special) and issubclass(t1, t2)
for t2 in all_params - {t1}):
all_params.remove(t1)
return tuple(t for t in params if t in all_params)
_cleanups = [] _cleanups = []
@ -440,19 +427,6 @@ Union = _SpecialForm('Union', doc=
Union[int, str] == Union[str, int] Union[int, str] == Union[str, int]
- When two arguments have a subclass relationship, the least
derived argument is kept, e.g.::
class Employee: pass
class Manager(Employee): pass
Union[int, Employee, Manager] == Union[int, Employee]
Union[Manager, int, Employee] == Union[int, Employee]
Union[Employee, Manager] == Employee
- Similar for object::
Union[int, object] == object
- You cannot subclass or instantiate a union. - You cannot subclass or instantiate a union.
- You can use Optional[X] as a shorthand for Union[X, None]. - You can use Optional[X] as a shorthand for Union[X, None].
""") """)

View file

@ -0,0 +1,3 @@
Do not simplify arguments to `typing.Union`. Now `Union[Manager, Employee]`
is not simplified to `Employee` at runtime. Such simplification previously
caused several bugs and limited possibilities for introspection.