Является ли Pythonic использовать понимание списка только для побочных эффектов?

#python #dictionary #rename

#python #понимание списка

Вопрос:

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

 def fun_with_side_effects(x):
    ...side effects...
    return y
 

Теперь, является ли Pythonic использовать понимание списка для вызова этой функции:

 [fun_with_side_effects(x) for x in y if (...conditions...)]
 

Обратите внимание, что я нигде не сохраняю список

Или я должен называть эту функцию следующим образом:

 for x in y:
    if (...conditions...):
        fun_with_side_effects(x)
 

Что лучше и почему?

Комментарии:

1. это погранично, но вы, вероятно, получите больше возражений, чем поддержки. Я собираюсь пропустить это: ^)

2. Это простой выбор. Важна читаемость — сделайте это вторым способом. Если вы не можете разместить 2 дополнительные строки на экране, получите монитор большего размера 🙂

3. @larsmans: если бы только GvR понял это, когда он впервые представил понимание списков!

4. @larsmans, Стив Джессоп, я думаю, что неверно представлять понимание списка как цикл. Это вполне может быть реализовано как цикл, но смысл подобных конструкций заключается в том, чтобы оперировать агрегированными данными функциональным и (концептуально) параллельным образом. Если есть проблема с синтаксисом, она for ... in используется в обоих случаях — что приводит к таким вопросам, как этот!

5. @senderle: Хотя, я думаю, это зависит от побочных эффектов. Если побочные эффекты просто изменяют один элемент за раз, независимо, то я думаю, что для этого вполне разумно использовать конструкции функционального стиля на императивном языке, потому что важно не управление потоком цикла, а приложение к каждому элементу. Если побочные эффекты таковы, что порядок имеет значение, возможно, тогда абстракция «понимания» начинает просачиваться. Однако достаточно ли он протекает, чтобы иметь значение, это другой вопрос — никто не притворяется, что Python выполняет ленивую оценку.

Ответ №1:

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

Ответ №2:

Вам не следует использовать понимание списка, потому что, как уже говорили люди, это приведет к созданию большого временного списка, который вам не нужен. Следующие два метода эквивалентны:

 consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)
 

с определением consume из itertools справочной страницы:

 def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is none, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)
 

Конечно, последнее понятнее и понятнее.

Комментарии:

1. @Paul: Я думаю, так и должно быть. И действительно, вы можете, хотя map это может быть не так интуитивно понятно, если раньше не занимался функциональным программированием.

2. Не уверен, что это особенно идиоматично. Нет никакого преимущества по сравнению с использованием явного цикла.

3. Решение consume = collections.deque(maxlen=0).extend

Ответ №3:

Понимание списков предназначено для создания списков. И если вы на самом деле не создаете список, вам не следует использовать понимание списка.

Итак, я бы выбрал второй вариант, просто выполнив итерацию по списку, а затем вызвав функцию, когда применяются условия.

Комментарии:

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

Ответ №4:

Второй лучше.

Подумайте о человеке, которому нужно было бы понять ваш код. Вы можете легко получить плохую карму с первым 🙂

Вы могли бы пойти посередине между ними, используя filter() . Рассмотрим пример:

 y=[1,2,3,4,5,6]
def func(x):
    print "call with %r"%x

for x in filter(lambda x: x>3, y):
    func(x)
 

Комментарии:

1. Вам даже не нужен фильтр. Просто поместите выражение генератора в скобки здесь: for el in (x for x in y if x > 3): . el и x могут иметь одно и то же имя, но это может сбить людей с толку.

2. Обратите внимание, что эти два x параметра находятся в разных областях, что является плохой практикой.

Ответ №5:

Зависит от вашей цели.

Если вы пытаетесь выполнить какую-то операцию над каждым объектом в списке, следует использовать второй подход.

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

Явное лучше, чем неявное. Простое лучше, чем сложное. (Python Zen)

Комментарии:

1. В принципе, лучший ответ. Синтаксис подчиняется правилам, так что можно ответить «правильно», «неправильно», «так», «так». Стиль подчиняется другим соображениям, таким как здравый смысл, элегантность, лаконичность, эффективность, эффективность и т. Д. Это зависит от ваших целей и даже от вашего вкуса.

Ответ №6:

Вы можете сделать

 for z in (fun_with_side_effects(x) for x in y if (...conditions...)): pass
 

но это не очень красиво.

Ответ №7:

Использование понимания списка для его побочных эффектов уродливо, непитонно, неэффективно, и я бы этого не сделал. Вместо этого я бы использовал for цикл, потому for что цикл сигнализирует о процедурном стиле, в котором важны побочные эффекты.

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

 any(fun_with_side_effects(x) and False for x in y if (...conditions...))
 

или:

 all(fun_with_side_effects(x) or True for x in y if (...conditions...))
 

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

Я думаю, что это некрасиво, и я бы на самом деле не стал делать это в коде. Но если вы настаиваете на реализации своих циклов таким образом, вот как я бы это сделал.

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

Комментарии:

1. Что, если fun_with_side_effects возвращает True?

2. Я думаю, что это лекарство хуже, чем болезнь — itertools.consume намного чище.

3. @PaulMcG — itertools.consume больше не существует, вероятно, потому, что использование понимания с побочными эффектами уродливо.

4. Оказывается, я ошибся, и он никогда не существовал как метод в stdlib. Это рецепт в документах itertools: docs.python.org/3/library /…