Может ли Python Decorator сделать функцию способной интеллектуально работать с отдельными объектами и объектами коллекций?

1

Я нахожу, что пишу много функций, которые я хочу использовать для аргументов 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)
  • 9
    Сама идея не так хороша, так как вы не поймете, что входит и выходит из этой функции, что затрудняет поиск ошибок. Просто введите (mul(*x) for x in xs)
  • 0
    да, это имеет смысл. UP.
Показать ещё 1 комментарий
Теги:
generics
collections
decorator

1 ответ

2
Лучший ответ

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

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

  • 0
    Одна сложность заключается в том, что строки являются итеративными. Поэтому, если исходная функция принимает в качестве аргумента одну строку, код не может определить, должен ли func("abc") быть одним вызовом или тремя вызовами func("a"); func("b"); func("c") .
  • 0
    @Blckknght это хорошая точка зрения (расширение комментария Йохена Ритцела выше). Я обновил ответ с оговоркой на этот счет.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню