TIME NOW CALENDAR CONVERT LENGTH CONVERT TEMPERATURE DICTIONARIES, LISTS SCIENCE EDUCATION RELIGION WORK CALCULATOR CALCULATE LIFE

Python 3 programming language, learn python, tutorial

ALL TOPICS

Python 3. Iterators, generators. yeld

To go over a group of items, that is iteration.

An iterable is an object that has an __iter__ method which returns an iterator, or which defines a  __getitem__ method that can take sequential indexes starting from zero (and raises an IndexError when the indexes are no longer valid). So an iterable is an object that you can get an iterator from.

An iterator is an object with a next (Python 2) or __next__ (Python 3) method.

1. The for statement calls iter() on the container object. The function returns an iterator object that defines the method __next__() which accesses elements in the container one at a time. When there are no more elements, __next__() raises a StopIteration exception which tells the for loop to terminate. You can call the __next__() method using the next() built-in function:
s = 'abc'
it = iter(s)
print(it)

print(next(it))
print(next(it))
print(next(it))
print(next(it))

1a.

# As you can see, in the first case foo holds the entire list in memory at once. It's not a big deal for a list with 5 elements, but what if you want a list of 5 million?

def get_odd_numbers(i):
return range(1, i, 2)

def yield_odd_numbers(i):
for x in range(1, i, 2):
yield x

foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
print (foo)

print(bar)
# saves, remembers state of continuation
print(bar.next())
print(bar.next())
print(bar.next())

2.
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)

def __iter__(self):
return self

def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]

rev = Reverse('spam')
print (iter(rev))

for char in rev:
print(char)

3.
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high

def __iter__(self):
return self

def __next__(self):
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1

for c in Counter(3, 8):
print (c)

ITERABLES

3a.
When you create a list, you can read its items one by one. Reading its items one by one is called iteration:
mylist = [1, 2, 3]
for i in mylist:
print(i)

3b.
mylist is an iterable. When you use a list comprehension, you create a list, and so an iterable:
mylist = [x*x for x in range(3)]
for i in mylist:
print(i)

Everything you can use "for... in..." on is an iterable; lists, strings, files...

These iterables are handy because you can read them as much as you wish, but you store all the values in memory and this is not always what you want when you have a lot of values.
GENERATORS

4. Generators are iterators, a kind of iterable you can only iterate over once. Generators do not store all the values in memory, they generate the values on the fly:
mygenerator = (x*x for x in range(3))

for i in mygenerator:
print(i)

for i in mygenerator:
print(i)

It is just the same except you used () instead of []. BUT, you cannot perform for i in mygenerator a second time since generators can only be used once: they calculate 0, then forget about it and calculate 1, and end calculating 4, one by one.

yield is a keyword that is used like return, except the function will return a generator. To master yield, you must understand that when you call the function, the code you have written in the function body does not run. The function only returns the generator object, this is a bit tricky. Then, your code will be run each time the for uses the generator. The generator object is sort of like an adapter - at one end it exhibits the iterator protocol, by exposing `__iter__()` and `next()` methods to keep the `for` loop happy. At the other end however, it runs the function just enough to get the next value out of it, and puts it back in suspended mode. A generator is a lazy, incrementally-pending list, and yield statements allow you to use function notation to program the list values the generator should incrementally spit out. NOTE: the generator is NOT a normal function, it remembers previous state like local variables (stack),
https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do

4a.
def makeRange(n):
# return 0,1,2,...,n-1
i = 0
while i < n:
yield i
i += 1

print (makeRange(5))

# To force the generator to immediately return its pending values, you can pass it into list() (just like you could any iterable):
print (list(makeRange(5)))

4b.
def makeRange(n):
# return 0,1,2,...,n-1
i = 0
while i < n:
yield i
i += 1

x = makeRange(6)
print (list(x))

# cleared
print (list(x))

4. Many yield:
def f():
yield 1
yield 2
yield 3

g = f()
for i in g:
print (i)

for i in g:
print (i)

5.
def createGenerator():
mylist = range(3)
for i in mylist:
yield i*i

# create a generator
mygenerator = createGenerator()

# mygenerator is an object!
print(mygenerator)

for i in mygenerator:
print(i)

# nothing
for i in mygenerator:
print(i)

6.
def counter(low, high):
current = low
while current <= high:
yield current
current += 1

for c in counter(3, 8):
print (c)

7.
def some_function():
i = 1
yield i

for i in some_function():
print (i)

8.
def some_function():

i = [1,2,3]
yield i

for i in some_function():
print (i)

9.
def some_function():
for i in range(4):
yield i

for i in some_function():
print (i)

10. infinite generator
# infinite
def f():
while True:
yield 4

# not infinite
def g():
yield 4

# call infinite
y = 0
for i in f():
y = y + 1;
if (y > 5):
break
print ("f = ", i)

# call not infinite
y = 0
for i in g():
y = y + 1;
if (y > 5):
break
print ("g = ", i)

--
ALL TOPICS