Well, see the "if" statement
doesn't return up the stack. It executes. And then control goes on to the next statement. Since there's no next statement in the code, the next statement in Python is assumed to be a "return" statement returning nothing.
The loop statement, on the other hand, never gets to the next statement, because it's still looping. Because the loop termination condition is always false, since "num" never changes (regardless of what nums in recursive calls did). So, no next statement, no implicit return.
A stack is just a pile of stuff. When most modern programming languages execute a procedure, function or method call, the stack is where the return address of the calling code is placed, then all of the function-local variables and the method arguments are piled on top of it. Call a sub-function, the same thing happens again. Do a "return" and the local variables are popped off the stack and the return address is likewise popped and the program execution proceeds at that address. The exact contents and arrangements of the stack data vary depending on the language and the hardware (IBM mainframes like the System/360
had no stack instructions and had to fake it). But the context for a called method is known as a
stack frame and contains everything you need to restore variables, machine registers,
etc. when you jump out of one chunk of code and into another. The process of popping off multiple frames is known as
unwinding. It's done when an Exception is thrown and execution percolates rapidly back up to whatever method set up a "catch" for that exception (with occasional diversions to execute "finally" code).
The execution model for modern programs is basically calls all the way down with the Main program being "called" by the Operating System (or the virtual machine in cases like Python and
Java). So every method that's called has a stack frame, even the Main method. Usually, the active method has a pre-allocated frame so that the method variables don't actually get stored when a sub-method is called because their memory is already allocated in the current frame. Although anything that's in a virtual or actual machine register will get stored before the call is taken.
Languages like Java have basically 2 types of memory - stack frame memory and heap memory (allocated via the "new" operator). Python also has something resembling old-fashioned static memory, which was generally a block of storage allocated when the program started. Java doesn't have that, since all memory in Java is object-based, not hanging around in some sort of global or module-local variable-space.