Генераторы python. Генераторы

Представим себе ситуацию - вам необходимо извлечь все элементы списка и присвоить каждый из них своей определенной переменной. Например, у нас есть список описывающий человека и содержащий следующие элементы:

Person_data = ["John", "Smith", 23, "programmer"]

User_name, user_surname, user_age, user_occupation = person_data

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

Обратите внимание, что количество создаваемых переменных должно соответствовать количеству элементов в списке, иначе вы получите ошибку.

Как пользоваться генераторами в Python.

Генераторами списков в Python называются однострочные конструкции, которые позволяют создавать новые списки.

Синтаксис генераторов списков такой:

# самый простой генератор new_list =

В итоге new_list будет содержать числа от 0 до 9. Понятно, что для того чтобы создать такой список незачем пользоваться генератором. Достаточно просто воспользоваться функцией range()

# пример посложнее word = "hello" new_list =

Теперь new_list будет выглядеть так:

["h","e","l","l","o"]

Так же в генераторы списков можно вставлять конструкцию if:

# Создаем список из чисел кратных трем new_list =

В итоге получим:

Наконец, в генераторе можно использовать несколько списков и переменных:

# создание колоды карт при помощи генератора списков # масти suits = "HDCS" # ранги ranks = "23456789TJQKA" # генерируем колоду deck =

Получим следующий результат:

Представьте, сколько времени мы сэкономили, написав всего одну строчку вместо нескольких циклов .

Как извлечь элементы из подсписков в Python.

Так же, часто возникает ситуация, когда необходимо извлечь элементы из подспиков списка.

Например имеем такой список:

Outer = [, , ]

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

New_list =

Таким образом получаем список со всеми элементами:

На этом на сегодня все. Удачи в освоении Python !

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

Допустим, нам необходимо получить список нечетных чисел, не превышающих 25. В принципе, только познакомившись с работой команды xrange решить эту проблему несложно.

Res = for x in xrange(1, 25, 2): res.append(x) ... print res

В общем-то, полученный результат - целиком нас устраивает всем, кроме длинной записи. Тут-то на помощь и придет наш «сахарок». В самом простом виде, он обычно

Res = ... print res

Синтаксис, в принципе прост. Все выражение записывается в квадратных скобках. Сначала идет выражение, которое будет задавать элементы списка, потом - цикл с помощью которого можно изменять выражение. Обе части могут быть сколь угодно сложными. Например, вот как можно получить список квадратов тех же нечетных чисел.

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

Res = ... print res

Задание 6.1. Генератор списка

Нужно получить список двоичных чисел не являющихся квадратами.

Генераторы

Конструкции типа mylist = формируют списки элементов, значения которых хранятся в памяти. К ним можно применять конструкцию for i in mylist: print(i) для работы с элементами сколько угодно раз.

Генераторы это тоже итерируемые объекты, но прочитать их можно лишь один раз. Это связано с тем, что они не хранят значения в памяти, а генерируют их на лету:

Mygenerator = (x*x for x in range(3)) for i in mygenerator: print(i)

Всё то же самое, разве что используются круглые скобки вместо квадратных. НО: нельзя применить конструкцию for i in mygenerator второй раз, так как генератор может быть использован только единожды: он вычисляет 0, потом забывает про него и вычисляет 1, завершая вычислением 4 - одно за другим. Также нельзя получить число элементов функцией len() . К генераторам нельзя применить срезы mygenerator . Но, генераторы позволяют сократить объем памяти для запуска программы.

Yield

Yield это ключевое слово, которое используется примерно как return - отличие в том, что функция вернёт генератор.

Def createGenerator() : mylist = range(3) for i in mylist: yield i*i mygenerator = createGenerator() # создаём генератор for i in mygenerator: print(i)

Задание 6.2. Генератор тетраэдрических чисел

При помощи генератора треугольных чисел создать генератор тетраэдрических чисел.

Задание 6.3. Генератор переливаний

Имеется школьная задача о получении нужного объема при помощи бесконечного бассейна и двух ведер. Например: нужно получить 4 литра при помощи двух ведер емкостью 3 и 5 литров. Имеется ее решение методом бильярдного шара .

Необходимо создать генератор, выдающий пары чисел - наполненности сосудов. Пример работы:

Buckets = pool(3,5) for a,b in buckets: print("a=",a," b=",b) if b==4: break

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

>>> lst =
>>> lst

>>>

>>>

