В Python лучше использовать понимание списка или для каждого цикла?

#python

#python #стиль кодирования #foreach #понимание списка

Вопрос:

Что из следующего лучше использовать и почему?

Метод 1:

 for k, v in os.environ.items():
       print "%s=%s" % (k, v)
 

Способ 2:

 print "n".join(["%s=%s" % (k, v) 
   for k,v in os.environ.items()])
 

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

(Код взят из погружения в Python)

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

1. Печать в любом случае идет медленно — перейдите к удобочитаемости (для цикла). В общем, сначала следите за удобочитаемостью. Затем, когда это кажется медленным, профилируйте и оптимизируйте. Если вы не разрабатываете фреймворк (библиотеку), сначала вам может сойти с рук вонючий код. Тогда вы почувствуете, что работает, а что нет.

Ответ №1:

Если итерация выполняется для ее побочного эффекта (как в вашем примере «print»), тогда цикл становится понятнее.

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

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

1. 1: понимание списка происходит из функционального программирования, в то время как циклы for происходят из императивного программирования.

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

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

4. Как говорится, преждевременная оптимизация — корень всего зла. Напишите его так, чтобы его можно было читать, таким образом, когда вы сможете протестировать готовую программу и фактически выяснить, где требуется оптимизация, вы понимаете, что делает код.

5. @badp: И использование понимания списка для всего делает функциональные языки, такие как Haskell, очень трудными для чтения.

Ответ №2:

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

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

Пример, который ясно показал бы превосходство list comp, можно было бы создать, заменив print пример примером, включающим создание другого фактического списка, путем добавления к одному на каждой итерации for цикла:

 L = []
for x in range(10):
    L.append(x**2)
 

Дает то же L самое, что:

 L = [x**2 for x in range(10)]
 

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

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

2. Еще одним преимуществом является то, что вы можете передать результирующий список в другую функцию или в цепочку / гнездо.

Ответ №3:

Я нахожу первый пример лучше — менее подробный, понятный и более читаемый.

На мой взгляд, в конце концов, выбирайте то, что лучше всего отражает ваши намерения:

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

— из «Структуры и интерпретации компьютерных программ» Абельсона и Сассмана

Кстати, поскольку вы только начинаете изучать Python, начните изучать новый синтаксис форматирования строк прямо сейчас:

 for k, v in os.environ.items():
    print "{0}={1}".format(k, v)
 

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

1. пожалуйста, обратите внимание, что str.format() присутствует только в версии python> = 2.6

Ответ №4:

Понимание списка более чем в два раза быстрее, чем явный цикл. Основываясь на варианте Бена Джеймса, но замените x ** 2 более тривиальной функцией x 2, две альтернативы:

 def foo(n):
  L = []
  for x in xrange(n):
    L.append(x 2)
  return L


def bar(n):
  return [x 2 for x in xrange(n)]
 

Результат синхронизации:

 In [674]: timeit foo(1000)
10000 loops, best of 3: 195 us per loop

In [675]: timeit bar(1000)
10000 loops, best of 3: 81.7 us per loop
 

Понимание списка выигрывает с большим отрывом.

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

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

1. Точно, с пониманием списков вы пишете более высокопроизводительный код, который будет понятен продвинутым питонистам, а не студентам первого курса.

2. понимание списка лучше выражает фактическую логику, близкую к нотации set builder. Цикл for должен быть «выполнен в голове», чтобы увидеть, что он делает, в целом.

3. Я не пробовал в реальном Python, но ваш пример кода выглядит так, как будто прирост производительности может быть сколь угодно большим за счет увеличения n: если Python не сделает что-то, чтобы противостоять этому эффекту, foo использует алгоритм O (n ^ 2), в то время как bar выполняется в O (n) .

4. foo — это O (n) так же, как bar . Python list.append равен O (1) .

Ответ №5:

Первый, на мой взгляд, потому что:

  • Он не создает огромную строку.
  • Он не создает огромный список (его можно легко исправить с помощью генератора, удалив [] ).

В обоих случаях вы получаете доступ к элементам одинаково (используя итератор словаря).

Ответ №6:

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

Ответ №7:

Я согласен с @Ben, @Tim, @Steven:

  • удобочитаемость — самая важная вещь (сделайте «импортируйте это», чтобы напомнить себе о том, что есть)
  • listcomp может быть или не быть намного быстрее, чем версия с итеративным циклом… это зависит от общего количества выполненных вызовов функций
  • если вы решите использовать listcomps с большими наборами данных, вместо этого лучше использовать выражения генератора

Пример:

 print "n".join("%s=%s" % (k, v) for k,v in os.environ.iteritems())
 

в приведенном выше фрагменте кода я внес два изменения… Я заменил listcomp на genexp и изменил вызов метода на iteritems() . [эта тенденция развивается, как и в Python 3, iteritems() заменяет и переименовывается в items() .]

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

1. Почему удобочитаемость всегда и для всех важнее всего?

2. это менталитет с оплатой вперед, который делает такой код «питоническим …», проявляя доброту и уважение к вашим коллегам-программистам, особенно. поскольку ваш код, скорее всего, переживет вас на вашем рабочем месте. это трудно хорошо объяснить словами. опять же, проверьте «импортировать это»… the Zen of Python дает вам общие рекомендации, поскольку они относятся к философии Python.

3. @Warren: Поскольку удобочитаемость приводит к удобству обслуживания, а удобочитаемость значительно упрощает повышение производительности. Никогда не забывайте концентрироваться на тех частях, на которые указывает ваш профилировщик при оптимизации.