Hi All,

FWIW, pydantic considers unions to be "somewhat ordered" - in other words we try hard to identify the best union choice to validate against regardless of order, but in the absence of an alternative we have to eventually try union choices in the order they're defined in the union.

Example of what this means:

Example 1

Union[str, int]:
Here, even if we're in "lax mode" and "123" could be legitimately coerced to the number 123 we can infer the best option, in all these cases Union[str, int] would be the same as Union[int, str].

Example 2

Union[int, bool]:
Union[bool, int]:
  • Input True -> output "True"
  • input 1 -> output 1
  • input "1" -> output True

  • Note in the last case the string "1" can be coerced to both the int 1 and the bool True, thus the output differs depending on the order of the union choices.

    While you might (probably will) disagree and disapprove of the "lax mode" coercion. I hope you'll agree that given we have it, there's no option but to try choices in the order they're defined.

    AFAIK, in strict mode, Union behaviour is independent of order, but I could be wrong.

    (Note some of these cases refer to the as yet unreleased Pydantic V2, and pydantic-core. Pydantic V1 has some unfortunate rough edges).

    Samuel

    --

    Samuel Colvin


    On Sat, 9 Jul 2022 at 18:03, Paul Bryan <pbryan@anode.ca> wrote:
    A case I think is worth consideration: the runtime use of type hints for serialization/deserialization of values.

    If deserializing a value that's annotated as `list[str | int]`, if UnionType argument order is applied, every deserialized list element would resolve to a `str` type. This is in fact how a library I maintain currently performs deserialization. To date, for cases like this I've recommended the order be switched so as to avoid every element falling into the catch-all `str` deserialization.

    On Sat, 2022-07-09 at 07:39 +0000, Eric Traut wrote:
    I'm not familiar with any documentation (in PEP 484 or elsewhere) that explicitly states that unions in the Python type system are unordered, but I have made that assumption in Pyright. I think mypy makes a similar assumption.

    In type theory, it makes sense that unions would be order-independent and fully commutative — i.e. `X | Y` should be equivalent to `Y | X`.

    There are a few places where this comes up. One is when determining whether two types are compatible, especially when a union is used for the specialization of an invariant type parameter. I think we can agree that the following is type safe and should be allowed.

    ```python
    def func(a: list[str | int]):
        b: list[int | str] = a
    ```

    Another place where union ordering becomes important is in the constraint solver. Martin, I'm guessing this is the case that you're referring to?

    The way I solve the union ordering problem in the constraint solver in Pyright is to attempt type matches for each subtype within the union and "score" each successful match. The score represents the "simplicity" of the solution. I choose the one with the simplest solution. For example:

    ```python
    from typing import TypeVar

    T = TypeVar("T")

    def func1(a: T | list[T]) -> T: ...
    def func2(a: list[T] | T) -> T: ...

    def test(val: list[int]):
        reveal_type(func1(val))  # int
        reveal_type(func2(val))  # int
    ```

    In this example, the constraint solver attempts to match `list[int]` against both `list[T]` and `T`. Both of these are valid solutions given the constraints, but the simplest solution is `T = int` (versus `T = list[int]`). The "simplicity calculation" is a heuristic, and I've needed to tune it over time to achieve the best results, but it seems to work well and produce deterministic results that do not depend on union ordering — unless two solutions happen to result in exactly the same simplicity score.

    Interestingly, mypy emits false positive errors for the above example.

     -Eric

    --
    Eric Traut
    Contributor to Pyright & Pylance
    Microsoft
    _______________________________________________
    Typing-sig mailing list -- typing-sig@python.org
    To unsubscribe send an email to typing-sig-leave@python.org
    https://mail.python.org/mailman3/lists/typing-sig.python.org/
    Member address: pbryan@anode.ca

    _______________________________________________
    Typing-sig mailing list -- typing-sig@python.org
    To unsubscribe send an email to typing-sig-leave@python.org
    https://mail.python.org/mailman3/lists/typing-sig.python.org/
    Member address: samcolvin@gmail.com