📜 ⬆️ ⬇️

Python: collections, part 4/4: All about expression-generators, list generators, sets and dictionaries

Part 1Part 2Part 3Part 4
image The final part of my cycle attended working with collections. This article is independent, can be studied without prior study of previous ones.

This article is deeper and more detailed than the previous ones and therefore may be interesting not only for beginners, but also for quite experienced Python developers .

image The following will be considered : generator expressions, list, dictionary and set generators, nested generators (5 variants), working with enumerate (), range ().
And also: classification and terminology, syntax, analogs in the form of cycles and examples of application.
')
I tried to consider the subtleties and nuances that are not covered in all the books and courses, and, among other things, are missing from the articles on this topic already published on Habrahabr.

Table of contents:


1. Definitions and classification.
2. Syntax.
3. Analogs in the form of a for loop and in the form of functions.
4. Expressions-generators.
5. Generation of standard collections.
6. Frequency and partial search.
7. Nested loops and generators.
8. Use range ().
9. Appendix 1. Additional examples.
10. Appendix 2. Related links.

1. Definitions and classification


1.1 What and why



1.2 Benefits of Using Expression Generators



1.3 Classification and features


At once I will say that there is some terminological confusion in the Russian names of what we are going to talk about.

This article uses the following notation:


image

In some places, to avoid cluttering up terms, the term “generator” will be used without further clarification.

2. Syntax


To begin with, we give an illustration of the general syntax of a generator expression.
Important : this syntax is the same for the generator expression and for all three types of collection generators, the difference is in which brackets it will be enclosed (see the previous illustration).
image

General principles important to understand:



2.1 Basic syntax


list_a = [-2, -1, 0, 1, 2, 3, 4, 5] #       list_b = [x for x in list_a] #       print(list_b) # [-2, -1, 0, 1, 2, 3, 4, 5] print(list_a is list_b) # False -   ! 

In fact, nothing interesting happened here, we just got a copy of the list. There is no special point in making such copies or simply distilling collections from type to type using generators - this can be done much easier by applying the appropriate methods or functions to create collections (discussed in the first article of the cycle).

The power of expression generators lies in the fact that we can set conditions for the inclusion of an element in a new collection and can do the transformation of the current element using an expression or function before its output (inclusion in the new collection).

2.2 Add filtering condition


Important : The condition is checked at each iteration, and only elements satisfying it are processed in the expression.

Add a condition to the previous example - take only even elements.

 # if x % 2 == 0 -     2   -   list_a = [-2, -1, 0, 1, 2, 3, 4, 5] list_b = [x for x in list_a if x % 2 == 0] print(list_b) # [-2, 0, 2, 4] 

We can use several conditions by combining them with logical operators :

 list_a = [-2, -1, 0, 1, 2, 3, 4, 5] list_b = [x for x in list_a if x % 2 == 0 and x > 0] #   x,       print(list_b) # [2, 4] 

2.3 Add item processing in expression


We can insert not the current element itself that passed the filter, but the result of evaluating an expression with it or the result of its processing by a function.

Important : The expression is executed independently at each iteration, processing each element individually.

For example, we can calculate the squares of the values ​​of each element:

 list_a = [-2, -1, 0, 1, 2, 3, 4, 5] list_b = [x**2 for x in list_a] print(list_b) # [4, 1, 0, 1, 4, 9, 16, 25] 

Or, calculate the lengths of the lines using the len () function.
 list_a = ['a', 'abc', 'abcde'] list_b = [len(x) for x in list_a] print(list_b) # [1, 3, 5] 

2.4 Expression branching


Note: We can use (starting with Python 2.5) in an expression an if-else construct to branch the final expression .

In this case:


 list_a = [-2, -1, 0, 1, 2, 3, 4, 5] list_b = [x if x < 0 else x**2 for x in list_a] #  x- -  x,    -   x print(list_b) # [-2, -1, 0, 1, 4, 9, 16, 25] 

Nobody forbids combining filtering and branching :

 list_a = [-2, -1, 0, 1, 2, 3, 4, 5] list_b = [x**3 if x < 0 else x**2 for x in list_a if x % 2 == 0] #         #          ,      print(list_b) # [-8, 0, 4, 16] 

