iterator pattern when the interpreter encounters the yield statementit keeps record of the current state of the function and returns the value that is yielded once the next value is requested via the __next__(methodthe internal state is loaded and the function continues from where it left off generators are great way to simplify the creation of iterators since generator is function that produces sequence of results and follows the interface for collections that can be iterated over as per the python implementation of the iterator pattern in the case of generator functionthe python internals take care of the iterator protocol for you when generator function is calledit returns generator object but does not begin any execution until the __next__(method is called the first time at that timethe function starts executing and runs until the yield is reached once the generator reaches its endit raises the same exception as iterators do the generator returned by the generator function is also an iterator generator expression much like we used list comprehensions as shorthand to create iteratorsthere is special shorthand for generators called generator expressions look at the example generator expression that follows it is simply an alternative for the generator function we defined earlier ( * for in range( )generator expressions can be used as arguments to certain functions that consume iteratorslike the max(functions print(max(( * for in range( )))this prints the number with pythonwe can drop the parentheses around generator if there is only one argument in the calling function print(max( * for in range( ))this is little bit neater