Вопрос разработки Python (можно / нужно использовать декораторы в этом случае?) - PullRequest
2 голосов
/ 13 августа 2010

У меня есть проблема, которую можно упростить следующим образом: у меня есть определенный набор объектов, которые я хочу изменить определенным образом.Итак, я могу написать функцию, которая модифицирует отдельный объект, а затем создать декоратор, который применяет эту функцию ко всем объектам в наборе.

Итак, давайте предположим, что у меня есть что-то вроде этого:

def modify_all(f):
    def fun(objs):
        for o in objs:
            f(o)

@modify_all
def modify(obj):
    # Modify obj in some way.

modify(all_my_objs)

Однако могут также быть случаи, когда я просто хочу оперировать одним объектом самостоятельно.

Есть ли способ "декорировать" функцию modify программным способом, чтобы снова вернуть исходную функцию (один объект)?(Я имею в виду, не просто удаляя декоратор.) Или есть другой подход, который лучше использовать в таком случае?

Просто для ясности и полноты в фактическом modify_all декораторе происходит гораздо больше, чем показано здесь.И modify, и modify_all должны выполнить определенное количество работы, поэтому я подумал, что декоратор может быть хорошим.Кроме того, у меня есть другие варианты modify, которые могут непосредственно извлечь выгоду из modify_all, что делает его еще более полезным.Но иногда мне нужно делать что-то, используя оригинальную функцию modify.Я знаю, что всегда могу передать одноэлементный набор, чтобы «обмануть» его, чтобы он работал как есть, но мне интересно, есть ли лучший способ или лучший дизайн для ситуации.

Ответы [ 5 ]

2 голосов
/ 13 августа 2010

Декораторы предназначены для применения функции высшего порядка (HOF) в определенной форме

def f ...

f = HOF(f)

В этом случае синтаксис (с идентичной семантикой)

@HOF
def f ...

является более кратким и сразу предупреждает читателя, что f никогда не будет использоваться «голым».

Для вашего случая использования, где вам нужно и «голое f» и «украшенное f», они будутдолжны иметь два разных имени, поэтому синтаксис декоратора не применим немедленно, но и вовсе не обязателен!Отличной альтернативой может быть использование в качестве оформленного имени атрибута оформленной функции, например:

def modify(obj): ...

modify.all = modify_all(modify)

Теперь вы можете вызывать modify(justone) и modify.all(allofthem) и кодировать долго и счастливо.

2 голосов
/ 13 августа 2010

Примените декоратор вручную к функции один раз и сохраните результат под новым именем.

def modify_one(obj):
    ...

modify_some = modify_all(modify_one)
modify_some([a, b, c])
modify_one(d)
1 голос
/ 13 августа 2010

Алекс уже опубликовал нечто подобное, но это была моя первая мысль, так что я пойду и опубликую это, потому что это обходит возражение Майка Грэхемса. (даже если я не совсем согласен с его возражением: тот факт, что люди не знают о чем-то, является , а не причиной не использовать это. Именно так люди продолжают не знаю об этом)

def modify_all(f):
    def fun(objs):
        for o in objs:
            f(o)
    fun.unmodified = f
    return fun

def undecorate(f):
    return f.unmodified

@modify_all
def modify(obj):
    # Modify obj in some way.

modify(all_my_objs)
undecorate(modify)(one_obj)

Вы можете просто позвонить undecorate, чтобы получить доступ к оформленной функции.

другой альтернативой будет обработать это следующим образом:

def modify_one(modify, one):
    return modify.unmodified(one)
0 голосов
/ 13 августа 2010

Вы можете использовать декоратор, чтобы применить идею Алекса

>>> def forall(f):
...     def fun(objs):
...         for o in objs:
...             f(o)
...     f.forall=fun
...     return f
... 
>>> @forall
... def modify(obj):
...     print "modified ", obj
... 
>>> modify("foobar")
modified  foobar
>>> modify.forall("foobar")
modified  f
modified  o
modified  o
modified  b
modified  a
modified  r

Возможно, foreach - лучшее имя

0 голосов
/ 13 августа 2010

В вашем конкретном случае я хотел бы рассмотреть возможность распаковки аргументов.Это может быть сделано только с небольшой модификацией существующего кода:

def broadcast(f):
    def fun(*objs):
        for o in objs:
            f(o)
    return fun

@broadcast
def modify(obj):
    # Modify obj in some way.

modify(*all_my_objs)
modify(my_obj)

Однако, операция распаковки аргумента занимает немного времени, так что это, вероятно, будет немного медленнее, чем оригинальная версия, которая простопроходит простой старый список.Если вы обеспокоены тем, что это слишком сильно замедляет работу вашего приложения, попробуйте его и посмотрите, какое оно имеет значение.

В зависимости от того, с какими объектами вы работаете, другой вариант - сделать декоратор«разумно» решите, должен ли он использовать цикл:

def opt_broadcast(f):
    def fun(obj):
        if isinstance(obj, list):
            for o in objs:
                f(o)
        else:
            f(o)
    return fun

@opt_broadcast
def modify(obj):
    # Modify obj in some way.

modify(all_my_objs)
modify(my_obj)

Однако, если вы сделаете это, есть некоторая путаница, и, очевидно, она не сработает, если вы когда-либо будете звонить modifyсо списком, когда вы хотите, чтобы он работал со списком как с одним объектом.(Другими словами, если my_obj может быть списком, не делайте этого)

...