The same example in the form of a loop
 list_a = [-2, -1, 0, 1, 2, 3, 4, 5] list_b = [] for x in list_a: if x % 2 == 0: if x < 0: list_b.append(x ** 3) else: list_b.append(x ** 2) print(list_b) # [-8, 0, 4, 16] 

2.5 Improving readability


Do not forget that in Python syntax allows the use of line breaks inside brackets. Using this feature, you can make the expression generator syntax easier to read:

 numbers = range(10) # Before squared_evens = [n ** 2 for n in numbers if n % 2 == 0] # After squared_evens = [ n ** 2 for n in numbers if n % 2 == 0 ] 

3. Analogs in the form of a for loop and in the form of functions


As mentioned above, the problems solved with the help of expression generators can be solved without them. We give other approaches that can be used to solve the same problems.

For example, let's take a simple task - let's make a list of squares of even numbers from the list of numbers and solve it using three different approaches:

3.1 Solution using a list generator


 numbers = range(10) squared_evens = [n ** 2 for n in numbers if n % 2 == 0] print(squared_evens) # [0, 4, 16, 36, 64] 

3.2. Solution using a for loop


Important : Each expression generator can be rewritten as a for loop, but not every for loop can be represented as such an expression.

 numbers = range(10) squared_evens = [] for n in numbers: if n % 2 == 0: squared_evens.append(n ** 2) print(squared_evens) # [0, 4, 16, 36, 64] 

In general, for very complex and complex tasks, the solution in the form of a cycle can be clearer and easier to maintain and refine. For simpler tasks, the syntax of a generator expression will be more compact and easier to read.

3.3. Solution using functions.


To begin with, I note that expression generators and collection generators are also a functional style, but more new and preferred.

Older functional approaches can be used to solve the same problems by combining map (), lambda and filter ().

 numbers = range(10) squared_evens = map(lambda n: n ** 2, filter(lambda n: n % 2 == 0, numbers)) print(squared_evens) # <map object at 0x7f661e5dba20> print(list(squared_evens)) # [0, 4, 16, 36, 64] # :  Python 2   squared_evens   ,   Python 3 «map object»,        list() 

Despite the fact that this example is quite working, it is hard to read and using the syntax of expression generators will be more preferable and understandable.

4. Generator expressions


Generator expressions (generator expressions) are available starting from Python 2.4. Their main difference from collection generators is that they give an element one by one, without loading the entire collection into memory at once .

UPD: Once again pay attention to this point: if we create a large data structure without using a generator, it is loaded into memory as a whole, respectively, this increases the memory consumption by your application, and in extreme cases, the memory may simply not be enough and your application will “fall” With MemoryError . In the case of the use of a generator expression, this does not occur, since the elements are created one by one, at the time of access.

An example of a generator expression:
 list_a = [-2, -1, 0, 1, 2, 3, 4, 5] my_gen = (i for i in list_a) # - print(next(my_gen)) # -2 -     print(next(my_gen)) # -1 -     

Features of Generator Expressions


  1. Generator cannot be written without brackets - this is a syntax error.
     # my_gen = i for i in list_a # SyntaxError: invalid syntax 

  2. When passing to the function, additional brackets are optional.
     list_a = [-2, -1, 0, 1, 2, 3, 4, 5] my_sum = sum(i for i in list_a) # my_sum = sum((i for i in list_a)) #    print(my_sum) # 12 

  3. Cannot get len () length
     # my_len = len(i for i in list_a) # TypeError: object of type 'generator' has no len() 

  4. Cannot print items with the print () function.
     print(my_gen) # <generator object <genexpr> at 0x7f162db32af0> 

  5. Please note that after passing through the expression-generator, it remains empty !
     list_a = [-2, -1, 0, 1, 2, 3, 4, 5] my_gen = (i for i in list_a) print(sum(my_gen)) # 12 print(sum(my_gen)) # 0 

  6. Generator expression can be infinite .
     import itertools inf_gen = (x for x in itertools.count()) #    0 to ! 
    Be careful in working with such generators, as if not properly used, the “effect” will be like from an infinite loop.

  7. The expression-generator does not apply cuts !
     list_a = [-2, -1, 0, 1, 2, 3, 4, 5] my_gen = (i for i in list_a) my_gen_sliced = my_gen[1:3] # TypeError: 'generator' object is not subscriptable 

  8. From the generator it is easy to get the desired collection . This is discussed in detail in the next chapter.

