Lecture 22 Advanced Python Topics II

We'll have more examples about Iterators, Generators, and Decorators for Python:

1. Iterators:

As we learned in last lecture, the data has to be converted into a iterator to work with the 'next()' function to iterate through all elements in the data:



The following one works:



If it is out of range, an error message pops up:



Python iterators implement iterator protocol which consists of two special methods __iter__() and __next__(). The __iter__() method returns an iterator object where as __next__() method returns the next element from the sequence.
There is the __iter__() method working behind the scene for the iteration. But __iter__() alone cannot iterate through all items as we need to move to next item for iteration. So __next__() function is used for that.

Here is an example to build our own iterator to display odd number from 1 to the max number supplied as the argument.
In the example below, it seems like iterate with an object doesn't make any sense. But I defined the iterator inside the class for how to iterate an object instantiated from this class.

The 'for loop' didn't show you what is being ran but behind the scene, it invokes the __iter__ method and the __next__ method. I defined the __inter__ method and the __next__ method in the class OddNum to override the what is suppposed to be ran.




Let’s use a Python built-in function 'dir()' to find out all the associated attributes of the iterable x.



There is the __iter__() method working behind the scene for the iteration. But __iter__() alone cannot iterate through all items as we need to move to next item for iteration. So __next__() function is used for that.

2. Generators

To clarify the confusion, let's look at the following example again. When you iterate through a generator function, it will be ran until the first 'yield' and stop at there. This will return the 'yielded' value but won't terminate the function.



'yield n' actually returned 'n'. If I print the 'next(a)' funciton, then you will see 'n' will be printed.



You've to use two more 'next()' functions to print all the n's.



Keep in mind that the following print function won't work (due to the absence of the 'next()' function):



However, if you have at least one 'next()' function, the while loop will at least be ran until the first iteration and stop after the first 'yield'.



Instead, you need a for loop to print out all the intermediate values (the 'for loop' has many 'next()' functions inside).



As we mentioned in the last lecture, 
using the 'Generator Expression as follows, the generator expression did not produce the required result immediately. Instead, it returned a generator object with produces items on demand.
Generators can be implemented in a clear and concise way as compared to their iterator class counterpart.
Generator Expressions:

Like a list, generators can also be written in the same manner except they return a generator object rather than a list:



Take note of the parentheses on either side of the second line denoting a generator expression, which, for the most part, does the same thing that a list comprehension does, but does it lazily: The generator expression takes less size of the memory:



The 'sys.getsizeof' function returns the memory consumption.

3. Closure and Decorator Examples

An example: The result of calling say_hello is passed into the capitalize decorator. The decorator modifies the say_hello function by changing its result to uppercase. We see that capitalize decorator takes in a callable(say_hello) as an argument and returns another callable(uppercase). This is just a basic example on decorator



One more example:

The following one is a simple decorator example that using a wrapper to check if the input is a certain data type.



However, if the returned data is a dictionary:



If the dictionary has more than 1 entires: