Сообщить об ошибке.

Декоратор @singledispatch модуля functools в Python

Универсальная функция одиночной диспетчеризации

Синтаксис:

from functools import singledispatch

singledispatchmethod(func)
@singledispatch

Параметры:

  • func - функция, которую следует сделать универсальной.

Возвращаемое значение:

  • функция одиночной диспетчеризации.

Описание:

Декоратор @singledispatch модуля functools создает из обычной функции - универсальную функцию одиночной диспетчеризации.

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

Одиночная (single) диспетчеризация это алгоритм диспетчеризации для универсальных функций, при котором нужная реализация выбирается на основе типа одного аргумента.

Чтобы определить универсальную функцию, оберните ее с помощью декоратора @singledispatch. Обратите внимание, что в перегруженные реализации передается тип первого аргумента:

from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say,", end=" ")
    print(arg)

Чтобы добавить перегруженные реализации в функцию, используйте атрибут .register() обобщенной функции fun. Выражение fun.register() то же является декоратором. Для функций, аннотированных типами, декоратор @singledispatch автоматически выведет тип первого аргумента:

@fun.register
def _(arg: int, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)

@fun.register
def _(arg: list, verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)

Для кода, который не использует аннотации типов, соответствующий аргумент типа может быть явно передан самому декоратору:

@fun.register(complex)
def _(arg, verbose=False):
    if verbose:
        print("Better than complicated.", end=" ")
    print(arg.real, arg.imag)

Чтобы включить регистрацию лямбда-функций и уже существующих функций, атрибут .register() может использоваться в функциональной форме:

def nothing(arg, verbose=False):
    print("Nothing.")

fun.register(type(None), nothing)

Атрибут .register() возвращает не декорированную функцию, которая позволяет вкладывать декораторы, делать выборку, а также создавать модульные тесты для каждого варианта независимо:

@fun.register(float)
@fun.register(Decimal)
def fun_num(arg, verbose=False):
    if verbose:
        print("Half of your number:", end=" ")
    print(arg / 2)

fun_num is fun
#False

При вызове универсальная функция отправляет тип первого аргумента:

>>> fun("Hello, world.")
# Hello, world.
>>> fun("test.", verbose=True)
# Let me just say, test.
>>> fun(42, verbose=True)
# Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
# Enumerate this:
# 0 spam
# 1 spam
# 2 eggs
# 3 spam
>>> fun(None)
# Nothing.
>>> fun(1.23)
# 0.615

Там, где для определенного типа нет зарегистрированной реализации, используется порядок разрешения методов, чтобы найти более общую реализацию. Оригинальная функция, декорированная @singledispatch, зарегистрирована для базового типа объекта, это означает, что будет использоваться оригинальная функция, если не найдена лучшая реализация для переданного типа.

Чтобы проверить, какую реализацию выберет оригинальная функция для данного типа, используйте атрибут fun.dispatch():

>>> fun.dispatch(float)
# <function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)
# <function fun at 0x103fe0000>

Чтобы получить доступ ко всем зарегистрированным реализациям, используйте атрибут только для чтения fun.registry:

>>> fun.registry.keys()
# dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
#           <class 'decimal.Decimal'>, <class 'list'>,
#           <class 'float'>])
>>> fun.registry[float]
# <function fun_num at 0x1035a2840>
>>> fun.registry[object]
# <function fun at 0x103fe0000>

Изменено в Python 3.7: Атрибут .register() теперь поддерживает использование аннотаций типов.

Изменено в Python 3.11: атрибут .register() теперь в качестве аннотаций типов поддерживает types.UnionType и typing.Union.

Пример аннотации к аргументу отправки:

from functools import singledispatch

@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say,", end=" ")
    print(arg)

@fun.register
def _(arg: int | float, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)

from typing import Union
@fun.register
def _(arg: Union[list, set], verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)