5. Generation of standard collections


5.1 Creating Collections from a Generator Expression


Creating collections from a generator expression using the functions list (), tuple (), set (), frozenset ()

Note : So you can create both a fixed set and a tuple, since they will become unchanged after generation.

Warning : For the string, this method does not work! The syntax for creating a dictionary generator in this way has its own characteristics; it is discussed in the next sub-section.

  1. By transferring the finished generator expression assigned to a variable to the collection creation function.

     list_a = [-2, -1, 0, 1, 2, 3, 4, 5] my_gen = (i for i in list_a) # - my_list = list(my_gen) print(my_list) # [-2, -1, 0, 1, 2, 3, 4, 5] 

  2. Writing a generator expression right inside the brackets of the called collection creation function.

     list_a = [-2, -1, 0, 1, 2, 3, 4, 5] my_list = list(i for i in list_a) print(my_list) # [-2, -1, 0, 1, 2, 3, 4, 5] 

    The same for a tuple, a set and an unchanged set
     #  my_tuple = tuple(i for i in list_a) print(my_tuple) # (-2, -1, 0, 1, 2, 3, 4, 5) #  my_set = set(i for i in list_a) print(my_set) # {0, 1, 2, 3, 4, 5, -1, -2} #   my_frozenset = frozenset(i for i in list_a) print(my_frozenset) # frozenset({0, 1, 2, 3, 4, 5, -1, -2}) 

5.2 Special syntax of collection generators


Unlike the generator expression, which gives the value one by one, without loading the entire collection into memory, using the collection generators, the collection is generated immediately.

Accordingly, instead of the peculiarities of the generator expressions listed above, such a collection will have all the standard properties characteristic of a collection of this type.

Note that the same brackets are used to generate the set and the dictionary, the difference is that the dictionary has the double key: value element.

  1. List comprehension

     list_a = [-2, -1, 0, 1, 2, 3, 4, 5] my_list = [i for i in list_a] print(my_list) # [-2, -1, 0, 1, 2, 3, 4, 5] 

    Do not write parentheses in square!

     list_a = [-2, -1, 0, 1, 2, 3, 4, 5] my_list = [(i for i in list_a)] print(my_list) # [<generator object <genexpr> at 0x7fb81103bf68>] 

  2. Set comprehension

     list_a = [-2, -1, 0, 1, 2, 3, 4, 5] my_set= {i for i in list_a} print(my_set) # {0, 1, 2, 3, 4, 5, -1, -2} -   

  3. Dictionary comprehension
    turning over the dictionary

     dict_abc = {'a': 1, 'b': 2, 'c': 3, 'd': 3} dict_123 = {v: k for k, v in dict_abc.items()} print(dict_123) # {1: 'a', 2: 'b', 3: 'd'} #  ,   ""!     , #     ,    . 

    Dictionary from the list:
     list_a = [-2, -1, 0, 1, 2, 3, 4, 5] dict_a = {x: x**2 for x in list_a} print(dict_a) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, -2: 4, -1: 1, 5: 25} 

    It is important ! Such syntax for creating a dictionary works only in curly brackets, so it is impossible to create a generator expression , for this a slightly different syntax is used (thanks to longclaps for a hint in the comments):
     # dict_gen = (x: x**2 for x in list_a) # SyntaxError: invalid syntax dict_gen = ((x, x ** 2) for x in list_a) #   -   # dict_a = dict(x: x**2 for x in list_a) # SyntaxError: invalid syntax dict_a = dict((x, x ** 2) for x in list_a) #     @longclaps 

5.3 String generation


To create a string instead of the syntax of generator expressions, use the string method. join (), to which an expression generator can be passed as an argument.
Please note : collection elements for a string combination must be strings!

 list_a = [-2, -1, 0, 1, 2, 3, 4, 5] #     .join()       my_str = ''.join(str(x) for x in list_a) print(my_str) # -2-1012345 

