Recursion is not required, but it's one of the clearest ways to do unknown levels of nested loops. You will only use one level of stack per element in your input tuple, so you have a fair bit of breathing room.
For a recursive function you need a base case and a recursive case. The base case is when you have zero elements in your tuple, which is where you print your result. The recursive case is where you do each nested loop and append the results to the level below.
def combineRanges(maxValues, result=()):
if len(maxValues) is 0:
for x in range(0, maxValues):
combineRanges(maxValues[1:], result + (str(x),))
Each recursive call pulls one element off the front of
maxValues and adds one result to the
result tuple, until
maxValues is empty. This type of recursion is a little trickier because you are accumulating the result in an argument, but this pattern is very useful and comes up a lot, so it is worth learning. Also, python's lists aren't immutable, which is why I use a tuple to hold the result.