The main advantage of generators over just building a list or something is that the items are generated one at a time. This means that:
Think of reading lines from a CSV file (read the file one line at a time instead of the whole thing, which could be GBs). Or even reading the first 50 lines that contain "hello".
Additionally, the yield keyword makes it easy to build state machines. E.g. for a parsing context-dependent input ("var" must be followed by a name and then "="). If you want to do it one token at a time, you could build an elaborate state machine that remembers the last token. But you could also
if input.nex_word() == "var":
yield Var()
identifier_name = input.next_word()
if is_valid_identifier(identifier_name ier):
yield Identifier(identifier_name)
else:
raise InvalidInputException()
if input.next_word() == "=":
yield Assign()
else:
raise InvalidInputException()
Which is easier to read and to write.
You can also send values into generators, a bit like channels, which is a whole extra topic.
TL;DR: it's more than trivial math examples.
P.s. you can also type stuff in the body if your question if the title is too long