.. _`type-system-concepts`: Type system concepts ==================== .. _`union-types`: Union types ----------- Since accepting a small, limited set of expected types for a single argument is common, the type system supports union types, created with the ``|`` operator. Example:: def handle_employees(e: Employee | Sequence[Employee]) -> None: if isinstance(e, Employee): e = [e] ... A type factored by ``T1 | T2 | ...`` is a supertype of all types ``T1``, ``T2``, etc., so that a value that is a member of one of these types is acceptable for an argument annotated by ``T1 | T2 | ...``. One common case of union types are *optional* types. By default, ``None`` is an invalid value for any type, unless a default value of ``None`` has been provided in the function definition. Examples:: def handle_employee(e: Employee | None) -> None: ... A past version of this specification allowed type checkers to assume an optional type when the default value is ``None``, as in this code:: def handle_employee(e: Employee = None): ... This would have been treated as equivalent to:: def handle_employee(e: Employee | None = None) -> None: ... This is no longer the recommended behavior. Type checkers should move towards requiring the optional type to be made explicit. Support for singleton types in unions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A singleton instance is frequently used to mark some special condition, in particular in situations where ``None`` is also a valid value for a variable. Example:: _empty = object() def func(x=_empty): if x is _empty: # default argument value return 0 elif x is None: # argument was provided and it's None return 1 else: return x * 2 To allow precise typing in such situations, the user should use a union type in conjunction with the ``enum.Enum`` class provided by the standard library, so that type errors can be caught statically:: from enum import Enum class Empty(Enum): token = 0 _empty = Empty.token def func(x: int | None | Empty = _empty) -> int: boom = x * 42 # This fails type check if x is _empty: return 0 elif x is None: return 1 else: # At this point typechecker knows that x can only have type int return x * 2 Since the subclasses of ``Enum`` cannot be further subclassed, the type of variable ``x`` can be statically inferred in all branches of the above example. The same approach is applicable if more than one singleton object is needed: one can use an enumeration that has more than one value:: class Reason(Enum): timeout = 1 error = 2 def process(response: str | Reason = '') -> str: if response is Reason.timeout: return 'TIMEOUT' elif response is Reason.error: return 'ERROR' else: # response can be only str, all other possible values exhausted return 'PROCESSED: ' + response