Generators, iterators, lambdas and closures;

A Python generator is a piece of specialized code able to produce a series of values, and to control the iteration process. This is why generators are very often called iterators, and although some may find a very subtle distinction between these two, we’ll treat them as one.

1. An iterator is an object of a class providing at least two methods (not counting the constructor):

    • __iter__() is invoked once when the iterator is created and returns the iterator’s object itself;
    • __next__() is invoked to provide the next iteration’s value and raises the StopIteration exception when the iteration comes to an end.

Simple generator example:

The iterator protocol is a way in which an object should behave to conform to the rules imposed by the context of the for and in statements. An object conforming to the iterator protocol is called an iterator.

The Fibonacci numbers (Fibi) are defined as follows:

Fib1 = 1
Fib2 = 1
Fibi = Fibi-1 + Fibi-2

In other words:

  • the first two Fibonacci numbers are equal to 1;
  • any other Fibonacci number is the sum of the two previous ones (e.g., Fib3 = 2, Fib4 = 3, Fib5 = 5, and so on)

 

 

2. The yield statement can be used only inside functions. The yield statement suspends function execution and causes the function to return the yield’s argument as a result. Such a function cannot be invoked in a regular way – its only purpose is to be used as a generator (i.e. in a context that requires a series of values, like a for loop.)

Take a look at this function:

It’s clear that the for loop has no chance to finish its first execution, as the return will break it irrevocably.

Moreover, invoking the function won’t change anything – the for loop will start from scratch and will be broken immediately.

We can say that such a function is not able to save and restore its state between subsequent invocations.

This also means that a function like this cannot be used as a generator.

Let’s replace exactly one word in the code:

There is one important limitation: such a function should not be invoked explicitly as – in fact – it isn’t a function anymore; it’s a generator object.

The invocation will return the object’s identifier, not the series we expect from the generator.

This is how we can use it:

0
1
2
3
4

1
2
4
8
16
32
64
128

Example.

s= ++
s= ++++

++++++

 

3. A conditional expression is an expression built using the if-else operator. For example:


outputs True.

 

4. A list comprehension becomes a generator when used inside parentheses (used inside brackets, it produces a regular list). For example:

outputs 02468.

or

[1, 2, 4, 8, 16]

 

More examples list comprehensions:

[1, 10, 100, 1000, 10000, 100000]

[1, 10, 100, 1000, 10000, 100000]

 

[1, 0, 1, 0, 1, 0, 1, 0, 1, 0]

 

5. List comprehensions vs. generators

Just one change can turn any list comprehension into a generator. It’s the parentheses. The brackets make a comprehension, the parentheses make a generator.

1 0 1 0 1 0 1 0 1 0

1 0 1 0 1 0 1 0 1 0

10

Traceback (most recent call last):

File "main.py", line 15, in <module>

print (len(the_generator))

TypeError: object of type 'generator' has no len()

You can’t use the len() function on the generator.

 

6. The list() function can transform a series of subsequent generator invocations into a real list:

[1, 2, 4]

 

7. The in operator allows you to use a generator, too.

1
2
4
8

 

8. Fibonacci number generator looks much better than the objective version based on the direct iterator protocol implementation.

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

 

9. A lambda is a function without a name (you can also call it an anonymous function). The declaration of the lambda function doesn’t resemble a normal function declaration in any way:

Example 1.


outputs 3.0.

Example 2.

4 4
1 1
0 0
1 1
4 4

Example 3.

f(-2)=18

f(-1)=8

f(0)=2

f(1)=0

f(2)=2

 

PEP 8, the Style Guide for Python Code, recommends that lambdas should not be assigned to variables, but rather they should be defined as functions.

This means that it is better to use a def statement, and avoid using an assignment statement that binds a lambda expression to an identifer. Analyze the code below:

 

 

10. The map(fun, list) function creates a copy of a list argument, and applies the fun function to all of its elements, returning a generator that provides the new list content element by element.

 

Example 1.


outputs

['Mython', 'Python', 'Fell', 'On', 'The', 'Floor'].

 

Example 2.

[0, 1, 2, 3, 4]

[1, 2, 4, 8, 16]

1 4 16 64 256

 

 

11. The filter(fun, list) function creates a copy of those list elements, which cause the fun function to return True. The function’s result is a generator providing the new list content element by element.

Example 1.

The isinstance() function returns True if the specified object is of the specified type, otherwise False.


outputs

['Python', 'Monty'].

 

Example 2.

[1, 8, 2, 7, -2]

[8, 2]

 

12. A closure is a technique which allows the storing of values in spite of the fact that the context in which they have been created does not exist anymore.

Example 1:

1

Example 2:

Note:

    • the first closure obtained from make_closure() defines a tool squaring its argument (fsqr);
    • the second one is designed to cube the argument (fcub).

This is why the code produces the following output:

0 0 0

1 1 1

2 4 8

3 9 27

4 16 64

 

Example 3:

outputs

<b>Monty Python</b>

 

Exercise 1

What is the expected output of the following code?

a e i o u y

 

Exercise 2

Write a lambda function, setting the least significant bit of its integer argument, and apply it to the map() function to produce the string 1 3 3 5 on the console.

Lambda:

 

Exercise 3

What is the expected output of the following code?

And*Now*for*Something*Completely*Different