Посмотрим на код более внимательно. ВНачале был создан список чисел "1,2,3,4,5", после чего следует форма записи генератора. Запись генератора списков береться в квадратные скобки. Вначале указывается действие для переменной, куда цикл помещает элементы на каждом шаге итерации, зачем следует классическая форма записи объявления цикла "for", куда передается созданный список чисел. Генератор списка возвращает список чисел, которые были сгенерированы в процессе указанного действия внутри генератора, а именно, взять элемент на каждом шаге итерации и умножить его на 2. Врезультате чего, получаем список чисел из списка "lst", Где каждый элемент умножен на 2. При использовании классического цикла "for", эта запись выглядит следующим образом:

>>> lst =
>>> lst

>>> lst2 =
>>> for i in lst:
... lst2.append(i*2)
...
>>> lst2

>>>

Согласитесь, запись в виде генератора списка, была куда более краткой. На первый взгляд, приемущества генератора списков очевидны далеко не сразу, но давайте смоделируем несколько ситуаций, где мы можем применить генератор списка, который позволит сократить количество написанного кода, а главное, позволит сократить код не в ущерб производительности и сопровождению. Кпримеру, у нас есть файл, с которого нам необходимо достать список строк. Файл находится в кодировке "utf-8" и в процессе доставания, нам необходимо декодировать строки в юникод, параллельно убрав эскейп последовательность "\n", в конце каждой строки.

>>> text = for s in open(ur"d:\dev\price.txt")]
>>> print text

>>>

Полюбуйтесь на этот код! Мы выполнили поставленную задачу в одну строку кода. Впринципе, код достаточно очевидный. Мы обратились к файлу непосредственно в цикле "for", что передало циклу объект-итератор файла. Далее цикл пробежался по строкам, и выполнил действие, которое мы указали ему вначале нашего генератора, Т.Е. Перевел каждую строку в юникод и вернул ее со срезом [:-1], что позволило удалить эскейп последовательность "\n". Собственно получившийся список, мы присвоили переменной "text", которая и содержит список обработанных строк:

>>> for i in text:
... print i
...
Прайс на чебурашек, чубурашек и чибураторов.




Чубурашка, цвет синий, характер мерзкий, цена 200 грн;
Чубурашка, цвет голубой, характер доставучий, цена 250 грн;
Чубурашка, цвет малиновый, характер клубничный, цена 300 грн;
Чибуратор, цвет хаки, характер нордический, цена 400 грн;
Чибуратор, цвет мутный, характер мутный, цена 400 грн;
Чибуратор, цвет шоколадный, характер мармеладный, цена 500 грн;
Чибуратор, цвет полосатый, характер сонный, цена 600 грн;
>>>

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

Генератор списков с условием "if"

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

>>> lst =
>>> lst

>>>

>>>

Мы расширили синтаксис генератора списков условием "if", которое имеет выражение ">= 5". Генератор вернет список только тех значений, которые будут обрабатываться на шаге итерации, при котором "if" будет получать "True". Соответственно, нам вернется список чисел, которые соответствуют утверждению ">= 5".
Мы можем еще более расширить генератор списка, воспользовавшись тернарной записью "if else":

>>> 1 if 3>2 else 0
1
>>> 1 if 3 < 2 else 0
0
>>>

Напомню, что тернарная запись "if else", возвращает результат слева, если выражение после "if", возвращает "True". В противном случае, возвращается результат справа. Теперь посмотрим, как мы можем соединить это с генератором списка, решив предыдущую задачу, но поставив условие, что, как только нам встречается число в списке меньше пяти, мы возвращаем его, но прибавив к нему +5:

>>> lst

>>>

>>>

Давайте решим еще одну, менее синтетическую задачу, на основании этого синтаксиса. Возьмем нашь файлик "price.txt" и вернем в список только те строки, которые начинаются со слова "Чебурашка", а для остальных результатов, вернуть "None":

>>> text = for string in open(ur"d:\dev\price.txt")]
>>> for i in text:
... print i
...
None
Чебурашка, цвет зеленый, характер унылый, цена 100 грн;
Чебурашка, цвет красный, характер агрессивный, цена 150 грн;
Чебурашка, цвет коричневый, характер чебурашечный, цена 150 грн;
Чебурашка, цвет белый, характер настырный, цена 200 грн;
None
None
None
None
None
None
None
>>>

(Строчный метод "startswith", возвращает "True", если строка начинается с переданного в метод префикса)

Генератор словарей

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

>>> lst

>>> {i:i*2 for i in lst}
{1: 2, 2: 4, 3: 6, 4: 8, 5: 10}
>>>

