to end the generation of valuesfunctions either use return statement with no value or simply allow control to fall off the end of the function body to most peoplethis process seems bit implicit (if not magicalon first encounter it' actually quite tangiblethough if you really want to see what is going on inside the forcall the generator function directlyx gensquares( you get back generator object that supports the iteration protocol we met in --the generator function was compiled to return this automatically the returned generator object in turn has __next__ method that starts the function or resumes it from where it last yielded valueand raises stopiteration exception when the end of the series of values is reached and the function returns for conveniencethe next(xbuilt-in calls an object' __next__(method for us in (and next(in )next(xsame as __next__(in next(xuse next(or next(in next( next( next(xtraceback (most recent call last)file ""line in stopiteration as we learned in for loops (and other iteration contextswork with generators in the same way--by calling the __next__ method repeatedlyuntil an exception is caught for generatorthe result is to produce yielded values over time if the object to be iterated over does not support this protocolfor loops instead use the indexing protocol to iterate notice that the top-level iter call of the iteration protocol isn' required here because generators are their own iteratorsupporting just one active iteration scan to put that another way generators return themselves for iterbecause they support next directly this also holds true in the generator expressions we'll meet later in this (more on this ahead) gensquares( iter(yis true next( returns generator which is its own iterator iter(is not requireda no-op here can run next()immediately comprehensions and generations