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)