Я нахожу, что пишу много функций, которые я хочу использовать для аргументов x, y, ...
, и в коллекциях, например. [(x1, y1, ...), (x2, y2, ...), ...]
.
Есть ли простой и простой декоратор, шаблон или техника для написания таких функций?
Здесь произвольный пример:
def awesome(func):
# ???
@awesome
def mult(a, b):
return a * b
mult(3, 4) # should return 12
mult((3, 4)) # should return 12 or possibly (12,)
mult(((3, 4), (2, 3), [3, 3])) # should return (12, 6, 9)
mult([(3, 4), [4, 5]]) # should return (12, 20)
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
, намереваясь запустить ее в режиме "один", но передавая ее аргумент, который является итерируемым, как строка.
Это действительно результат двусмысленности, присущей поведению, заданному в вопросе, а не описанной здесь реализации, но это стоит учитывать при рассмотрении вопроса о том, следует ли использовать эту технику.
func("abc")
быть одним вызовом или тремя вызовами func("a"); func("b"); func("c")
.
(mul(*x) for x in xs)