In most of my code, exception handling happens in one file, one function. You throw an exception, and you let it propagate until it is handled. Why is this so hard?
Because where the code that handles it is not the problem (in fact, optimisting that for simplicity could cause other problems). The problem is what state is everything left in when the exception is thrown? What if you're halfway through writing to a file/network socket/screen/shared resource when an exception happens?
That should be cleanly handled in a language like Python and others, where the context managers clean up the resources when exiting a scope, normally or after an exception.
It's like stack unwinding is a new concept or something.
You're missing the point. The OS resources might clean up, but that doesn't mean the application's world (some of which may be a file on a server on another continent) has been left in a known good state.
Trying to understand your point. If you try to open a file or a network connection or divide by zero, you don't handle any exception in that calling function, but instead let them propagate all the way to the top?
What are you going to do with that? Keep returning errors until the calling function gets the error? That's what exceptions do for you. Do you have any guarantee that there is no bug in the error-handling code somewhere?
And what will be the final result of the error? An error screen, right? That's presentation layer. Any fatal error should halt execution, be thrown to the top, and manifest itself as a message in the presentation layer, right?