Как стать автором
Обновить

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

There should be one - and preferably only one -obvious way to do it.

The Zen of Python.

Ну, эти-то пути совершенно unobvious.

[itertools]Этот набор инструментов содержит множество полезных методов, покрывающих наши потребности при работе с циклами.

Удобно приводить примеры на простых данных, называя всё это искусством.

Если бы потребности полностью покрывались itertools, то не придумали бы расширяющую lib more-itertools И даже они вместе не покрывают полные потребности при работе с циклами (сложные списки/вложенные с разными данными).

Очевидно, что "наши потребности" из статьи не равны "полным потребностям".

Вместо одного цикла, в случае even_only, мы получаем два цикла. Оптимальненько

Не получаем, потому что генератор работает синхронно, он генерирует yield когда выполняется условие, в этот момент сдвигается цикл вызвавший генератор, дальше работает цикл в генераторе пропуская нечетные, и снова yield по условии. Цена у такого решения O(n)
Вы точно поняли смысл данного примера?

В примере не объясняется работа генератора

Т.е. вы не поняли, но мнение высказали ))
Речь в статье о циклах, а не о генераторах, которые к слову являются основами.
Поясните, пожалуйста, зачем внутри even_only for.
очевидно для обхода итерируемого объекта, в данном случае списка
Да, протупил, почему-то показалось, что передается не лист, а его элемент.
Это другое, понимать надо!

Генераторы, функции с yield-ой, строго говоря, не являются дополнительными циклами.

От "елды" под столом. Зачёт!

Читал как «Песнь льда и огня»! Вот бы примеров в два, нет, в три, нет, в четыре раза побольше!
НЛО прилетело и опубликовало эту надпись здесь
«Существует не меньше трёх способов создания бесконечного цикла:»…
где-то в недрах C грустит одинокий while(1)
Он не одинок, с ним в компании как минимум for(;;)
Хм. Что бы макрос о нескольких операторах был «настоящим», а не «глючным», он должен быть циклом однократного исполнения:
do {
    ;
} while(0)


Но тема циклических хитростей языка C ещё ждёт своих авторов.
Лучше оборачивать в if (1){...} else ((void)0) По ссылке описано, что ломается в первом случае и остаётся во втором.
Увы, товарищ явно поторопился и не всё продумал.

Тривиальный контрпример, который вполне может случится с совершенно обычными макросами, без странных извращений, скажем, у макросов из ядра того же FreeBSD и т.п., если, не дай Бог, последовать непроверенным советам этого товарища:
#include <stdio.h>

#define PASSWORD_IF() \
    		if(1) { \
		    printf("Password if - "); \
		} else ((void)0)

#define PASSWORD_DO_WHILE() \
    		do { \
		    printf("Password do_while - "); \
		} while(0)

void PASSWORD_FUNCTIOM() {
    printf("Password function - ");
}
    		
int main()
{
    PASSWORD_FUNCTIOM(), printf("revocation function\n");
    printf("empty line\n");
    // hw.c:29:24: error: expected ';' after do/while statement
    // PASSWORD_DO_WHILE(), printf("revocation do_while\n");
    printf("empty line\n");
    PASSWORD_IF(), printf("revocation if\n");
    printf("empty line\n");

    return 0;
}

Ясен перец, выдача получается:
$ ./a.out
Password function - revocation function
empty line
empty line
Password if - empty line


Имеем необнаружимую на этапе компиляции ошибку. Выражение `printf(«revocation if\n»)' было съедено без какой-либо диагностики. Мало того, для крайних clang/gcc, ключи `clang-mp-11 -O -Wall -std=c11 hw.c' не выдают никаких предупреждений.

P.S.
На всяких lint-ах и прочих анализаторах, я этот код не проверял, но боюсь, что и они не заметят. Маловероятный шаблон, ибо, так делают макросы только весьма выдающиеся товарищи.
Извините, но Ваш пример похож на анекдот про сибирских мужиков и бензопилу. По крайней мере, примеры из блога по ссылке мне кажутся гораздо жизненнее приведенного.
Вызов функционального макроса это не вызов функции, они, несмотря неотличимый синтаксис, отличаются, как их не напиши. Но «неглючный» способ написания функционального макроса обязан приводить к ошибке компиляции в таких случаях, т.е. обязательное требование — "либо корректный код, либо ошибка компиляции".

