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

108

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

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)

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

синан
источник
6
это граница, но вы, вероятно, получите больше возражений, чем поддержки. Я собираюсь посидеть с этим: ^)
jcomeau_ictx
6
Это простой выбор. Читаемость имеет значение - делайте это вторым способом. Если вы не можете уместить на экране две лишние строки, купите монитор побольше :)
Джон Ла Рой,
1
Понимание списка не является питоническим, поскольку нарушает принцип «явное лучше, чем неявное» - вы скрываете цикл в другой конструкции.
Фред Фу
3
@larsmans: если бы только GvR понял это, когда впервые представил понимание списков!
Стив Джессоп,
2
@larsmans, Стив Джессоп, я считаю неправильным рассматривать понимание списка как цикл. Он вполне может быть реализован в виде цикла, но суть таких конструкций заключается в том, чтобы работать с агрегированными данными функциональным и (концептуально) параллельным образом. Если есть проблема с синтаксисом, он for ... inиспользуется в обоих случаях, что приводит к таким вопросам, как этот!
senderle

Ответы:

84

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

Игнасио Васкес-Абрамс
источник
5
Итак, что было бы более питоническим способом?
Иоахим Зауэр
6
Тот, который не хранит список; т.е. какой-то вариант второго способа (я, как известно, использовал генекс forраньше, чтобы избавиться от него if).
Игнасио Васкес-Абрамс,
6
@Joachim Sauer: Пример 2 выше. Правильный, явный цикл, не связанный со списком. Явный. Очистить. Очевидно.
S.Lott
31

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

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)

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

Катриэль
источник
@ Пол: Думаю, так и должно быть. И действительно, вы можете, хотя, mapвозможно, не будете настолько интуитивно понятны, если раньше не занимались функциональным программированием.
Катриэль
4
Не уверен, что это особенно идиоматично. Нет никаких преимуществ перед использованием явного цикла.
Marcin
1
Решениеconsume = collections.deque(maxlen=0).extend
PaulMcG
24

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

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

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

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

Подумайте о человеке, который должен понимать ваш код. С первой можно легко получить плохую карму :)

Вы можете пойти посередине между ними, используя 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)
user237419
источник
10
Ваша лямбда гораздо лучше написана как lambda x : x > 3.
PaulMcG
Вам даже не нужен фильтр. Просто поместите выражение генератора в круглых скобках здесь: for el in (x for x in y if x > 3):. elи xможет иметь то же имя, но это может сбить с толку людей.
Omnifarious
3

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

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

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

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

Rubayeet
источник
0

Ты можешь сделать

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

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

сига
источник
-1

Использование списка побочных эффектов уродливо, непитонично, неэффективно, и я бы не стал этого делать. 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 форма, возможно, немного более ясна, хотя я думаю, что обе они сбивают с толку и не должны использоваться.

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

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

Всевозможный
источник
Что если fun_with_side_effectsвернет True?
Катриэль
7
Я считаю, что это лекарство хуже болезни - itertools.consume намного чище.
PaulMcG
@PaulMcG - itertools.consumeбольше не существует, вероятно, потому, что использование понимания с побочными эффектами некрасиво.
Omnifarious
1
Оказывается, я ошибался, и его никогда не существовало в качестве метода в stdlib. Это является рецептом в itertools документы: docs.python.org/3/library/...
PaulMcG