6. Frequency and partial search


6.1 Working with enumerate ()


Sometimes in the conditions of a problem in a filter condition, it is necessary not to check the value of the current element, but to check for a certain periodicity, that is, for example, every third element must be taken.

For such tasks, you can use the enumerate () function, which sets the counter when iterating around a loop:

 for i, x in enumerate(iterable) 
here x is the current element i is its sequence number, starting with zero

Let's illustrate work with indexes:

 list_a = [-2, -1, 0, 1, 2, 3, 4, 5] list_d = [(i, x) for i, x in enumerate(list_a)] print(list_d) # [(0, -2), (1, -1), (2, 0), (3, 1), (4, 2), (5, 3), (6, 4), (7, 5)] 

Now we will try to solve the real problem - we will select every third element from the initial list in the list generator:

 list_a = [-2, -1, 0, 1, 2, 3, 4, 5] list_e = [x for i, x in enumerate(list_a, 1) if i % 3 == 0] print(list_e) # [0, 3] 

Important features of the function enumerate ():


  1. There are two options for calling the enumerate () function:
    • enumerate (iterator) without the second parameter counts from 0.
    • enumerate (iterator, start) - starts counting from the start value. Conveniently, for example, if we need to count from 1, not 0.

  2. enumerate () returns a tuple of the sequence number and value of the current iterator element. The tuple in the expression-generator result can be obtained in two ways:
    • (i, j) for i, j in enumerate (iterator) - brackets in the first pair are needed!
    • pair for pair in enumerate (mylist) - we work immediately with a pair

  3. Indices are calculated for all processed items , without taking into account they have passed the condition in the future or not!

     first_ten_even = [(i, x) for i, x in enumerate(range(10)) if x % 2 == 0] print(first_ten_even) # [(0, 0), (2, 2), (4, 4), (6, 6), (8, 8)] 

  4. The enumerate () function does not access any internal attributes of the collection, but simply implements a counter of processed elements , so nothing prevents it from being used for unordered collections that are not indexed.

  5. If we limit the number of elements included in the result by the enumerate () counter (for example, if i <10), then the iterator will still be processed entirely , which in the case of a huge collection will be very resource-consuming. The solution to this problem is discussed below in the sub-section “Looping a Part of the Iterated”.

6.2 Enumeration of the part to be iterated.


Sometimes there is a task from a very large collection or even an infinite generator to get a sample of the first few elements that satisfy the condition.

If we use the usual generator expression with the condition of restricting the enumerate () index or a slice of the resulting collection, then in any case we will have to go through the entire huge collection and spend a lot of computer resources on it.

The solution is to use the islice () function from the itertools package.

 import itertools first_ten = (itertools.islice((x for x in range(1000000000) if x % 2 == 0), 10)) print(list(first_ten)) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] 

For doubters: check lead time
 import time import itertools #       start_time = time.time() first_ten = (itertools.islice((x for x in range(100) if x % 2 == 0), 10)) print(list(first_ten)) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] elapsed_time = time.time() - start_time print(elapsed_time) # 3.409385681152344e-05 #       start_time = time.time() first_ten = (itertools.islice((x for x in range(100000000) if x % 2 == 0), 10)) print(list(first_ten)) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] elapsed_time = time.time() - start_time print(elapsed_time) # 1.1205673217773438e-05 #        range()    6 , #        

7. Nested loops and generators


Consider more complex options when we have cycles or the generator expressions themselves are nested. There are several possible options, with their own features and scope of application, so that there is no confusion, we will consider them separately, and then we will give a general scheme.

7.1 Nested loops


As a result of generation, we obtain a one-dimensional structure .

It is important ! When working with nested loops inside an expression generator, the order of following for in instructions will be the same (from left to right) as in a similar solution without a generator, only in cycles (top-down)! The same is true for deeper nesting levels.

7.1.1 Nested loops for where loops are iterated through independent iterators


General syntax: [expression for x in iter1 for y in iter2]
Application : we generate one-dimensional structure using data from two iterators.