А в данном случае, если при ошибке в одном символе вместо 'М(a); f(a)' оказалась 'М(a), f(a)', то при использовании шаблона 'if(0){...} else ((void)0)' это не приведет, ни к синтаксической ошибке, ни к корректному поведению.

И, если Вы посмотрите на творчество данного товарища:
#if p99_has_feature(statement_expression)
# define P99_PREFER(...) /* avoid the dangling else problem */              \
for (_Bool p00 = 1; p00 && p99_extension ({ { __VA_ARGS__ } 1; }); p00 = 0)
#else
# define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#endif
, то заметите что и он, на самом деле, использует иной шаблон (статья старая, а код, вероятно, с тех пор был исправлен).

Если использовать цикл for(;;) и расширение GCC, то удаётся получить поведение более менее идентичное обычному вызову функции (паразитное выражение после оператора ',' будет выполнено). И к такому шаблону для макросов претензий нет.

Если использовать традиционный шаблон 'do {… } while(0)', то возникнет синтаксическая ошибка. Обидно, но тоже без претензий.

Но шаблон, 'if(0){...} else ((void)0)' строго нельзя использовать для обычных функциональных макросов с обычными аргументами.

Насчёт, жизненнее?! Забавно, т.е. Вы каждый день пишите что-то вроде SUPER_MACROS(if(xy) break;) ?! Бывает, конечно. Однако, если просто взять статистику по существующему коду, то подавляющее большинство макросов так свои аргументы не используют.

Однако и для макросов с аргументами в виде фрагментов кода, на мой взгляд, шаблон 'if(0){...} else ((void)0)' не применим, так же как и не применим шаблон 'do {… } while(0)', они оба неспособны выполнить требование: «либо корректный код, либо ошибка компиляции».

Лично я, для макросов с аргументами в виде фрагментов кода, мог бы допустить только шаблон '{… }', да, код:
if (<condition>)
  M(a);
else
  f(a);
Будет приводить к ошибке компиляции, да это ограничение для таких макросов, но это гораздо лучше некорректного результирующего кода.

Если макросы с аргументами в виде фрагментов кода будут использованы только с продвинутыми GCC или CLang, то вариант цикла for(;;) и расширение GCC тоже допустим.
А мне наоборот не нравятся расширения как всё, что в явном виде отсутствует в стандарте, собственно, библиотека P99 изначально писалась для того, чтобы на основе С99 делать всякие интересные штуки и не зависеть от компилятора. На оператор «запятая» я тоже делаю стойку: он достаточно редок и как будто говорит «эй, я тут делаю что-то грязное, например, лезу в глобальное состояние».
Вы каждый день пишите что-то вроде SUPER_MACROS(if(xy) break;) ?!
Ну, XCASE и XDEFAULT удобные, чтобы проверить на микроконтроллерах не скорраптил ли ты сам себе память.
обязательное требование — «либо корректный код, либо ошибка компиляции»
Эх, весь блог PVS-Studio про то, как часто это не соблюдается для, казалось бы, очевидного кода.
EDIT извините за некоторую резкость, я просто достаточно часто использую break и continue, а также по мелочи глумлюсь над макроязыком, поэтому привык к варианту с if(1) {} else ((void)0). Для моих случаев он действительно «прочнее» классики do {} while(0)
На оператор «запятая» я тоже делаю стойку: он достаточно редок и как будто говорит «эй, я тут делаю что-то грязное, например, лезу в глобальное состояние».
Вопрос, как Вы их обнаруживаете?
Эх, весь блог PVS-Studio про то, как часто это не соблюдается для, казалось бы, очевидного кода.
PVS-Studio, в принципе, способен обнаруживать «hw1.c 37 note V2528 [MISRA-C-12.3] The comma operator should not be used.». Но без различий использования оператора ',', внутри операторов 'for(;;)' или выдающих значения выражений в полезных макросах.

