[Python] How to properly override Python's sorted function so that the output type is the same...

Discussão em 'Python' iniciado por Stack, Julho 3, 2025.

  1. Stack

    Stack Membro Participativo

    This is part of a bigger issue that I have, so I'll try to be precise to avoid a XY problem.

    For my static typing in Python 3.13, I need my input to support sorting, which I first implemented with a simple custom Protocol requiring the in-place sort method:

    import typing as tp

    class Sortable(tp.Protocol):
    def sort() -> None: ...

    def f1[T: Sortable](x: T) -> T:
    y = x.copy()
    return y.sort()


    However this will not work on tuples, that cannot be sorted in-place as they are immutable. Therefore, I tried a different approach, that is to require an Iterable and use the sorted built-in function. According to sorted's doc, it always returns a list, whatever the type of the given iterable input (which must be an Iterable, although it is not explicitly stated by the signature).

    import typing as tp

    def f2[K, T: tp.Iterable](x: T) -> list:
    return sorted(x)


    This works and will pass mypy checks, but it will always return a list. I tried explicit conversion of the output to the original type in all the different ways that I could think of but it did not work for all types, even when they support the sorted function:

    import typing as tp
    import numpy as np

    def f3[T: tp.Iterable](x: T) -> T:
    """
    fails when `type(x)` is not a constructor for the type of x
    also flagged by mypy for this reason
    """
    return type(x)(sorted(x))

    f3((1,5,8)) # ok
    f3("azert") # ok
    sorted(np.array((1,3)))
    # ^ returns [np.int64(1), np.int64(3)], a bit clunky but ok
    f3(np.array((1,3)))
    # ^ runs without error but returns array([[4.9e-324, 9.9e-324, 4.4e-323]]),
    # ^ which is the output of np.empty((1,3))


    Of course, I could hardcode all the Iterables I will actually use in my package, as in How can I enforce a specific output type for a specific input type in Python?, but that would be a tedious and not very elegant way to do it.


    An additional (related) issue that I have, that might be a new question altogether, is that my Iterables always contain a single type (otherwise it would not be guaranteed that I can sort them) but that is beyond the scope of this question, I'm only including it to avoid a XY problem. My guess was something along the lines of:

    import typing as tp

    class SortableValue(tp.Protocol):
    # maybe I should use tp.Self instead of a type spec?
    def __gt__[T](self: T, y: T) -> bool: ...


    def fa[K: SortableValue, IT: tp.Iterable](x: IT[K]) -> list[K]:
    """error: Type variable "IT" used with arguments [valid-type]"""
    return sorted(x)

    def fb[K: SortableValue, IT: tp.Iterable[K]](x: IT) -> list[K]:
    """error: Name "K" is not defined [name-defined]"""
    return sorted(x)

    # With as before the ultimate goal of:
    def fc[K: SortableValue, IT: tp.Iterable](x: IT[K]) -> IT[K]:
    """sorts x"""
    ...

    Continue reading...

Compartilhe esta Página