Подтвердить что ты не робот

В Python приложение частичной функции (каррирование) по сравнению с явным определением функций

В Python считается лучшим стилем:

  • явно определять полезные функции в терминах более общих, возможно внутренних функций, функций; или,
  • использовать приложение частичной функции для явного описания функции currying?

Я объясню свой вопрос с помощью надуманного примера.

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

def _sort_by_score(scoring, items_list):
    unsorted_scored_list = [(scoring(len(items_list), item_position), item) for item_position, item in enumerate(items_list)]
    sorted_list = [item for score, item in sorted(unsorted_scored_list)]
    return sorted_list

def _identity_scoring(items_list_size, item_position):
    return item_position

def _reversed_scoring(items_list_size, item_position):
    return items_list_size - item_position

Функция _sort_by_score никогда не вызывается напрямую; вместо этого он вызывается другими функциями одного аргумента, которые передают функцию подсчета очков и их одиночный аргумент (список элементов) в _sort_by_scoring и возвращают результат.

# Explicit function definition style
def identity_ordering(items_list):
    return _sort_by_score(_identity_scoring, items_list)

def reversed_ordering(items_list):
    return _sort_by_score(_reversed_scoring, items_list)

Очевидно, что это намерение лучше выражено в терминах каррирования функции.

# Curried function definition style
import functools
identity_ordering = functools.partial(_sort_by_score, _identity_scoring)
reversed_ordering = functools.partial(_sort_by_score, _reversed_scoring)

Использование (в любом случае):

>>> foo = [1, 2, 3, 4, 5]
>>> identity_ordering(foo)
[1, 2, 3, 4, 5]
>>> reversed_ordering(foo)
[5, 4, 3, 2, 1]

Явные преимущества явного стиля определения функции:

  • полезные функции могут быть определены до того, как будут созданы более общие функции, не повышая значение NameErrors;
  • вспомогательные функции (например, функции подсчета очков) могут быть определены внутри тела определения функции;
  • возможно проще отладить;
  • код выглядит красивым в силу "явного лучше, чем неявный".

Очевидные преимущества стиля определения картой:

  • выражает намерение функционального программирования идиоматически;
  • код выглядит красивым в силу лаконичности.

Для определения "полезных" функций, какой из двух стилей является предпочтительным? Существуют ли другие стили, более идиоматические/Pythonic/etc.?

4b9b3361

Ответ 1

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

  • Легче назначить docstring для явного определения функции. Для функций partial() вам необходимо назначить атрибут __doc__, который несколько уродлив.

  • Определения реальных функций легче снимать при просмотре источника модуля.

Я использовал бы functools.partial() аналогично лямбда-выражениям, т.е. для локально необходимых отбрасывающих функций.

В вашем конкретном примере, я бы, вероятно, не использовал ни одного, сбросьте основные подчеркивания и вызовите

sort_by_score(identity_scoring, foo)

который кажется мне наиболее явным.

Ответ 2

Как небольшая касательная, обычно желательно, чтобы встроенный sorted выполнял работу по декорированию-сортировке-undecorate как практично. Например:

def _sort_by_score(scoring, items_list):
    num_items = len(items_list)
    def score(entry):
        return scoring(num_items, entry[0])
    return [item for position, item in sorted(enumerate(items_list), key=score)]

(Только отправлено как ответ, потому что блоки кода не работают как комментарии. См. ответ Sven для ответа на заданный вопрос)

Изменить кем-то еще. Функция сортировки Python выполняет итерацию по списку и сначала генерирует список ключей. Функция key() вызывается только один раз для каждого элемента списка в порядке списка ввода. Таким образом, вы также можете использовать следующую реализацию:

def _sort_by_score(scoring, items_list):
    num_items = len(items_list)
    index = itertools.count()
    def score(entry):
        return scoring(num_items, next(index))
    return sorted(items_list, key=score)

(опубликовано только как ревизия, потому что блоки кода не работают как комментарии.)