Чистый конвейер операций в питоне - PullRequest
0 голосов
/ 04 мая 2018

У меня длинный конвейер, который выполняет различные операции со списком строк input_list. Конвейер отображает каждое слово в нижний регистр, заменяет подчеркивание, отфильтровывает определенное слово, удаляет дубликаты и клипы определенной длины.

result = list(set(filter(lambda x : x != word, map(lambda x : x.lower().replace('_',' '), input_list))))[:clip_length]

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

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

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

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

  • вызов функции выполняется с обычной префиксной нотацией f(arg)
  • для получения подмассива используется специальный синтаксис arr[n?:m?] вместо функции slice(n,m)
  • set - это совершенно другой тип, но он используется промежуточным образом, потому что наборы имеют некоторое поведения, которое мы хотим - то, что мы хотим, это «уникальные» элементы в итерируемом и т. Д. наша функция должна называться unique. Если мы осуществим unique с использованием set, это нормально, но это не забота читателя, чей разум свободен от таких отвлекающих факторов
  • x.lower() - это динамический вызов с lower в инфиксной позиции. Сравните с префиксом позиции lower(x). То же самое относится к s.replace(pat,rep) против replace(s, pat, rep)
  • map и filter однако имеют функциональный интерфейс map(f,iter) и filter(f,iter)

Но при написании программы, подобной той, которой вы поделились, в некотором роде упускается самая сильная и универсальная черта функционального стиля: функция. Да, функциональное программирование - это также составление красивых цепочек выражений, но не за счет читабельности! Если читаемость начинает ухудшаться, сделайте это лучше с помощью функции: D

Рассмотрим эту программу, которая использует равномерный функциональный стиль. Это все еще обычная программа на Python.

def program (word = '', clip_length = 5, input = ''):
  make_words = \
    compose ( lower
            , partial (replace, '_', ' ')
            )

  process = \
    compose ( partial (map, make_words)
            , partial (filter, lambda x: x != word)
            , unique
            , partial (take, clip_length)
            )

  return process (input)

print (program ('b', 4, 'A_a_a_B_b_b_c_c_c_d_e'))
# ['d', ' ', 'e', 'a']
# Note, your output may vary. More on this later.

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

def partial (f, *xs):
  return lambda *ys: f (*xs, *ys)

def compose (f = None, *fs):
  def comp (x):
    if f is None:
      return x
    else:
      return compose (*fs) (f (x))
  return comp

def take (n = 0, xs = []):
  return xs [:n]

def lower (s = ''):
  return s .lower ()

def replace (pat = '', rep = '', s = ''):
  return s .replace (pat, rep)

def unique (iter):
  return list (set (iter))

На самом деле, этот вопрос не мог создать лучшую сцену для некоторых из этих пунктов. Я собираюсь вернуться к выбору set, который использовался в исходном вопросе (и в программе выше), потому что существует огромная проблема: если вы повторно запустите нашу программу несколько раз, мы получим другой выход. Проще говоря, у нас нет ссылочной прозрачности . Это потому, что наборы Python неупорядочены, и когда мы конвертируем из упорядоченного списка в набор, а затем обратно в список, не гарантируется, что мы всегда будем получать одни и те же элементы.

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

# deterministic implementation of unique
def unique (iter):
  result = list ()
  seen = set ()
  for x in iter:
    if x not in seen:
      seen .add (x)
      result .append (x)
  return result

Теперь, когда мы запускаем нашу программу, мы всегда получаем один и тот же результат

print (program ('b', 4, 'A_a_a_B_b_b_c_c_c_d_e'))
# ['a', ' ', 'c', 'd']
# always the same output now

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

Функциональное программирование - это около функций . Язык твой, чтобы приручить. Это все еще обычная программа на Python.

def fwd (x):
  return lambda k: fwd (k (x))

def program (word = '', clip_length = 5, input = ''):
  make_words = \
    compose ( lower
            , partial (replace, '_', ' ')
            )

  fwd (input)                               \
    (partial (map, make_words))             \
    (partial (filter, lambda x: x != word)) \
    (unique)                                \
    (partial (take, clip_length))           \
    (print)

program ('b', 4, 'A_a_a_B_b_b_c_c_c_d_e')
# ['a', ' ', 'c', 'd']

Нажмите и поэкспериментируйте с этой программой на repl.it

0 голосов
/ 04 мая 2018

Это функциональный стиль, который вы можете прочитать от внутреннего выражения к внешнему.

Помещение в несколько строк с некоторыми комментариями может помочь читабельности:

result = list(                                # (5) convert to list
  set(                                        # (4) convert to set (remove dupes)
    filter(
      lambda x: x != word,                    # (3) filter items != to word
      map(
        lambda x: x.lower().replace('_',' '), # (2) apply transformation
        input_list                            # (1) take input_list
      )
    )
  )
)[:clip_length]                               # (6) limit number of results

Это вопрос вкуса. Я предпочитаю предпочитать отдельные выражения, подобные этому, с минимальным форматированием, которое позволило бы ему хорошо вписаться:

result = list(set(filter(lambda x : x != word,
    map(lambda x : x.lower().replace('_',' '), input_list))))[:clip_length]

Эквивалентная обработка в стиле императива:

result = set()
for x in input_list:
    x = x.lower().replace('_', ' ')
    if x != word:
        result.add(x)
result = list(result)[:clip_length]
...