HTML Diff
1 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>В Python есть много возможностей, которые привлекают математиков. Вот некоторые из них: встроенная поддержка кортежей, списков и множеств, которые записываются практически так же, как это делается в математике, list comprehensions или генераторы списков, синтаксис которых похож на генераторы множеств, и другое.</p>
1 <p>В Python есть много возможностей, которые привлекают математиков. Вот некоторые из них: встроенная поддержка кортежей, списков и множеств, которые записываются практически так же, как это делается в математике, list comprehensions или генераторы списков, синтаксис которых похож на генераторы множеств, и другое.</p>
2 <p>Ещё один набор полезных для математиков инструментов - итераторы и генераторы, а также связанный с ними модуль itertools. Эти инструменты позволяют писать элегантный код при работе с такими математическими объектами, как бесконечные последовательности, случайные процессы, рекуррентные отношения и комбинаторные структуры. В этой публикации описана работа с итераторами и генераторами в Python.</p>
2 <p>Ещё один набор полезных для математиков инструментов - итераторы и генераторы, а также связанный с ними модуль itertools. Эти инструменты позволяют писать элегантный код при работе с такими математическими объектами, как бесконечные последовательности, случайные процессы, рекуррентные отношения и комбинаторные структуры. В этой публикации описана работа с итераторами и генераторами в Python.</p>
3 <h2>Содержание</h2>
3 <h2>Содержание</h2>
4 <ul><li><a>Итераторы</a></li>
4 <ul><li><a>Итераторы</a></li>
5 <li><a>Генераторы</a></li>
5 <li><a>Генераторы</a></li>
6 <li><a>Рекурсивные генераторы</a></li>
6 <li><a>Рекурсивные генераторы</a></li>
7 <li><a>Генераторные выражения</a></li>
7 <li><a>Генераторные выражения</a></li>
8 <li><a>Модуль itertools</a></li>
8 <li><a>Модуль itertools</a></li>
9 </ul><h2>Итераторы</h2>
9 </ul><h2>Итераторы</h2>
10 <p>Итераторы - объекты, которые позволяют обходить коллекции. Коллекции не должны обязательно существовать в памяти и быть конечными. Официальную документацию смотрите<a>по ссылке</a>.<em>Примечание редактора: также смотрите наш<a>курс по спискам</a>.</em></p>
10 <p>Итераторы - объекты, которые позволяют обходить коллекции. Коллекции не должны обязательно существовать в памяти и быть конечными. Официальную документацию смотрите<a>по ссылке</a>.<em>Примечание редактора: также смотрите наш<a>курс по спискам</a>.</em></p>
11 <p>Давайте уточним определения. Итерируемый - объект, в котором есть метод __iter__. В свою очередь, итератор - объект, в котором есть два метода: __iter__ и __next__. Почти всегда iterator возвращает себя из метода __iter__, так как они выступают итераторами для самих себя, но есть исключения.</p>
11 <p>Давайте уточним определения. Итерируемый - объект, в котором есть метод __iter__. В свою очередь, итератор - объект, в котором есть два метода: __iter__ и __next__. Почти всегда iterator возвращает себя из метода __iter__, так как они выступают итераторами для самих себя, но есть исключения.</p>
12 <p>В целом стоит избегать прямого вызова __iter__ и __next__. При использовании for или генераторов списков Python вызывает эти методы сам. Если всё-таки необходимо вызвать методы напрямую, используйте встроенные функции iter и next и в параметрах передавайте итератор или контейнер. Например, если c - итерируемый, используйте конструкцию iter(c) вместо c.__iter__(). Если a - итератор, используйте next(a), а не a.__next__(). Это похоже на использование len.</p>
12 <p>В целом стоит избегать прямого вызова __iter__ и __next__. При использовании for или генераторов списков Python вызывает эти методы сам. Если всё-таки необходимо вызвать методы напрямую, используйте встроенные функции iter и next и в параметрах передавайте итератор или контейнер. Например, если c - итерируемый, используйте конструкцию iter(c) вместо c.__iter__(). Если a - итератор, используйте next(a), а не a.__next__(). Это похоже на использование len.</p>
13 <p>Раз уж речь зашла о len, то стоит упомянуть, что итераторы не должны иметь и часто не имеют определённой длины. Поэтому в них часто нет имплементации __len__. Чтобы подсчитать количество элементов в итераторе, приходится делать это вручную или использовать sum. Пример есть ниже после раздела о модуле itertools.</p>
13 <p>Раз уж речь зашла о len, то стоит упомянуть, что итераторы не должны иметь и часто не имеют определённой длины. Поэтому в них часто нет имплементации __len__. Чтобы подсчитать количество элементов в итераторе, приходится делать это вручную или использовать sum. Пример есть ниже после раздела о модуле itertools.</p>
14 <p>Некоторые итерируемые (iterable) не являются итераторами, но используют другие объекты как итераторы. Например, объект list относится к итерируемым, но не является итератором. В нём реализован метод __iter__, но отсутствует метод __next__. Итераторы объектов list относятся к типу listiterator. Обратите внимание, у объектов list есть определённая длина, а у listiterator нету.</p>
14 <p>Некоторые итерируемые (iterable) не являются итераторами, но используют другие объекты как итераторы. Например, объект list относится к итерируемым, но не является итератором. В нём реализован метод __iter__, но отсутствует метод __next__. Итераторы объектов list относятся к типу listiterator. Обратите внимание, у объектов list есть определённая длина, а у listiterator нету.</p>
15 <p>Когда итератор завершает работу, интерпретатор Python ожидает возбуждения исключения StopIteration. Однако, как уже отмечалось, итераторы могут работать с бесконечными множествами. В таких случаях программист должен позаботиться о выходе из цикла.</p>
15 <p>Когда итератор завершает работу, интерпретатор Python ожидает возбуждения исключения StopIteration. Однако, как уже отмечалось, итераторы могут работать с бесконечными множествами. В таких случаях программист должен позаботиться о выходе из цикла.</p>
16 <p>Ниже дан простой пример итератора. Он считает с нуля до бесконечности. Это упрощённая версия itertools.count.</p>
16 <p>Ниже дан простой пример итератора. Он считает с нуля до бесконечности. Это упрощённая версия itertools.count.</p>
17 <p>Посмотрите пример использования. В последней строке сделана попытка превратить итератор в список. Это приводит к бесконечному циклу.</p>
17 <p>Посмотрите пример использования. В последней строке сделана попытка превратить итератор в список. Это приводит к бесконечному циклу.</p>
18 <p>Важная поправка к сказанному выше: если у объекта нет метода __iter__, его можно обойти, если определить метод __getitem__. В этом случае встроенная функция iter возвращает итератор с типом iterator, который использует __getitem__ для обхода элементов списка. Этот метод возвращает StopIteration или IndexError, когда обход завершается. Пример:</p>
18 <p>Важная поправка к сказанному выше: если у объекта нет метода __iter__, его можно обойти, если определить метод __getitem__. В этом случае встроенная функция iter возвращает итератор с типом iterator, который использует __getitem__ для обхода элементов списка. Этот метод возвращает StopIteration или IndexError, когда обход завершается. Пример:</p>
19 <p>И пример использования:</p>
19 <p>И пример использования:</p>
20 <p>Рассмотрим ещё один интересный пример: генерацию<a>последовательности Q Хофштадтера</a>. В приведённом ниже коде итератор используется для генерации последовательности с помощью вложенных повторений.</p>
20 <p>Рассмотрим ещё один интересный пример: генерацию<a>последовательности Q Хофштадтера</a>. В приведённом ниже коде итератор используется для генерации последовательности с помощью вложенных повторений.</p>
21 <p>Q(n)=Q(n-Q(n-1))+Q(n-Q(n-2))</p>
21 <p>Q(n)=Q(n-Q(n-1))+Q(n-Q(n-2))</p>
22 <p>Например, qsequence([1, 1]) генерирует точную последовательность Хофштадтера. Мы используем исключение StopIteration, чтобы показать, что последовательность не может продолжаться, так как для генерации следующего элемента должен использоваться несуществующий индекс. Если в параметрах указать значения [1, 2], последовательность немедленно заканчивается.</p>
22 <p>Например, qsequence([1, 1]) генерирует точную последовательность Хофштадтера. Мы используем исключение StopIteration, чтобы показать, что последовательность не может продолжаться, так как для генерации следующего элемента должен использоваться несуществующий индекс. Если в параметрах указать значения [1, 2], последовательность немедленно заканчивается.</p>
23 <p>Вот пример использования:</p>
23 <p>Вот пример использования:</p>
24 <h2>Генераторы</h2>
24 <h2>Генераторы</h2>
25 <p>Генераторами называют итераторы, определение которых выглядит как определение функций. Ещё одно определение: генераторы - функции, которые внутри используют выражение yield. Генераторы не могут возвращать значения, вместо этого выдают элементы по готовности. Python автоматизирует запоминание контекста генератора, то есть текущий поток управления, значение локальных переменных и так далее. Каждый вызов метода __next__ у объекта генератора возвращает следующее значение. Метод __iter__ также реализуется автоматически. То есть генераторы можно использовать везде, где требуются итераторы. Пример кода ниже похож на ранее рассмотренный класс итератора. Но этот код более компактный и читабельный.</p>
25 <p>Генераторами называют итераторы, определение которых выглядит как определение функций. Ещё одно определение: генераторы - функции, которые внутри используют выражение yield. Генераторы не могут возвращать значения, вместо этого выдают элементы по готовности. Python автоматизирует запоминание контекста генератора, то есть текущий поток управления, значение локальных переменных и так далее. Каждый вызов метода __next__ у объекта генератора возвращает следующее значение. Метод __iter__ также реализуется автоматически. То есть генераторы можно использовать везде, где требуются итераторы. Пример кода ниже похож на ранее рассмотренный класс итератора. Но этот код более компактный и читабельный.</p>
26 <p>Посмотрите, как это применяется на практике.</p>
26 <p>Посмотрите, как это применяется на практике.</p>
27 <p>Теперь взгляните на реализацию последовательности Q Хофштадтера с помощью генератора. Заметьте, эта реализация значительно проще использованного выше подхода. Однако здесь уже невозможно использовать методы типа current_state. Извне невозможно получить доступ к переменным, которые хранятся в контексте генератора.</p>
27 <p>Теперь взгляните на реализацию последовательности Q Хофштадтера с помощью генератора. Заметьте, эта реализация значительно проще использованного выше подхода. Однако здесь уже невозможно использовать методы типа current_state. Извне невозможно получить доступ к переменным, которые хранятся в контексте генератора.</p>
28 <blockquote><p>Существует словарь gi_frame.f_locals, но он относится к CPython, но не входит в стандарт языка Python.</p>
28 <blockquote><p>Существует словарь gi_frame.f_locals, но он относится к CPython, но не входит в стандарт языка Python.</p>
29 </blockquote><p>Одно из возможных решений - получение одновременно списка и результата.</p>
29 </blockquote><p>Одно из возможных решений - получение одновременно списка и результата.</p>
30 <p>def hofstadter_generator(s): a = s[:] while True: try: q = a[-a[-1]] + a[-a[-2]] a.append(q) yield q except IndexError: return</p>
30 <p>def hofstadter_generator(s): a = s[:] while True: try: q = a[-a[-1]] + a[-a[-2]] a.append(q) yield q except IndexError: return</p>
31 <p>Обратите внимание, итерация в данном примере завершается простым return без параметров. Внутри происходит возбуждение исключения StopIteration. Следующий пример связан с<a>распределением Бернулли</a>, которое реализуется с помощью двух генераторов. Речь идёт о бесконечной последовательности случайных булевых значений. При этом вероятность True равна p, а вероятность False определяется формулой q=1-p. Затем применяется экстрактор фон Неймана, который принимает процесс Бернулли с 0 &lt; p &lt; 1 как источник энтропии и возвращает чистый процесс Бернулли с p = 0.5.</p>
31 <p>Обратите внимание, итерация в данном примере завершается простым return без параметров. Внутри происходит возбуждение исключения StopIteration. Следующий пример связан с<a>распределением Бернулли</a>, которое реализуется с помощью двух генераторов. Речь идёт о бесконечной последовательности случайных булевых значений. При этом вероятность True равна p, а вероятность False определяется формулой q=1-p. Затем применяется экстрактор фон Неймана, который принимает процесс Бернулли с 0 &lt; p &lt; 1 как источник энтропии и возвращает чистый процесс Бернулли с p = 0.5.</p>
32 <p>import random def bernoulli_process(p): if p &gt; 1.0 or p &lt; 0.0: raise ValueError("p should be between 0.0 and 1.0.") while True: yield random.random() &lt; p def von_neumann_extractor(process): while True: x, y = next(proccess), next(process) if x != y: yield x</p>
32 <p>import random def bernoulli_process(p): if p &gt; 1.0 or p &lt; 0.0: raise ValueError("p should be between 0.0 and 1.0.") while True: yield random.random() &lt; p def von_neumann_extractor(process): while True: x, y = next(proccess), next(process) if x != y: yield x</p>
33 <p>Наконец, с помощью генераторов удобно реализовывать дискретные динамические системы. Пример ниже показывает, как с помощью генераторов реализуется<a>отображение тент</a>.</p>
33 <p>Наконец, с помощью генераторов удобно реализовывать дискретные динамические системы. Пример ниже показывает, как с помощью генераторов реализуется<a>отображение тент</a>.</p>
34 <p>&gt;&gt;&gt; def tent_map(mu, x0): ... x = x0 ... while True: ... yield x ... x = mu * min(x, 1.0 - x) ... &gt;&gt;&gt; &gt;&gt;&gt; t = tent_map(2.0, 0.1) &gt;&gt;&gt; for __ in range(30): ... print(next(t)) ... 0.1 0.2 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.799999999999 0.400000000001 0.800000000003 0.399999999994 0.799999999988 0.400000000023 0.800000000047 0.399999999907 0.799999999814 0.400000000373 0.800000000745 0.39999999851 0.79999999702</p>
34 <p>&gt;&gt;&gt; def tent_map(mu, x0): ... x = x0 ... while True: ... yield x ... x = mu * min(x, 1.0 - x) ... &gt;&gt;&gt; &gt;&gt;&gt; t = tent_map(2.0, 0.1) &gt;&gt;&gt; for __ in range(30): ... print(next(t)) ... 0.1 0.2 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.8 0.4 0.799999999999 0.400000000001 0.800000000003 0.399999999994 0.799999999988 0.400000000023 0.800000000047 0.399999999907 0.799999999814 0.400000000373 0.800000000745 0.39999999851 0.79999999702</p>
35 <p>Ещё один пример касается<a>последовательности Коллатца</a>.</p>
35 <p>Ещё один пример касается<a>последовательности Коллатца</a>.</p>
36 <p>Обратите внимание, в этом примере не нужно вручную использовать StopIteration. Это исключение срабатывает автоматически, когда поток управления достигает конца функции.</p>
36 <p>Обратите внимание, в этом примере не нужно вручную использовать StopIteration. Это исключение срабатывает автоматически, когда поток управления достигает конца функции.</p>
37 <p>Пример использования генератора:</p>
37 <p>Пример использования генератора:</p>
38 <p>&gt;&gt;&gt; # Если гипотеза Коллатца верна, list(collatz(n)) с любым n ... # всегда завершается &gt;&gt;&gt; list(collatz(7)) [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] &gt;&gt;&gt; list(collatz(13)) [13, 40, 20, 10, 5, 16, 8, 4, 2, 1] &gt;&gt;&gt; list(collatz(17)) [17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] &gt;&gt;&gt; list(collatz(19)) [19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]</p>
38 <p>&gt;&gt;&gt; # Если гипотеза Коллатца верна, list(collatz(n)) с любым n ... # всегда завершается &gt;&gt;&gt; list(collatz(7)) [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] &gt;&gt;&gt; list(collatz(13)) [13, 40, 20, 10, 5, 16, 8, 4, 2, 1] &gt;&gt;&gt; list(collatz(17)) [17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] &gt;&gt;&gt; list(collatz(19)) [19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]</p>
39 <h2>Рекурсивные генераторы</h2>
39 <h2>Рекурсивные генераторы</h2>
40 <p>Генераторы могут быть рекурсивными, как любая другая функция. Рассмотрим это на примере собственной реализации itertools.permutations. Это генератор перестановок элементов в списке. Обратите внимание, стандартная функция itertools.permutations работает быстрее, поэтому используйте её на практике. Идея простая: функция перемещает каждый элемент списка на первое место, заменяя его первым элементом в списке.</p>
40 <p>Генераторы могут быть рекурсивными, как любая другая функция. Рассмотрим это на примере собственной реализации itertools.permutations. Это генератор перестановок элементов в списке. Обратите внимание, стандартная функция itertools.permutations работает быстрее, поэтому используйте её на практике. Идея простая: функция перемещает каждый элемент списка на первое место, заменяя его первым элементом в списке.</p>
41 <h2>Генераторные выражения</h2>
41 <h2>Генераторные выражения</h2>
42 <p>Генераторные выражения позволяют реализовать генераторы с помощью очень простого синтаксиса. В примере ниже представлен генератор, который обходит все совершенные квадраты. Результатами генераторных выражений являются объекты с типом generator. В них реализованы методы __next__ и __iter__.</p>
42 <p>Генераторные выражения позволяют реализовать генераторы с помощью очень простого синтаксиса. В примере ниже представлен генератор, который обходит все совершенные квадраты. Результатами генераторных выражений являются объекты с типом generator. В них реализованы методы __next__ и __iter__.</p>
43 <p>Процесс Бернулли из предыдущих разделов также можно реализовать с помощью генераторных выражений. В данном примере p=0.4. Если в генераторном выражении требуется ещё один итератор, который используется в качестве счётчика цикла, можно использовать itertools.count для генерации бесконечной последовательности. В противном случае можно использовать range.</p>
43 <p>Процесс Бернулли из предыдущих разделов также можно реализовать с помощью генераторных выражений. В данном примере p=0.4. Если в генераторном выражении требуется ещё один итератор, который используется в качестве счётчика цикла, можно использовать itertools.count для генерации бесконечной последовательности. В противном случае можно использовать range.</p>
44 <p>Как отмечалось выше, генераторные выражения можно передавать в функции, которые нуждаются в итераторе. Например, сумму первых десяти совершенных квадратов можно получить так:</p>
44 <p>Как отмечалось выше, генераторные выражения можно передавать в функции, которые нуждаются в итераторе. Например, сумму первых десяти совершенных квадратов можно получить так:</p>
45 <p>Ниже будут другие примеры генераторных выражений.</p>
45 <p>Ниже будут другие примеры генераторных выражений.</p>
 
