Skip to content

Commit f65e31f

Browse files
authored
bpo-28556: Don't simplify unions at runtime (GH-6841)
1 parent 5634331 commit f65e31f

File tree

4 files changed

+21
-45
lines changed

4 files changed

+21
-45
lines changed

Doc/library/typing.rst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -961,16 +961,15 @@ The module defines the following classes, functions and decorators:
961961

962962
Union[int, str] == Union[str, int]
963963

964-
* When a class and its subclass are present, the latter is skipped, e.g.::
965-
966-
Union[int, object] == object
967-
968964
* You cannot subclass or instantiate a union.
969965

970966
* You cannot write ``Union[X][Y]``.
971967

972968
* You can use ``Optional[X]`` as a shorthand for ``Union[X, None]``.
973969

970+
.. versionchanged:: 3.7
971+
Don't remove explicit subclasses from unions at runtime.
972+
974973
.. data:: Optional
975974

976975
Optional type.

Lib/test/test_typing.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,11 @@ def test_union_any(self):
253253
def test_union_object(self):
254254
u = Union[object]
255255
self.assertEqual(u, object)
256-
u = Union[int, object]
257-
self.assertEqual(u, object)
258-
u = Union[object, int]
259-
self.assertEqual(u, object)
256+
u1 = Union[int, object]
257+
u2 = Union[object, int]
258+
self.assertEqual(u1, u2)
259+
self.assertNotEqual(u1, object)
260+
self.assertNotEqual(u2, object)
260261

261262
def test_unordered(self):
262263
u1 = Union[int, float]
@@ -267,13 +268,11 @@ def test_single_class_disappears(self):
267268
t = Union[Employee]
268269
self.assertIs(t, Employee)
269270

270-
def test_base_class_disappears(self):
271-
u = Union[Employee, Manager, int]
272-
self.assertEqual(u, Union[int, Employee])
273-
u = Union[Manager, int, Employee]
274-
self.assertEqual(u, Union[int, Employee])
271+
def test_base_class_kept(self):
275272
u = Union[Employee, Manager]
276-
self.assertIs(u, Employee)
273+
self.assertNotEqual(u, Employee)
274+
self.assertIn(Employee, u.__args__)
275+
self.assertIn(Manager, u.__args__)
277276

278277
def test_union_union(self):
279278
u = Union[int, float]
@@ -317,7 +316,8 @@ def test_cannot_instantiate(self):
317316
def test_union_generalization(self):
318317
self.assertFalse(Union[str, typing.Iterable[int]] == str)
319318
self.assertFalse(Union[str, typing.Iterable[int]] == typing.Iterable[int])
320-
self.assertTrue(Union[str, typing.Iterable] == typing.Iterable)
319+
self.assertIn(str, Union[str, typing.Iterable[int]].__args__)
320+
self.assertIn(typing.Iterable[int], Union[str, typing.Iterable[int]].__args__)
321321

322322
def test_union_compare_other(self):
323323
self.assertNotEqual(Union, object)
@@ -917,7 +917,7 @@ def test_extended_generic_rules_eq(self):
917917
self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str])
918918
class Base: ...
919919
class Derived(Base): ...
920-
self.assertEqual(Union[T, Base][Derived], Base)
920+
self.assertEqual(Union[T, Base][Union[Base, Derived]], Union[Base, Derived])
921921
with self.assertRaises(TypeError):
922922
Union[T, int][1]
923923

Lib/typing.py

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ def _check_generic(cls, parameters):
206206

207207

208208
def _remove_dups_flatten(parameters):
209-
"""An internal helper for Union creation and substitution: flatten Union's
210-
among parameters, then remove duplicates and strict subclasses.
209+
"""An internal helper for Union creation and substitution: flatten Unions
210+
among parameters, then remove duplicates.
211211
"""
212212
# Flatten out Union[Union[...], ...].
213213
params = []
@@ -228,20 +228,7 @@ def _remove_dups_flatten(parameters):
228228
all_params.remove(t)
229229
params = new_params
230230
assert not all_params, all_params
231-
# Weed out subclasses.
232-
# E.g. Union[int, Employee, Manager] == Union[int, Employee].
233-
# If object is present it will be sole survivor among proper classes.
234-
# Never discard type variables.
235-
# (In particular, Union[str, AnyStr] != AnyStr.)
236-
all_params = set(params)
237-
for t1 in params:
238-
if not isinstance(t1, type):
239-
continue
240-
if any((isinstance(t2, type) or
241-
isinstance(t2, _GenericAlias) and t2._special) and issubclass(t1, t2)
242-
for t2 in all_params - {t1}):
243-
all_params.remove(t1)
244-
return tuple(t for t in params if t in all_params)
231+
return tuple(params)
245232

246233

247234
_cleanups = []
@@ -440,19 +427,6 @@ class Starship:
440427
441428
Union[int, str] == Union[str, int]
442429
443-
- When two arguments have a subclass relationship, the least
444-
derived argument is kept, e.g.::
445-
446-
class Employee: pass
447-
class Manager(Employee): pass
448-
Union[int, Employee, Manager] == Union[int, Employee]
449-
Union[Manager, int, Employee] == Union[int, Employee]
450-
Union[Employee, Manager] == Employee
451-
452-
- Similar for object::
453-
454-
Union[int, object] == object
455-
456430
- You cannot subclass or instantiate a union.
457431
- You can use Optional[X] as a shorthand for Union[X, None].
458432
""")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Do not simplify arguments to `typing.Union`. Now `Union[Manager, Employee]`
2+
is not simplified to `Employee` at runtime. Such simplification previously
3+
caused several bugs and limited possibilities for introspection.

0 commit comments

Comments
 (0)