В Python 3.4 представлены универсальные функции с одной отправкой с декоратором @singledispatch
, обеспечивающие простой способ реализации различного поведения при вызове одной и той же функции с разными типами аргументов.Например, чтобы ваша функция mult()
могла принимать итерации:
from functools import singledispatch
from collections.abc import Iterable
@singledispatch
def mult(a, b):
return a * b
@mult.register(Iterable)
def _(it):
return [mult(a, b) for a, b in it]
Базовая версия вашей функции украшена @singledispatch
, превращая ее в универсальную функцию.Затем определена функция _()
для вызова mult()
для каждого элемента в итерируемой и зарегистрированной для универсальной функции с помощью @mult.register()
.
Хотя в приведенном выше примере используется collections.abc.Iterable
ради максимальной общности, также возможно зарегистрировать функцию для нескольких конкретных типов:
@mult.register(list)
@mult.register(tuple)
def _(it):
return [mult(a, b) for a, b in it]
Используя вышеописанную технику, можно написать декоратор @takes_iterable
, который преобразует произвольную функцию вуниверсальная функция и регистрирует в ней другую функцию для соответствующей обработки итераций:
def takes_iterable(func):
generic = singledispatch(func)
@generic.register(Iterable)
def _(it):
return [func(*args) for args in it]
return generic
@takes_iterable
def mult(a, b):
return a * b
Пример использования:
>>> mult(17,23)
391
>>> mult([(11, 29), (13, 23), (17, 19)])
[319, 299, 323]
CAVEAT : Как указано в Blckknght вкомментарии, вы получите неожиданные результаты, если вызовете функцию, украшенную @takes_iterable
, намереваясь запустить ее в «одиночном» режиме, но передав ей аргумент, который оказывается итеративным, например строку.
Этона самом деле результат неоднозначности, присущей поведению, о котором идет речь в вопросе, а не реализации, описанной здесь, но это стоитПри рассмотрении вопроса о том, стоит ли использовать эту технику, не возражайте.