46 + <h2>Модуль itertools</h2>
46 <p>В модуле itertools есть набор итераторов, которые упрощают работу с перестановками, комбинациями, декартовыми произведениями и другими комбинаторными структурами. Документация доступна<a>по ссылке</a>.</p>
47 <p>В модуле itertools есть набор итераторов, которые упрощают работу с перестановками, комбинациями, декартовыми произведениями и другими комбинаторными структурами. Документация доступна<a>по ссылке</a>.</p>
47 <p>Обратите внимание, представленные ниже алгоритмы не являются оптимальными для практического использования. Примеры используются, чтобы показать возможности перестановок и комбинаций. На практике лучше избегать перечисления перестановок и комбинаций, если вы не имеете веской причины для этого, так как размер перечислений растёт по экспоненте.</p>
48 <p>Обратите внимание, представленные ниже алгоритмы не являются оптимальными для практического использования. Примеры используются, чтобы показать возможности перестановок и комбинаций. На практике лучше избегать перечисления перестановок и комбинаций, если вы не имеете веской причины для этого, так как размер перечислений растёт по экспоненте.</p>
48 <p>Посмотрите на интересные примеры ниже. В первом представлен альтернативный способ реализации известного паттерна: обход трёхмерного массива, а также обход всех индексов с 0≤i&lt;j&lt;k≤n.</p>
49 <p>Посмотрите на интересные примеры ниже. В первом представлен альтернативный способ реализации известного паттерна: обход трёхмерного массива, а также обход всех индексов с 0≤i&lt;j&lt;k≤n.</p>
49 <p>Альтернативные реализации с использованием перечислителей itertools обеспечивают два преимущества: они короче (одна строка вместо трёх), и их проще обобщать. Нужно помнить, что разница в быстродействии может быть значительной, особенно при больших значениях n.</p>
50 <p>Альтернативные реализации с использованием перечислителей itertools обеспечивают два преимущества: они короче (одна строка вместо трёх), и их проще обобщать. Нужно помнить, что разница в быстродействии может быть значительной, особенно при больших значениях n.</p>
50 <p>Второй пример касается интересной математической задачи. С помощью генераторных выражений, itertools.combinations и itertools.permutations вычислим количество инверсий перестановки, а затем суммируем количество инверсий во всех перестановках в списке.</p>
51 <p>Второй пример касается интересной математической задачи. С помощью генераторных выражений, itertools.combinations и itertools.permutations вычислим количество инверсий перестановки, а затем суммируем количество инверсий во всех перестановках в списке.</p>
51 <p>Сумма количества инверсий перестановок n равна<a>OEIS A001809</a>. Поэтому относительно легко показать, что закрытая форма равна n!n(n-1)/4. На практике эта формула эффективнее, но пример кода ниже необходим для отработки применения перечислителей itertools.</p>
52 <p>Сумма количества инверсий перестановок n равна<a>OEIS A001809</a>. Поэтому относительно легко показать, что закрытая форма равна n!n(n-1)/4. На практике эта формула эффективнее, но пример кода ниже необходим для отработки применения перечислителей itertools.</p>
52 <p>Пример использования:</p>
53 <p>Пример использования:</p>
53 <p>В качестве ещё одного примера давайте рассчитаем количество повторений с помощью метода полного перебора. Даны n и k, количество повторений Dn,k определяется как количество перестановок множества n, в котором k фиксированных значений.</p>
54 <p>В качестве ещё одного примера давайте рассчитаем количество повторений с помощью метода полного перебора. Даны n и k, количество повторений Dn,k определяется как количество перестановок множества n, в котором k фиксированных значений.</p>
54 <p>Сначала пишем функцию, которая использует генераторное выражение для подсчёта фиксированных значений в перестановке. Затем используем itertools.permutations и ещё одно генераторное выражение, чтобы посчитать общее количество перестановок n, в которых k фиксированных значений. Ещё раз обратите внимание, код ниже неоптимальный, он служит для отработки применения генераторных выражений.</p>
55 <p>Сначала пишем функцию, которая использует генераторное выражение для подсчёта фиксированных значений в перестановке. Затем используем itertools.permutations и ещё одно генераторное выражение, чтобы посчитать общее количество перестановок n, в которых k фиксированных значений. Ещё раз обратите внимание, код ниже неоптимальный, он служит для отработки применения генераторных выражений.</p>
55 <p>Применение:</p>
56 <p>Применение:</p>
56 <p>В статье рассмотрели особенности использования итераторов, генераторов и модуля itertools в Python. Вопросы и пожелания пишите в комментариях.</p>
57 <p>В статье рассмотрели особенности использования итераторов, генераторов и модуля itertools в Python. Вопросы и пожелания пишите в комментариях.</p>
57 <p><em>Адаптированный перевод статьи<a>A Study of Python's More Advanced Features Part I: Iterators, Generators, itertools</a>by Sahand Saba. Мнение адмнистрации "Хекслета" может не совпадать с мнением автора оригинальной публикации.</em></p>
58 <p><em>Адаптированный перевод статьи<a>A Study of Python's More Advanced Features Part I: Iterators, Generators, itertools</a>by Sahand Saba. Мнение адмнистрации "Хекслета" может не совпадать с мнением автора оригинальной публикации.</em></p>