"We have previously seen that we can iterate over items in, for example, a dictionary or a list.\n",
"\n",
"For example:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cat\n",
"dog\n",
"bird\n"
]
}
],
"source": [
"my_list = ['cat', 'dog', 'bird']\n",
"\n",
"for item in my_list:\n",
" print(item)\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This loop iterates over all elements in the list, gives us access to each element in turn and then stops once we reach the end of the list.\n",
"\n",
"We could do this manually by creating an *iterator* and then use this to traverse the list, until no more elements are left over and an ```StopIteration``` exception is raised.\n",
"\n",
"We define the iterator with the keyword ```my_iter = iter(my_object)``` and then proceed to the next item with ```next(my_iter)```."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cat\n",
"dog\n",
"bird\n",
"Reached the end of the list \n"
]
}
],
"source": [
"my_list = ['cat', 'dog', 'bird']\n",
"\n",
"my_iter = iter(my_list)\n",
"\n",
"try:\n",
" while True:\n",
" print(next(my_iter))\n",
"except StopIteration as e:\n",
" print('Reached the end of the list {}'.format(e))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Obviously, this is quite a horrible way to loop over a list (though some programming languages are not far off from doing this as the default way...)\n",
"\n",
"One situation where we need to think about iterators is when we need to define a class that we can iterate over.\n",
"Then wen need to implement the \"magic\" (or dunder) functions ```__iter__()``` and ```__next__()```. Remember that the double underscores before and after the keywords indicate that we should not call these methods ourselves. \\\n",
"For example, you may need to develop a more fancy list, a counter, an even more powerful dictionary, ..."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Value of the counter is now: 0\n",
"Value of the counter is now: 1\n",
"Value of the counter is now: 2\n",
"Value of the counter is now: 3\n",
"Value of the counter is now: 4\n",
"Value of the counter is now: 5\n"
]
}
],
"source": [
"class Counter:\n",
" def __init__(self, start, stop):\n",
" # number: our counter we want to iterate over\n",
" self.number = start\n",
" self.stop = stop\n",
"\n",
" def __iter__(self):\n",
" return self\n",
" \n",
" def __next__(self):\n",
" # check if we have reached the largest number we want to run over\n",
" if self.number > self.stop:\n",
" raise StopIteration\n",
" else:\n",
" current_number = self.number\n",
" self.number = self.number + 1\n",
" return current_number\n",
"\n",
"\n",
"my_counter = Counter(0, 5)\n",
"for counter in my_counter:\n",
" print('Value of the counter is now: {}'.format(counter))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Generators\n",
"\n",
"Generators in python are special functions that remember their state each time you call them. Remeber that with the \"normal\" function we have seen earlier, we call the function, do our computations or other actions, maybe define local variables, etc. We may return the result of what the function does - but then the fuction is \"forgotten\".\n",
"\n",
"In some cases, we may want to remember the state of the function. For example, we could read from a very large file: If it's very large, we cannot keep it in memory, but need to parse the content one line at the time. However, it would be very cumbersome to open a file, read one line, remember which line we have read, close the file, open the file, jump to the appropriate place, etc. \\\n",
"Another use-case is we want to compute a long list of elements - but we do not know how many to start with. We could do this with a ```for``` or ```while``` loop (as we have done previously) and define the start and stop conditions.\n",
"\n",
"Generators provide a neat way to do this without having to decide on start or stop condition in the function and, instead, focus on the function itself.\n",
"\n",
"Generators are defined very similarly to normal functions, the main difference is the keyword ```yield```. When we encounter the ```yield``` statement, the execution of the function is stopped, we return a generator object (instead of the value), and the current state of the function is kept\n",
"\n",
"**Example** \\\n",
"Infinite sum - we want to obtain the sum of all integers until we decide to stop."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"2\n",
"3\n",
"4\n",
"5\n",
"6\n",
"7\n"
]
}
],
"source": [
"def my_infinite_sum():\n",
" sum = 0\n",
" while True:\n",
" yield sum\n",
" sum = sum + 1\n",
"\n",
"# Then we can use this and print a few elements\n",
"my_generator = my_infinite_sum()\n",
"print(next(my_generator))\n",
"\n",
"print(next(my_generator))\n",
"\n",
"print(next(my_generator))\n",
"\n",
"# we can also loop over this\n",
"for i in range(0, 5):\n",
" print(next(my_generator))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also introduce a stop criterion that prevents us from reaching the ```yield``` statement. Then, as with the iterators above, a ```StopIteration``` exception to signal the end."
We have previously seen that we can iterate over items in, for example, a dictionary or a list.
For example:
%% Cell type:code id: tags:
``` python
my_list=['cat','dog','bird']
foriteminmy_list:
print(item)
```
%% Output
cat
dog
bird
%% Cell type:markdown id: tags:
This loop iterates over all elements in the list, gives us access to each element in turn and then stops once we reach the end of the list.
We could do this manually by creating an *iterator* and then use this to traverse the list, until no more elements are left over and an ```StopIteration``` exception is raised.
We define the iterator with the keyword ```my_iter = iter(my_object)``` and then proceed to the next item with ```next(my_iter)```.
%% Cell type:code id: tags:
``` python
my_list=['cat','dog','bird']
my_iter=iter(my_list)
try:
whileTrue:
print(next(my_iter))
exceptStopIterationase:
print('Reached the end of the list {}'.format(e))
```
%% Output
cat
dog
bird
Reached the end of the list
%% Cell type:markdown id: tags:
Obviously, this is quite a horrible way to loop over a list (though some programming languages are not far off from doing this as the default way...)
One situation where we need to think about iterators is when we need to define a class that we can iterate over.
Then wen need to implement the "magic" (or dunder) functions ```__iter__()``` and ```__next__()```. Remember that the double underscores before and after the keywords indicate that we should not call these methods ourselves. \
For example, you may need to develop a more fancy list, a counter, an even more powerful dictionary, ...
%% Cell type:code id: tags:
``` python
classCounter:
def__init__(self,start,stop):
# number: our counter we want to iterate over
self.number=start
self.stop=stop
def__iter__(self):
returnself
def__next__(self):
# check if we have reached the largest number we want to run over
ifself.number>self.stop:
raiseStopIteration
else:
current_number=self.number
self.number=self.number+1
returncurrent_number
my_counter=Counter(0,5)
forcounterinmy_counter:
print('Value of the counter is now: {}'.format(counter))
```
%% Output
Value of the counter is now: 0
Value of the counter is now: 1
Value of the counter is now: 2
Value of the counter is now: 3
Value of the counter is now: 4
Value of the counter is now: 5
%% Cell type:markdown id: tags:
# Generators
Generators in python are special functions that remember their state each time you call them. Remeber that with the "normal" function we have seen earlier, we call the function, do our computations or other actions, maybe define local variables, etc. We may return the result of what the function does - but then the fuction is "forgotten".
In some cases, we may want to remember the state of the function. For example, we could read from a very large file: If it's very large, we cannot keep it in memory, but need to parse the content one line at the time. However, it would be very cumbersome to open a file, read one line, remember which line we have read, close the file, open the file, jump to the appropriate place, etc. \
Another use-case is we want to compute a long list of elements - but we do not know how many to start with. We could do this with a ```for``` or ```while``` loop (as we have done previously) and define the start and stop conditions.
Generators provide a neat way to do this without having to decide on start or stop condition in the function and, instead, focus on the function itself.
Generators are defined very similarly to normal functions, the main difference is the keyword ```yield```. When we encounter the ```yield``` statement, the execution of the function is stopped, we return a generator object (instead of the value), and the current state of the function is kept
**Example**\
Infinite sum - we want to obtain the sum of all integers until we decide to stop.
%% Cell type:code id: tags:
``` python
defmy_infinite_sum():
sum=0
whileTrue:
yieldsum
sum=sum+1
# Then we can use this and print a few elements
my_generator=my_infinite_sum()
print(next(my_generator))
print(next(my_generator))
print(next(my_generator))
# we can also loop over this
foriinrange(0,5):
print(next(my_generator))
```
%% Output
0
1
2
3
4
5
6
7
%% Cell type:markdown id: tags:
We can also introduce a stop criterion that prevents us from reaching the ```yield``` statement. Then, as with the iterators above, a ```StopIteration``` exception to signal the end.