What happens much closer to the hardware level than Python is concerned, is that the return value is pushed to the stack and then execution jumps back to where the function was called, the caller can then find the return value at the top of the stack
Printing would instead call some other procedure which makes things appear on some monitor, this is pretty much going rogue unless the caller asked for it, a slow and unwanted side-effect. And if it doesn't then leave the result on top of the stack as it returns, then the caller has no access to that.
Computer memory is like an array (in some languages arrays are addresses to memory, though the OS is still adding a layer of abstraction first). Executing code is to loop from a location and executing an instruction one at a time. Some of those are to jump to other locations.
The stack is like a pile of values where only the location of the top value is kept track of. Adding something means to move the address one step, removing is to move the address in the other direction one step. To get the 5th value from the top, you start at the top and count 5 steps down and there's your value. This is how computers keep track of function calls and also the local variables in each function call, all piled up on a stack. Other values are instead stored wherever there's free room, and and address to where that is gets stored in on the stack. Useful for carrying values between function calls, particularly large ones.
A less technical analogy is if you ask me the time and I go tell Bob the time and then return to you saying nothing. It's just kind of rude isn't it.