Наверное, можно опробовать их убедить, что оператор ',' без использования его результата и вне цикла 'for(;;)' это потенциально проблемная конструкция требующая предупреждения, которое должно иметь более высокий приоритет, скажем, 'GA:2'.
поэтому привык к варианту с if(1) {} else ((void)0). Для моих случаев он действительно «прочнее» классики do {} while(0)
Когда-нибудь он устроит Вам геморой.

Если у Вас высокая дисциплина программирования, т.е. блоки «if(){}else{}» всегда имеют фигурные скобки, как того рекомендуют некоторые стили кодирования, тот же MISRA-C-15.6, то нет смысла «хитрить». Чем проще, тем надёжнее и разумнее использовать вариант '{...}'.

Если низкая, то разумнее уж «хитрить», так «хитрить» с учётом всех известных проблем: 'for (_Bool __p_ = 1; __p_ && __extension__({{… } (_Bool)1; }); __p_ = (_Bool)0) ((void)0)'.

Ну, или постоянное использование PVS-Studio или иного анализатора настроенного на детектирование проблем оператора ',' сможет хоть как-то оградить от подобных проблем.
Вопрос, как Вы их обнаруживаете?
В основном «глаз пристрелямши», если ведёт себя странно — гоняю линтер clang'a. Я стараюсь писать по MISRA, хотя и не могу гарантировать, что всегда получается.
Не, это уже не труЪ, всё-таки не совсем цикл, хотя компилироваться должно одинаково. Так-то и setjmp/longjmp можно.
В питоне можно while(True) делать. Не знаю почему автор не использовал это в статье. Видимо возможности for циклов затмили мозг.

Да, также весьма удивлён. Лучше без скобок, просто:

while True:
  pass
За что люблю питон, так это за то, что там можно реализовать даже такой извращенный подход к циклам:
class cycles:
	def __init__(self,a,b):
		self.a,self.b=a,b
	def __iter__(self):
		return self
	def __next__(self):
		while self.a<self.b:
			self.a+=1
			return self.a
	def __repr__(self):
		return str(self.a)

>>> x=cycles(10,20)
>>> while next(x):
	print(x)

	
11
12
13
14
15
16
17
18
19
20

Да, протокол итератора - крутая штука!

Возможно, кому-нибудь будет также полезно узнать о существовании else оператора в for-цикле, чтобы обработать случай, когда мы уже прошли все итерации цикла до конца:


for item in container:
    if search_something(item):
        # Found it!
        process(item)
        break
else:
    # Didn't find anything..
    not_found_in_container()
Да не может быть!!! Вот ценная, так ценная информация. И ведь ни где про это не говорится, даже на любых обучающих сайтах/курсах.

Кстати на счет "полезно узнать" Бретт Слаткин с вами не согласен, в своей книге Эффективный Пайтон он как раз предостерегает от использования подобной конструкции:- "Avoid using else blocks after loops because their behaviour isn’t intuitive and can be confusing."

Лучше вообще не использовать цикл for. вместо него есть list comprehension.

Перед тем, как использовать itertools.groupby нужно отсортировать входящие данные.
Без сортировки:


for item, group in itertools.groupby([1, 2, 3, 1, 2, 3]):
    print(item, list(group))
# 1 [1]
# 2 [2]
# 3 [3]
# 1 [1]

С сортировкой:


for item, group in itertools.groupby(sorted([1, 2, 3, 1, 2, 3])):
    print(item, list(group))
# 1 [1, 1]
# 2 [2, 2]
# 3 [3, 3]

https://docs.python.org/3/library/itertools.html#itertools.groupby

Подскажите, в чем сокральная разница между
for i in chain(list_a, list_b, list_c):
    print(i)

и
for i in list_a + list_b + list_c:
    print(i)


И ещё вопрос, про product. Хорошо, если нужно три подряд вложенных цикла объеденить, но если нужно выполнить некоторые действия и только потом входить во вложенный цикл (что гораздо чаще случается), то выходит что он не поможет??

Оба делают одно и то же. Первый вариант создаёт итератор. Второй создаёт новый список с копиями элементов всех списков. Может не хватить памяти. Первый вариант позволяет смешивать коллекции разных видов (список, кортеж, множество, любые итераторы). Второй умеет работать только с одинаковыми типами - все списки или все кортежи.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий