Может ли Python Decorator сделать функцию способной интеллектуально работать с отдельными объектами и объектами коллекций? - PullRequest
1 голос
/ 25 мая 2011

Я пишу множество функций, которые я хочу использовать для аргументов 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)

1 Ответ

2 голосов
/ 09 июня 2014

В 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, намереваясь запустить ее в «одиночном» режиме, но передав ей аргумент, который оказывается итеративным, например строку.

Этона самом деле результат неоднозначности, присущей поведению, о котором идет речь в вопросе, а не реализации, описанной здесь, но это стоитПри рассмотрении вопроса о том, стоит ли использовать эту технику, не возражайте.

...