аватар question@mail.ru · 01.01.1970 03:00

Разница между @singledispatch и @overload

В чём разница между и ? С помощью чего в питоне делать переопределение?

аватар answer@mail.ru · 01.01.1970 03:00

tl;dr:

  • @overload только декларирует (для линтеров или IDE) разные возможные сочетания аннотаций типов для одной функции
  • @singledispatch выполняет переключение на нужную реализацию функции в зависимости от фактических типов аргументов

Наиболее краткое и понятное описание декоратора overload нашел :

def overload(func):    """"""Decorator for overloaded functions/methods.    In a stub file, place two or more stub definitions for the same    function in a row, each decorated with @overload.  For example:      @overload      def utf8(value: None) -> None: ...      @overload      def utf8(value: bytes) -> bytes: ...      @overload      def utf8(value: str) -> bytes: ...    In a non-stub file (i.e. a regular .py file), do the same but    follow it with an implementation.  The implementation should *not*    be decorated with @overload.  For example:      @overload      def utf8(value: None) -> None: ...      @overload      def utf8(value: bytes) -> bytes: ...      @overload      def utf8(value: str) -> bytes: ...      def utf8(value):          # implementation goes here    """"""    retu _overload_dummy

Смысл такой:

1. Если используется в стаб-файле (pyi)

Для одной и той же функции можно указать, что она может принимать разные типы аргументов. Т.е. реализация одна, а деклараций несколько:

Файл pyi

@overloaddef some_function(value: int) -> int: ...@overloaddef some_function(value: float) -> float: ...@overloaddef some_function(value: str) -> str: ...

В py файле:

def some_function(value):    if isinstance(value, int):        retu value + 1    elif isinstance(value, float):        retu value * 2.0    elif isinstance(value, str):        retu ""Hello, "" + value    else:        raise TypeError

2. Если используется в py-файле

- аналогично, декларирует различные варианты типов, в самих декларациях не должно быть реализации, а у реализации не должно быть декоратора @overload (""In a non-stub file (i.e. a regular .py file), do the same but follow it with an implementation. The implementation should not be decorated with @overload."").

Т.е. по сути в предыдущем примере просто все складываем в один файл:

@overloaddef some_function(value: int) -> int: ...@overloaddef some_function(value: float) -> float: ...@overloaddef some_function(value: str) -> str: ...def some_function(value):    if isinstance(value, int):        retu value + 1    elif isinstance(value, float):        retu value * 2.0    elif isinstance(value, str):        retu ""Hello, "" + value    else:        raise TypeError

Таким образом, @overload только декларирует разные возможные типы аргументов и возвращаемых значений одной функции, но не выполняет переключение между разными реализациями одной функции.

Это можно рассматривать как альтернативу Union, с помощью которого можно было бы типы описать так:

def some_function(value: Union[int, float, str]) -> Union[int, float, str]: ...

Но с такими аннотациями не видно, что при полученном int возвращается int и т.д., а только декларируется, что может быть принят любой из перечисленных типов, и вернуться также любой из них (в любых сочетаниях).

:

from typing import overloadclass bytes:    ...    @overload    def __getitem__(self, i: int) -> int: ...    @overload    def __getitem__(self, s: slice) -> bytes: ...

Т.е. метод __getitem__ (обращение через квадратные скобки) у объекта bytes при передаче целого числа (индекса) вернет целое число, а при передаче слайса вернет набор байт.


singledispatch наоборот выполняет переключение на нужную реализацию в зависимости от типа аргумента:

from functools import singledispatch@singledispatchdef some_function(value):    raise TypeError@some_function.registerdef _(value: int):    retu value + 1@some_function.registerdef _(value: float):    retu value * 2.0@some_function.registerdef _(value: str):    retu ""Hello, "" + valueprint(some_function(2))  # 3print(some_function(2.0))  # 4.0print(some_function(""Insolor""))  # Hello, Insolor

На мой взгляд, более красиво (без подчеркиваний вместо имен функций) ""переключение"" между реализациями в зависимости от типа аргумента реализовано в библиотеке :

from fastcore.dispatch import typedispatch@typedispatchdef some_function(value):    raise TypeError@typedispatchdef some_function(value: int):    retu value + 1@typedispatchdef some_function(value: float):    retu value * 2.0@typedispatchdef some_function(value: str):    retu ""Hello, "" + valueprint(some_function(2))  # 3print(some_function(2.0))  # 4.0print(some_function(""Insolor""))  # Hello, Insolor

Последние

Похожие