Все выражение генератора мы заключаем в фигурные скобочки, а в начале, в виде действия, указываем синтаксис записи пары "ключ/значение" для словаря.

Генератор множеств

Тройку встроенных генераторов Python, заканчивает генератор множеств, суть которого остается такой же, как и у предыдущих генераторов. Его базовый синтаксис выглядит следующим образом:

>>> lst =
>>> lst

>>> {i*2 for i in lst}
set()
>>>

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

В языке программирования Python существует специальная синтаксическая конструкция, которая позволяет по определенным правилам создавать заполненные списки. Такие конструкции называются генераторами списков . Их удобство заключается в более короткой записи программного кода, чем если бы создавался список обычным способом.

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

>>> a = >>> for i in range (1 , 15 ) : ... a.append (i) ... >>> a

На создание списка ушло три строчки кода. Генератор же сделает это за одну:

>>> a = [ i for i in range (1 , 15 ) ] >>> a

Здесь конструкция [ i for i in range (1 , 15 ) ] является генератором списка. Вся конструкция заключается в квадратные скобки, что как бы говорит, что будет создан список. Внутри квадратных скобок можно выделить три части: 1) что делаем с элементом (в данном случае ничего не делаем, просто добавляем в список), 2) что берем (в данном случае элемент i), 3) откуда берем (здесь из объекта range). Части отделены друг от друга ключевыми словами for и in .

Рассмотрим такой пример:

>>> a = [ 2 , -2 , 4 , -4 , 7 , 5 ] >>> b = [ i**2 for i in a] >>> b

В данном случае в генераторе списка берется каждый элемент из списка a и возводится в квадрат. Таким образом, 1) что делаем - возводим элемент в квадрат, 2) что берем - элемент, 3) откуда берем - из списка a .

>>> >>> b = [ i*a[ i] for i in a] >>> b

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

>>> a = { 1 :10 , 2 :20 , 3 :30 } >>> b = [ [ i, a[ i] ] for i in a] >>> b [, , ] >>> c = [ j for i in b for j in i] >>> c

В этом примере генерируемый список b состоит из вложенных списков. Если бы в генераторе были опущены квадратные скобки в выражении [ i, a[ i] ] , то произошла бы ошибка. Если все же надо получить одноуровневый список из ключей и значений словаря, надо взять каждый вложенный список и из него взять каждый элемент. Это достигается за счет вложенной конструкции for , что демонстрируется в строчке c = [ j for i in b for j in i] . "Классический" синтаксис для заполнения списка c выглядел бы так:

>>> c = >>> for i in b: ... for j in i: ... c.append (j) ... >>> c

В конец генератора можно добавлять конструкцию if . Например, надо из строки извлечь все цифры:) if i%30 == 0 or i%31 == 0 ] >>> a

Таким образом, генераторы позволяют создавать списки легче и быстрее. Однако заменить ими достаточно сложные конструкции не получится. Например, когда условие проверки должно включать ветку else .

Do you know the difference between the following syntax?


(x for x in range(5))
tuple(range(5))

Let’s check it

4 Facts About the Lists

First off, a short review on the lists (arrays in other languages).

  • list is a type of data that can be represented as a collection of elements. Simple list looks like this –
  • lists take all possible types of data and combinations of data as their components:
>>> a = 12 >>> b = "this is text" >>> my_list = , (1, 2, 3), a] >>> print(my_list) , (1, 2, 3), 12]
  • lists can be indexed. You can get access to any individual element or group of elements using the following syntax:
& >>> a = ["red", "green", "blue"] >>> print(a) red
  • lists are mutable in Python. This means you can replace, add or remove elements.

What is List Comprehension?

Often seen as a part of functional programming in Python, list comprehensions allow you to create lists with a for loop with less code.

Let’s look at the following example.

You create a list using a for loop and a range() function.

& >>> my_list = >>> for x in range(10): ... my_list.append(x * 2) ... >>> print(my_list)

And this is how the implementation of the previous example is performed using a list comprehension:

>>> comp_list = >>> print(comp_list)

The above example is oversimplified to get the idea of syntax. The same result may be achieved simply using list(range(0, 19, 2)) function. However, you can use a more complex modifier in the first part of comprehension or add a condition that will filter the list. Something like this:

>>> comp_list = >>> print(comp_list)

Another available option is to use list comprehension to combine several lists and create a list of lists. At first glance, the syntax seems to be complicated. It may help to think of lists as an outer and inner sequences.

