0 added
0 removed
Original
2026-01-01
Modified
2026-02-21
1
<p><a>#статьи</a></p>
1
<p><a>#статьи</a></p>
2
<ul><li>26 янв 2021</li>
2
<ul><li>26 янв 2021</li>
3
<li>0</li>
3
<li>0</li>
4
</ul><p>Генераторы используют, чтобы оперативная память не давилась большими объёмами информации. В Python это фишки, экономящие память.</p>
4
</ul><p>Генераторы используют, чтобы оперативная память не давилась большими объёмами информации. В Python это фишки, экономящие память.</p>
5
<p> vlada_maestro / shutterstock</p>
5
<p> vlada_maestro / shutterstock</p>
6
<p>Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.</p>
6
<p>Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.</p>
7
<p>Допустим, у вас есть файл, который весит десяток гигабайт. Из него нужно выбрать и обработать строки, подходящие под какое-то условие, а то и сравнить со строками другого большого файла.</p>
7
<p>Допустим, у вас есть файл, который весит десяток гигабайт. Из него нужно выбрать и обработать строки, подходящие под какое-то условие, а то и сравнить со строками другого большого файла.</p>
8
<p>Другой пример: нужно проанализировать практически бесконечный поток данных. Это могут быть, например, показания счётчиков, биржевые котировки, сетевой трафик.</p>
8
<p>Другой пример: нужно проанализировать практически бесконечный поток данных. Это могут быть, например, показания счётчиков, биржевые котировки, сетевой трафик.</p>
9
<p>А может, нужно создать поток данных самостоятельно: рассчитать комбинаторную структуру для определения вероятности какого-то события, математическую последовательность или последовательность случайных чисел.</p>
9
<p>А может, нужно создать поток данных самостоятельно: рассчитать комбинаторную структуру для определения вероятности какого-то события, математическую последовательность или последовательность случайных чисел.</p>
10
<p>Что делать? Хранить такие объёмы данных в компьютере нереально: они не поместятся в оперативную память - а некоторые и на жёсткий диск. Выход один - обрабатывать информацию небольшими порциями, чтобы не вызывать переполнения памяти. В Python на этот случай есть специальный инструмент - генераторы.</p>
10
<p>Что делать? Хранить такие объёмы данных в компьютере нереально: они не поместятся в оперативную память - а некоторые и на жёсткий диск. Выход один - обрабатывать информацию небольшими порциями, чтобы не вызывать переполнения памяти. В Python на этот случай есть специальный инструмент - генераторы.</p>
11
<ul><li>Генератор - это объект, который сразу при создании не вычисляет значения всех своих элементов.</li>
11
<ul><li>Генератор - это объект, который сразу при создании не вычисляет значения всех своих элементов.</li>
12
<li>Он хранит в памяти только последний вычисленный элемент, правило перехода к следующему и условие, при котором выполнение прерывается.</li>
12
<li>Он хранит в памяти только последний вычисленный элемент, правило перехода к следующему и условие, при котором выполнение прерывается.</li>
13
<li>Вычисление следующего значения происходит лишь при выполнении метода next(). Предыдущее значение при этом теряется.</li>
13
<li>Вычисление следующего значения происходит лишь при выполнении метода next(). Предыдущее значение при этом теряется.</li>
14
</ul><p>Этим генераторы отличаются от списков - те хранят в памяти все свои элементы, и удалить их можно только программно. Вычисления с помощью генераторов называются ленивыми, они экономят память.</p>
14
</ul><p>Этим генераторы отличаются от списков - те хранят в памяти все свои элементы, и удалить их можно только программно. Вычисления с помощью генераторов называются ленивыми, они экономят память.</p>
15
<p>Рассмотрим пример: создадим объект-генератор gen с помощью так называемого генераторного выражения. Он будет считать квадраты чисел от 1 до 4 - такую последовательность создаёт функция range(1,5).</p>
15
<p>Рассмотрим пример: создадим объект-генератор gen с помощью так называемого генераторного выражения. Он будет считать квадраты чисел от 1 до 4 - такую последовательность создаёт функция range(1,5).</p>
16
>>> a = (i**2 for i in range(1,5)) >>> a <generator object <genexpr> at 0x0000023A7524D6D0> >>> next(a) 1 >>> next(a) 4 >>> next(a) 9 >>> next(a) 16 >>> next(a) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration<p>Когда мы выведем на консоль переменную gen, то увидим лишь сообщение, что это объект-генератор.</p>
16
>>> a = (i**2 for i in range(1,5)) >>> a <generator object <genexpr> at 0x0000023A7524D6D0> >>> next(a) 1 >>> next(a) 4 >>> next(a) 9 >>> next(a) 16 >>> next(a) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration<p>Когда мы выведем на консоль переменную gen, то увидим лишь сообщение, что это объект-генератор.</p>
17
<p>При четырёх вызовах метода next(a) будут по одному рассчитываться и выводиться на консоль значения генератора: 1, 4, 9, 16. Причём в памяти будет сохраняться только последнее значение, а предыдущие сотрутся.</p>
17
<p>При четырёх вызовах метода next(a) будут по одному рассчитываться и выводиться на консоль значения генератора: 1, 4, 9, 16. Причём в памяти будет сохраняться только последнее значение, а предыдущие сотрутся.</p>
18
<p>Когда мы попытаемся вызвать next(gen) в пятый раз, генератор сотрёт из памяти последний элемент (число 16) и выдаст исключение StopIteration.</p>
18
<p>Когда мы попытаемся вызвать next(gen) в пятый раз, генератор сотрёт из памяти последний элемент (число 16) и выдаст исключение StopIteration.</p>
19
>>> next(gen) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration<p>Всё! Генератор больше не работает. Сколько бы мы ни вызывали next(gen), ничего считаться не будет. Чтобы запустить генератор ещё раз, придётся создавать его заново.</p>
19
>>> next(gen) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration<p>Всё! Генератор больше не работает. Сколько бы мы ни вызывали next(gen), ничего считаться не будет. Чтобы запустить генератор ещё раз, придётся создавать его заново.</p>
20
<p>Нет, значения можно вычислять в цикле<em>for</em>. В этом случае метод next() вызывается неявно. Например:</p>
20
<p>Нет, значения можно вычислять в цикле<em>for</em>. В этом случае метод next() вызывается неявно. Например:</p>
21
>>> a = (i**2 for i in range(1,5)) >>> for i in a: ... print(i) 1 4 9 16<p>Когда весь цикл пройден, произойдёт исключение StopIteration. Хотя на консоль сообщение об этом не выводится, но генератор помнит о нём и больше работать не будет. То есть цикл<em>for</em>можно запускать только один раз, во второй раз не получится. Нельзя об этом забывать.</p>
21
>>> a = (i**2 for i in range(1,5)) >>> for i in a: ... print(i) 1 4 9 16<p>Когда весь цикл пройден, произойдёт исключение StopIteration. Хотя на консоль сообщение об этом не выводится, но генератор помнит о нём и больше работать не будет. То есть цикл<em>for</em>можно запускать только один раз, во второй раз не получится. Нельзя об этом забывать.</p>
22
<p>Для этого сначала рассмотрим упрощённый способ создания генератора - с помощью генераторного выражения.</p>
22
<p>Для этого сначала рассмотрим упрощённый способ создания генератора - с помощью генераторного выражения.</p>
23
<p>Генераторные выражения позволяют создавать объект-генератор в одну строчку. В общем случае их пишут по шаблону:</p>
23
<p>Генераторные выражения позволяют создавать объект-генератор в одну строчку. В общем случае их пишут по шаблону:</p>
24
<p>(<em>выражение for j in итерируемый объект if условие</em>)</p>
24
<p>(<em>выражение for j in итерируемый объект if условие</em>)</p>
25
<p>Где<em>for,</em><em>in, if</em> - ключевые слова,<em>j</em> - переменная.</p>
25
<p>Где<em>for,</em><em>in, if</em> - ключевые слова,<em>j</em> - переменная.</p>
26
<p>Пример генераторного выражения мы рассмотрели выше. Теперь посмотрим, как можно применить его для обработки большого файла.</p>
26
<p>Пример генераторного выражения мы рассмотрели выше. Теперь посмотрим, как можно применить его для обработки большого файла.</p>
27
<p><strong>Перед нами задача</strong>: на сервере есть огромный журнал событий log.txt, в котором хранятся сведения о работе какой-то системы за год. Из него нужно выбрать и обработать для статистики данные об ошибках - строки, содержащие слово error.</p>
27
<p><strong>Перед нами задача</strong>: на сервере есть огромный журнал событий log.txt, в котором хранятся сведения о работе какой-то системы за год. Из него нужно выбрать и обработать для статистики данные об ошибках - строки, содержащие слово error.</p>
28
<p>Такие строки можно выбрать и сохранить в памяти с помощью списка:</p>
28
<p>Такие строки можно выбрать и сохранить в памяти с помощью списка:</p>
29
with open(path + "\log.txt", "r") as log_file: err_list = [st for st in log_file if "error" in st]<p>Здесь path - путь к файлу log. В результате сформируется список вида:</p>
29
with open(path + "\log.txt", "r") as log_file: err_list = [st for st in log_file if "error" in st]<p>Здесь path - путь к файлу log. В результате сформируется список вида:</p>
30
<p>[строка1, строка2, строка3, ….]</p>
30
<p>[строка1, строка2, строка3, ….]</p>
31
<p>В списке e_l содержатся все строки со словом error, они записаны в память компьютера. Теперь их можно обработать в цикле. Недостаток метода в том, что, если таких строк будет слишком много, они переполнят память и вызовут ошибку<em>MemoryError</em>.</p>
31
<p>В списке e_l содержатся все строки со словом error, они записаны в память компьютера. Теперь их можно обработать в цикле. Недостаток метода в том, что, если таких строк будет слишком много, они переполнят память и вызовут ошибку<em>MemoryError</em>.</p>
32
<p>Переполнения памяти можно избежать, если организовать поточную обработку данных с использованием объекта-генератора. Мы создадим его с помощью генераторного выражения (оно отличается от генератора списка только круглыми скобками).</p>
32
<p>Переполнения памяти можно избежать, если организовать поточную обработку данных с использованием объекта-генератора. Мы создадим его с помощью генераторного выражения (оно отличается от генератора списка только круглыми скобками).</p>
33
<p>Рассмотрим следующий код:</p>
33
<p>Рассмотрим следующий код:</p>
34
with open("path\log.txt", "r") as log_file: err_gen = (st for st in log_file if "error" in st) for item in err_gen: <обработка строки item><ul><li>Генераторное выражение возвращает объект-генератор err_gen.</li>
34
with open("path\log.txt", "r") as log_file: err_gen = (st for st in log_file if "error" in st) for item in err_gen: <обработка строки item><ul><li>Генераторное выражение возвращает объект-генератор err_gen.</li>
35
<li>Генератор начинает в цикле выбирать из файла по одной строке со словом error и передавать их на обработку.</li>
35
<li>Генератор начинает в цикле выбирать из файла по одной строке со словом error и передавать их на обработку.</li>
36
<li>Обработанная строка стирается из памяти, а следующая записывается и обрабатывается. И так до конца цикла.</li>
36
<li>Обработанная строка стирается из памяти, а следующая записывается и обрабатывается. И так до конца цикла.</li>
37
</ul><p>Этот метод не вызывает переполнения, так как в каждый момент времени в памяти находится только одна строка. При этом нужный для работы объём памяти не зависит от размера файла и количества строк, удовлетворяющих условию.</p>
37
</ul><p>Этот метод не вызывает переполнения, так как в каждый момент времени в памяти находится только одна строка. При этом нужный для работы объём памяти не зависит от размера файла и количества строк, удовлетворяющих условию.</p>
38
<p>Генераторы часто используют при веб-скрапинге. Они позволяют поочерёдно получать нужные веб-страницы и обрабатывать их информацию. Это намного эффективнее, чем загрузить в память сразу все выбранные страницы и затем обрабатывать их в цикле.</p>
38
<p>Генераторы часто используют при веб-скрапинге. Они позволяют поочерёдно получать нужные веб-страницы и обрабатывать их информацию. Это намного эффективнее, чем загрузить в память сразу все выбранные страницы и затем обрабатывать их в цикле.</p>
39
<p>Генераторные выражения - это упрощённый вариант функций-генераторов, также создающих генераторы.</p>
39
<p>Генераторные выражения - это упрощённый вариант функций-генераторов, также создающих генераторы.</p>
40
<p>Функция-генератор отличается от обычной функции тем, что вместо команды return в ней используется yield. И если return завершает работу функции, то инструкция yield лишь приостанавливает её, при этом она возвращает какое-то значение.</p>
40
<p>Функция-генератор отличается от обычной функции тем, что вместо команды return в ней используется yield. И если return завершает работу функции, то инструкция yield лишь приостанавливает её, при этом она возвращает какое-то значение.</p>
41
<p>При первом вызове метода next() выполняется код функции с первой команды до yield. При втором next() и последующих до конца генератора - код со следующей после yield команды и до тех пор, пока yield не встретится снова.</p>
41
<p>При первом вызове метода next() выполняется код функции с первой команды до yield. При втором next() и последующих до конца генератора - код со следующей после yield команды и до тех пор, пока yield не встретится снова.</p>
42
<p>Чтобы было понятнее, рассмотрим небольшой пример:</p>
42
<p>Чтобы было понятнее, рассмотрим небольшой пример:</p>
43
>>> def f_gen(m): ... s = 1 ... for n in range(1,m): ... yield n**2 + s ... s += 1 ... >>> a = f_gen(5) >>> a <generator object f_gen at 0x0000023EE468D6D0> >>> for i in a: ... print(i) ... 2 6 12 20 >>><p>Здесь функция f_gen(5) при вызове создаёт генератор a. Мы видим это, когда выводим a на консоль.</p>
43
>>> def f_gen(m): ... s = 1 ... for n in range(1,m): ... yield n**2 + s ... s += 1 ... >>> a = f_gen(5) >>> a <generator object f_gen at 0x0000023EE468D6D0> >>> for i in a: ... print(i) ... 2 6 12 20 >>><p>Здесь функция f_gen(5) при вызове создаёт генератор a. Мы видим это, когда выводим a на консоль.</p>
44
<p>Посчитаем значения генератора в цикле for.</p>
44
<p>Посчитаем значения генератора в цикле for.</p>
45
<ul><li>При первой итерации выполняется код функции до yield: переменная s =1, n = 1, yield возвращает 2.</li>
45
<ul><li>При первой итерации выполняется код функции до yield: переменная s =1, n = 1, yield возвращает 2.</li>
46
<li>При второй итерации выполняется оператор после yield, далее к началу цикла и опять до yield: s = 2, n = 2, yield возвращает 6.</li>
46
<li>При второй итерации выполняется оператор после yield, далее к началу цикла и опять до yield: s = 2, n = 2, yield возвращает 6.</li>
47
<li>Соответственно, при третьей и четвёртой итерации генерируются значения 12 и 20, после чего выполнение генератора прекращается.</li>
47
<li>Соответственно, при третьей и четвёртой итерации генерируются значения 12 и 20, после чего выполнение генератора прекращается.</li>
48
</ul><p>Как видим, значения переменных<em>n и s</em>между вызовами сохраняются.</p>
48
</ul><p>Как видим, значения переменных<em>n и s</em>между вызовами сохраняются.</p>
49
<p>Yield - инструмент очень гибкий. Его можно несколько раз использовать в коде функции-генератора. В этом случае команды yield служат разделителями кода: при первом вызове метода next() выполняется код до первого yield, при следующих вызовах - операторы между yield. При этом в генераторной функции необязательно должен быть цикл, все значения генератора и так посчитаются.</p>
49
<p>Yield - инструмент очень гибкий. Его можно несколько раз использовать в коде функции-генератора. В этом случае команды yield служат разделителями кода: при первом вызове метода next() выполняется код до первого yield, при следующих вызовах - операторы между yield. При этом в генераторной функции необязательно должен быть цикл, все значения генератора и так посчитаются.</p>
50
<p>Рассмотрим, как можно с помощью генератора создать математическую последовательность, например, программу, генерирующую простые числа (напоминаем, это числа, не имеющие делителей, кроме 1).</p>
50
<p>Рассмотрим, как можно с помощью генератора создать математическую последовательность, например, программу, генерирующую простые числа (напоминаем, это числа, не имеющие делителей, кроме 1).</p>
51
<p>Наша программа будет последовательно анализировать целые числа больше 1. Для каждого числа n программа ищет делители в диапазоне от 2 до √n. Если делители есть, программа переходит к следующему числу. Если их нет, значит, n - число простое, и программа выводит его на печать.</p>
51
<p>Наша программа будет последовательно анализировать целые числа больше 1. Для каждого числа n программа ищет делители в диапазоне от 2 до √n. Если делители есть, программа переходит к следующему числу. Если их нет, значит, n - число простое, и программа выводит его на печать.</p>
52
>>> import math >>> def prime_num(): ... nm = 2 ... while True: ... sq = math.ceil(nm**1/2) ... for i in range(2, sq+1): ... if (nm % i) == 0: ... break ... else: ... yield nm ... nm += 1 ... >>> for num in prime_num(): ... print(num) ... 2 3 5 7 11 13 17 19 23 29 31<p>Этот код выдаёт бесконечную последовательность простых чисел без ограничения сверху. Остановить его можно только вручную.</p>
52
>>> import math >>> def prime_num(): ... nm = 2 ... while True: ... sq = math.ceil(nm**1/2) ... for i in range(2, sq+1): ... if (nm % i) == 0: ... break ... else: ... yield nm ... nm += 1 ... >>> for num in prime_num(): ... print(num) ... 2 3 5 7 11 13 17 19 23 29 31<p>Этот код выдаёт бесконечную последовательность простых чисел без ограничения сверху. Остановить его можно только вручную.</p>
53
<p>Подобным образом с помощью генераторов можно создавать ряды случайных чисел, комбинаторные структуры, рекуррентные ряды, например, ряд Фибоначчи и другие последовательности.</p>
53
<p>Подобным образом с помощью генераторов можно создавать ряды случайных чисел, комбинаторные структуры, рекуррентные ряды, например, ряд Фибоначчи и другие последовательности.</p>
54
<p>Когда-то был один next (), но в <a>Python 2.5</a>появилось ещё три метода:</p>
54
<p>Когда-то был один next (), но в <a>Python 2.5</a>появилось ещё три метода:</p>
55
<ul><li>.close () - останавливает выполнение генератора;</li>
55
<ul><li>.close () - останавливает выполнение генератора;</li>
56
<li>.throw () - генератор бросает исключение;</li>
56
<li>.throw () - генератор бросает исключение;</li>
57
<li>.send () - интересный метод, позволяет отправлять значения генератору.</li>
57
<li>.send () - интересный метод, позволяет отправлять значения генератору.</li>
58
</ul><p>Рассмотрим пару небольших примеров.</p>
58
</ul><p>Рассмотрим пару небольших примеров.</p>
59
<p>Сначала на .close () и .throw ():</p>
59
<p>Сначала на .close () и .throw ():</p>
60
>>> def f_gen(): ... n = 1 ... while True: ... yield n**2 ... n += 1 ... >>> generator1 = f_gen() >>> generator2 = f_gen() >>> >>> for i in generator1: ... print(i) ... if i > 10: ... generator1.close() ... 1 4 9 16 >>> for i in generator2: ... print(i) ... if i > 20: ... generator2.throw(Exception("Плохо!")) ... 1 4 9 16 25 Traceback (most recent call last): File "<stdin>", line 4, in <module> File "<stdin>", line 4, in f_gen Exception: Плохо!<p>Программа создаёт два генератора, возвращающих бесконечную последовательность квадратов чисел. Их выполнение прекращается с помощью методов .close() и .throw().</p>
60
>>> def f_gen(): ... n = 1 ... while True: ... yield n**2 ... n += 1 ... >>> generator1 = f_gen() >>> generator2 = f_gen() >>> >>> for i in generator1: ... print(i) ... if i > 10: ... generator1.close() ... 1 4 9 16 >>> for i in generator2: ... print(i) ... if i > 20: ... generator2.throw(Exception("Плохо!")) ... 1 4 9 16 25 Traceback (most recent call last): File "<stdin>", line 4, in <module> File "<stdin>", line 4, in f_gen Exception: Плохо!<p>Программа создаёт два генератора, возвращающих бесконечную последовательность квадратов чисел. Их выполнение прекращается с помощью методов .close() и .throw().</p>
61
<p>Пример использования .send()</p>
61
<p>Пример использования .send()</p>
62
>>> def generator(x): ... while True: ... x = yield x + 1 ... >>> g = generator(5) >>> g.send(None) 6 >>> g.send(10) 11 >>> g.send(15) 16 >>> g.send(4) 5<p>Здесь мы не получаем значения генератора, а отправляем их на обработку с помощью метода .send().</p>
62
>>> def generator(x): ... while True: ... x = yield x + 1 ... >>> g = generator(5) >>> g.send(None) 6 >>> g.send(10) 11 >>> g.send(15) 16 >>> g.send(4) 5<p>Здесь мы не получаем значения генератора, а отправляем их на обработку с помощью метода .send().</p>
63
<p>С помощью этих методов можно создавать сопрограммы, или корутины, - это функции, которым можно передавать значения, приостанавливать и снова возобновлять их работу. Их обычно используют в Python для анализа потоков данных в корпоративной многозадачности. Генераторы позволяют создавать сложные разветвлённые программы для обработки потоков.</p>
63
<p>С помощью этих методов можно создавать сопрограммы, или корутины, - это функции, которым можно передавать значения, приостанавливать и снова возобновлять их работу. Их обычно используют в Python для анализа потоков данных в корпоративной многозадачности. Генераторы позволяют создавать сложные разветвлённые программы для обработки потоков.</p>
64
<p>С изучения генераторов начинается освоение последовательной обработки гигантских потоков данных. Это может быть, например, трейдинг и технический анализ в биржевых операциях.</p>
64
<p>С изучения генераторов начинается освоение последовательной обработки гигантских потоков данных. Это может быть, например, трейдинг и технический анализ в биржевых операциях.</p>
65
<p>Но даже если не говорить о глобальных задачах, скрипты с применением генераторов - это способ избежать копирования данных в память. Генераторы позволяют экономить ресурсы компьютера и создавать красивый чистый код.</p>
65
<p>Но даже если не говорить о глобальных задачах, скрипты с применением генераторов - это способ избежать копирования данных в память. Генераторы позволяют экономить ресурсы компьютера и создавать красивый чистый код.</p>
66
<p>Python для всех</p>
66
<p>Python для всех</p>
67
<p>Вы освоите Python на практике и создадите проекты для портфолио - телеграм-бот, веб-парсер и сайт с нуля. А ещё получите готовый план выхода на удалёнку и фриланс. Спикер - руководитель отдела разработки в "Сбере".</p>
67
<p>Вы освоите Python на практике и создадите проекты для портфолио - телеграм-бот, веб-парсер и сайт с нуля. А ещё получите готовый план выхода на удалёнку и фриланс. Спикер - руководитель отдела разработки в "Сбере".</p>
68
<p><a>Пройти бесплатно</a></p>
68
<p><a>Пройти бесплатно</a></p>
69
<a><b>Бесплатный курс по разработке на Python ➞</b>Пройдите бесплатный курс по Python и создайте с нуля телеграм-бот, веб-парсер и сайт. Спикер - руководитель отдела разработки в "Сбере". Пройти курс</a>
69
<a><b>Бесплатный курс по разработке на Python ➞</b>Пройдите бесплатный курс по Python и создайте с нуля телеграм-бот, веб-парсер и сайт. Спикер - руководитель отдела разработки в "Сбере". Пройти курс</a>