For example, create a dictionary using coordinate tuples as keys, filling in its value with zeros for the beginning.

 rows = 1, 2, 3 cols = 'a', 'b' my_dict = {(col, row): 0 for row in rows for col in cols} print(my_dict) # {('a', 1): 0, ('b', 2): 0, ('b', 3): 0, ('b', 1): 0, ('a', 3): 0, ('a', 2): 0} 

Then we can set new values ​​or get them
 my_dict['b', 2] = 10 #     - print(my_dict['b', 2]) # 10 -     - 

The same can be done with additional filter conditions in each cycle:

 rows = 1, 2, 3, -4, -5 cols = 'a', 'b', 'abc' #       my_dict = { (col, row): 0 #     -    for row in rows if row > 0 #    for col in cols if len(col) == 1 #   } print(my_dict) # {('a', 1): 0, ('b', 2): 0, ('b', 3): 0, ('b', 1): 0, ('a', 3): 0, ('a', 2): 0} 

The same problem solved with the help of a cycle
 rows = 1, 2, 3, -4, -5 cols = 'a', 'b', 'abc' my_dict = {} for row in rows: if row > 0: for col in cols: if len(col) == 1: my_dict[col, row] = 0 print(my_dict) # {('a', 1): 0, ('b', 2): 0, ('b', 3): 0, ('b', 1): 0, ('a', 3): 0, ('a', 2): 0} 

7.1.2 Nested loops for where the inner loop follows the result of the outer loop


The general syntax is [expression for x in iterator for y in x] .

Application : The standard approach is when we need to bypass a two-dimensional data structure, turning it into a "flat" one-dimensional. In this case, we go through the rows in the outer loop, and the elements of each row of our two-dimensional structure in the inner loop.

Suppose we have a two-dimensional matrix - a list of lists. And we want to convert it into a flat one-dimensional list.

 matrix = [[0, 1, 2, 3], [10, 11, 12, 13], [20, 21, 22, 23]] #     : flattened = [n for row in matrix for n in row] print(flattened) # [0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23] 

Same problem solved with nested loops
 flattened = [] for row in matrix: for n in row: flattened.append(n) print(flattened) 

UPD: Elegant decisions from comments
 import itertools flattened = list(itertools.chain.from_iterable(matrix)) #  @iMrDron #      #       . flattened = sum(a, []) #  @YuriM1983 # sum(a, [])   (O(n^2)) #           

7.2 Embedded Generators


Nested can be not only for loops inside a generator expression, but also the generators themselves.
This approach is used when we need to build a two-dimensional structure .

Important !: Unlike the examples above with nested loops, for nested generators, the external generator is processed first, then the internal one, that is, the order goes from right to left.

Below we consider two options for similar use.

7.2.1 - Nested generator inside the generator - two-dimensional of two one-dimensional


The general syntax is: [[expression for y in iter2] for x in iter1]
Application : we generate a two-dimensional structure using data from two one-dimensional iterators.

For example, create a matrix of 5 columns and 3 rows and fill it with zeros:

 w, h = 5, 3 #      matrix = [[0 for x in range(w)] for y in range(h)] print(matrix) # [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] 

Creating the same matrix with two nested loops - note the order of nesting
 matrix = [] for y in range(h): new_row = [] for x in range(w): new_row.append(0) matrix.append(new_row) print(matrix) # [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] 

Note: After creation, we can work with the matrix as with a normal two-dimensional array.
 #       ( -    ) matrix[0][0] = 1 matrix[1][3] = 3 print(matrix) # [[1, 0, 0, 0, 0], [0, 0, 0, 3, 0], [0, 0, 0, 0, 0]] #      x, y = 1, 3 print(matrix[x][y]) # 3 

7.2.2 - Embedded generator inside the generator - two-dimensional of the two-dimensional


The general syntax is: [[expression for y in x] for x in iterator]
Application : We go around the two-dimensional data structure, saving the result to another two-dimensional structure.

Take the matrix:

 matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] 

Let's put each element of the matrix into a square:

 squared = [[cell**2 for cell in row] for row in matrix] print(squared) # [[1, 4, 9, 16], [25, 36, 49, 64], [81, 100, 121, 144]] 