It’s time to show the power of list comprehensions when you want to create a list of lists by combining two existing lists.

>>> nums = >>> letters = ["A", "B", "C", "D", "E"] >>> nums_letters = [ for n in nums for l in letters] #the comprehensions list combines two simple lists in a complex list of lists. >>> print(nums_letters) >>> print(nums_letters) [, , , , , , , , , , , , , , , , , , , , , , , , ] >>>

Let’s try it with text or it’s correct to say string object.

>>> iter_string = "some text" >>> comp_list = >>> print(comp_list) ["s", "o", "m", "e", "t", "e", "x", "t"]

The comprehensions are not limited to lists. You can create dicts and sets comprehensions as well.

>>> dict_comp = {x:chr(65+x) for x in range(1, 11)} >>> type(dict_comp) >>> print(dict_comp) {1: "B", 2: "C", 3: "D", 4: "E", 5: "F", 6: "G", 7: "H", 8: "I", 9: "J", 10: "K"} >>> set_comp = {x ** 3 for x in range(10) if x % 2 == 0} >>> type(set_comp) >>> print(set_comp) {0, 8, 64, 512, 216}

Difference Between Iterable and Iterator

It will be easier to understand the concept of generators if you get the idea of iterables and iterators.

Iterable is a “sequence” of data, you can iterate over using a loop. The easiest visible example of iterable can be a list of integers – . However, it’s possible to iterate over other types of data like strings, dicts, tuples, sets, etc.

Basically, any object that has iter () method can be used as an iterable. You can check it using hasattr() function in the interpreter.

>>> hasattr(str, "__iter__") True >>> hasattr(bool, "__iter__") False

Iterator protocol is implemented whenever you iterate over a sequence of data. For example, when you use a for loop the following is happening on a background:

  • first iter () method is called on the object to converts it to an iterator object.
  • next () method is called on the iterator object to get the next element of the sequence.
  • StopIteration exception is raised when there are no elements left to call.
>>> simple_list = >>> my_iterator = iter(simple_list) >>> print(my_iterator) >>> next(my_iterator) 1 >>> next(my_iterator) 2 >>> next(my_iterator) 3 >>> next(my_iterator) Traceback (most recent call last): File "", line 1, in StopIteration

Generator Expressions

In Python, generators provide a convenient way to implement the iterator protocol. Generator is an iterable created using a function with a yield statement.

The main feature of generator is evaluating the elements on demand. When you call a normal function with a return statement the function is terminated whenever it encounters a return statement. In a function with a yield statement the state of the function is “saved” from the last call and can be picked up the next time you call a generator function.

>>> def my_gen(): ... for x in range(5): ... yield x

Generator expression allows creating a generator on a fly without a yield keyword. However, it doesn’t share the whole power of generator created with a yield function. The syntax and concept is similar to list comprehensions:

>>> gen_exp = (x ** 2 for x in range(10) if x % 2 == 0) >>> for x in gen_exp: ... print(x) 0 4 16 36 64

In terms of syntax, the only difference is that you use parentheses instead of square brackets. However, the type of data returned by list comprehensions and generator expressions differs.

>>> list_comp = >>> gen_exp = (x ** 2 for x in range(10) if x % 2 == 0) >>> print(list_comp) >>> print(gen_exp) at 0x7f600131c410>

The main advantage of generator over a list is that it takes much less memory. We can check how much memory is taken by both types using sys.getsizeof() method.

Note: in Python 2 using range() function can’t actually reflect the advantage in term of size, as it still keeps the whole list of elements in memory. In Python 3, however, this example is viable as the range() returns a range object.

>>> from sys import getsizeof >>> my_comp = >>> my_gen = (x * 5 for x in range(1000)) >>> getsizeof(my_comp) 9024 >>> getsizeof(my_gen) 88

We can see this difference because while `list` creating Python reserves memory for the whole list and calculates it on the spot. In case of generator, we receive only ”algorithm”/ “instructions” how to calculate that Python stores. And each time we call for generator, it will only “generate” the next element of the sequence on demand according to “instructions”.

On the other hand, generator will be slower, as every time the element of sequence is calculated and yielded, function context/state has to be saved to be picked up next time for generating next value. That “saving and loading function context/state” takes time.

Final Thoughts

The very first thing that might scare or discourage a newbie programmer is the scale of educational material. The trick here is to treat each concept as an option offered by language, you’re not expected to learn all the language concepts and modules all at once. There are always different ways to solve the same task. Take it as one more tool to get the job done.