map() returns a map object, which is an iterator that yields items on demand. So, the natural replacement for map() is a generator expression because generator expressions return generator objects, which are also iterators that yield items on demand.
Every time a generator function hits a yield expression, it will return a value and pause execution (until it is invoked again using next() or otherwise)
The generator object maintains the state of the execution
If a generator function also returns a value, this will be passed as an argument to the StopIteration exception that is raised at the end of a generator’s execution
Ranges
Before Python 3, range() returned a list - this meant that memory optimisations couldn’t be realised by using range()