The same operation as nested loops.
 squared = [] for row in matrix: new_row = [] for cell in row: new_row.append(cell**2) squared.append(new_row) print(squared) # [[1, 4, 9, 16], [25, 36, 49, 64], [81, 100, 121, 144]] 

Summarize all of the above options in one scheme (full size on click):



7.3 - Generator iterating over generator


Since any generator can be used as an iterator in a for loop, it can also be used to create a generator using a generator.
In this case, syntactically, this can be written in two expressions or combined into a nested generator.

I will illustrate this possibility.
Suppose we have two such list generators:
 list_a = [x for x in range(-2, 4)] #      , #       range(-2, 4) list_b = [x**2 for x in list_a] 

The same can be written in one expression, substituting its list generator instead of list_a:
 list_c = [x**2 for x in [x for x in range(-2, 4)]] print(list_c) # [4, 1, 0, 1, 4, 9] 

UPD from longclaps : The advantage of combining generators on the example of a complex function f (x) = u (v (x))
 list_c = [t + t ** 2 for t in (x ** 3 + x ** 4 for x in range(-2, 4))] 

8. Using range ()


Speaking about how to generate collections, you can not ignore the simple and very convenient range () function, which is designed to create arithmetic sequences.

Features of the range () function:



Examples of using:

 print(list(range(5))) # [0, 1, 2, 3, 4] print(list(range(-2, 5))) # [-2, -1, 0, 1, 2, 3, 4] print(list(range(5, -2, -2))) # [5, 3, 1, -1] 

9. Appendix 1. Additional examples


9.1 Sequential walk through multiple lists


 import itertools l1 = [1,2,3] l2 = [10,20,30] result = [l*2 for l in itertools.chain(l1, l2)] print(result) # [2, 4, 6, 20, 40, 60] 

9.2 Matrix Transposition

(Matrix transformation when rows are reversed with columns).

Take the matrix.

 matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] 

Transpose it using an expression generator:

 transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))] print(transposed) # [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]] 

The same matrix transposition as a cycle
 transposed = [] for i in range(len(matrix[0])): new_row = [] for row in matrix: new_row.append(row[i]) transposed.append(new_row) print(transposed) # [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]] 

And some black magic from @longclaps
 transposed = list(map(list, zip(*matrix))) print(transposed) # [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]] 

9.3 The task of choosing only working days


 #     1  31     days = [d for d in range(1, 32)] #      weeks = [days[i:i+7] for i in range(0, len(days), 7)] print(weeks) # [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20, 21], [22, 23, 24, 25, 26, 27, 28], [29, 30, 31]] #       5  ,   work_weeks = [week[0:5] for week in weeks] print(work_weeks) # [[1, 2, 3, 4, 5], [8, 9, 10, 11, 12], [15, 16, 17, 18, 19], [22, 23, 24, 25, 26], [29, 30, 31]] #      -   wdays = [item for sublist in work_weeks for item in sublist] print(wdays) # [1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 15, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 30, 31] 

You can remove the weekend even more gracefully using only indexes.
 #     1  31     days = [d for d in range(1, 32)] wdays6 = [wd for (i, wd) in enumerate(days, 1) if i % 7 != 0] #   7-  #   6      : wdays5 = [wd for (i, wd) in enumerate(wdays6, 1) if i % 6 != 0] print(wdays5) # [1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 15, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 30, 31] #  ,        if  , #   ,  12-    6,      2  ! #     @sophist: days = [d + 1 for d in range(31) if d % 7 < 5] 

10. Appendix 2. Related links


  1. Good English article with a detailed explanation of what generators and iterators are

    Illustration from the article:
    image

  2. If you have difficulty understanding the logic of working with generator expressions, take a look at an interesting English-language article where the analogies between generator expressions and working with SQL and Excel tables are drawn .

    For example:
     squared_evens = [n ** 2 # SELECT for n in numbers # FROM if n % 2 == 0] # WHERE 

  3. UPD from fireSparrow : There is an extension Python - PythonQL, which allows you to work with databases in the style of collection generators.

  4. , .

  5. ( ).

Part 1Part 2Part 3Part 4

:


Source: https://habr.com/ru/post/320288/


All Articles