My QA Projects

QA Projects I was involded.

View on GitHub

Generators

Generator Functions

def my_range(first=0, last=10, step=1):
    number = first
    while number < last:
        yield number
        number += step

=> NOTE: I need to find a good example.

Generator Comprehensions

genobj = (pair for pair in zip(['a','b'],['1','2']))
genobj
<genrator object <genexpr> at 0x10308fde0>
for thing in genobj:
    print(thing)
('a','1')
('b','2')

06 Generators

A generator is a special class of function that serves as an iterator instead of returning a single value the generator returns a stream of values.

#generator.py 
def main():
    for i in range(25):
        print(i, end = ' ')
    print()

def inclusive_range(*args):
    numargs = len(args)
    start = 0
    step = 1
    
    # initialize parameters
    if numargs < 1:
        raise TypeError(f'at least 1 argument, got {numargs}')
    elif numargs == 1:
        stop = args[0]
    elif numargs == 2:
        (start, stop) = args
    elif numargs == 3:
        (start, stop, step) = args
    else: raise TypeError(f'at most 3 arguments, got {numargs}')

    # generator
    i = start
    while i <= stop:
        yield i
        i += step

if __name__ == '__main__': main()

If instead of calling my function I call range here and save and run you notice I’ve specified the number 25 and in a result it counts from zero to 24, which is 25 values starting at zero. I find that a little bit confusing myself at times and so I created a version of range that I call inclusive range. It simply works exactly like range only it returns all of the values from zero all the way up to 25, when I run this you see that my result now includes the 25. it’s great as an illustration of how a generator works, because that’s what range is is it’s a generator.

def main():
    for i in inclusive_range(25):
        print(i, end = ' ')
    print()

def inclusive_range(*args):
    numargs = len(args)
    start = 0
    step = 1
    
    # initialize parameters
    if numargs < 1:
        raise TypeError(f'at least 1 argument, got {numargs}')
    elif numargs == 1:
        stop = args[0]
    elif numargs == 2:
        (start, stop) = args
    elif numargs == 3:
        (start, stop, step) = args
    else: raise TypeError(f'at most 3 arguments, got {numargs}')

    # generator
    i = start
    while i <= stop:
        yield i
        i += step

if __name__ == '__main__': main()

So let’s take a look at the generator function. First off you’ll notice that we use a variable argument list and our number of args is the length and we initialize a couple of variables and then we use this chain of elifs, if and elifs, to decide how to initialize our variables however many arguments we get.

You remember the range works if it’s just one argument that argument is the stop. If it has two arguments the two arguments are start and stop, and if it has three arguments the arguments are start, stop, and step, and if I have less than one argument I raise an error and if I have more than three arguments I raise an error.

Then the actual generator is down here at the bottom. We use a while loop, we initialize i at the beginning and while i <= stop then we have this yield. Now yield is like return except it’s for a generator. It yields a value and then after it yields the value the function continues until it yields the next value. So when we run this we get => it works exactly like range.

I can tell it to start at five instead of starting at zero and when I run this

def main():
    for i in inclusive_range(5, 25):
        print(i, end = ' ')
    print()

def inclusive_range(*args):
    numargs = len(args)
    start = 0
    step = 1
    
    # initialize parameters
    if numargs < 1:
        raise TypeError(f'at least 1 argument, got {numargs}')
    elif numargs == 1:
        stop = args[0]
    elif numargs == 2:
        (start, stop) = args
    elif numargs == 3:
        (start, stop, step) = args
    else: raise TypeError(f'at most 3 arguments, got {numargs}')

    # generator
    i = start
    while i <= stop:
        yield i
        i += step

if __name__ == '__main__': main()

you notice it starts at five. And I can tell it to step by five if I like and when I run this you notice it counts by five. Five, 10, 15, 20, and 25.

def main():
    for i in inclusive_range(5, 25, 5):
        print(i, end = ' ')
    print()

def inclusive_range(*args):
    numargs = len(args)
    start = 0
    step = 1
    
    # initialize parameters
    if numargs < 1:
        raise TypeError(f'at least 1 argument, got {numargs}')
    elif numargs == 1:
        stop = args[0]
    elif numargs == 2:
        (start, stop) = args
    elif numargs == 3:
        (start, stop, step) = args
    else: raise TypeError(f'at most 3 arguments, got {numargs}')

    # generator
    i = start
    while i <= stop:
        yield i
        i += step

if __name__ == '__main__': main()

It’s really very simple, most of the processing of this function is dealing with the arguments. The generator itself is really just this four lines of code.

You notice also that I’m using exceptions for my errors and we’ll talk about exceptions in a later chapter, but if I give it no arguments I get an error that says type error expected at least one argument, it got zero, and if I give it too many arguments it says type error expected at most three arguments got four and those are these exceptions right here, raise type error, raise type error. If I put this back it works as expected.

So a generator is a special case of a function that is useful for creating a series of values.