I've been through a very similar situation when I had to deal with a terrible legacy Windows Forms code written by developers that clearly didn't know what they were doing.
First of all, you're not overacting. This is bad code. Like you said, the catch block should be about aborting and preparing to stop. It's not time to create objects (specially Panels). I can't even start explaining why this is bad.
That being said...
My first advice is: if it's not broken, don't touch it!
If your job is to maintain the code you have to do your best not to break it. I know it's painful (I've been there) but you have to do your best not to break what is already working.
My second advice is: if you have to add more features, try keeping the existing code structure as much as possible so you don't break the code.
Example: if there's a hideous switch-case statement that you feel could be replaced by proper inheritance, you must be careful and think twice before you decide to start moving things around.
You will definitely find situations where a refactoring is the right approach but beware: refactoring code is more likely to introduce bugs. You have to make that decision from the application owners perspective, not from the developer perspective. So you have to think if the effort (money) necessary to fix the problem is worth a refactoring or not. I've seen many times a developer spending several days fixing something that is not really broken just because he thinks "the code is ugly".
My third advice is: you will get burned if you break the code, it doesn't matter if it's your fault or not.
If you've been hired to give maintenance it doesn't really matter if the application is falling apart because somebody else made bad decisions. From the user perspective it was working before and now you broke it. You broke it!
Joel puts very well in his article explaining several reasons why you should not rewrite legacy code.
http://www.joelonsoftware.com/articles/fog0000000069.html
So you should feel really bad about that kind of code (and you should never write anything like that) but maintaining it is a whole different monster.
About my experience: I had to maintain the code for about 1 year and eventually I was able to rewrite it from scratch but not all at once.
What happened is that the code was so bad that new features were impossible to implement. The existing application had serious performance and usability issues. Eventually I was asked to make a change that would take me 3-4 months (mostly because working with that code took me way more time than usual). I thought I could rewrite that whole piece (including implementing the desired new feature) in about 5-6 months. I brought this proposition to the stakeholders and they agree to rewrite it (luckily for me).
After I rewrote this piece they understood I could deliver much better stuff than what they already had. So I was able to rewrite the entire application.
My approach was to rewrite it piece by piece. First I replaced the entire UI (Windows Forms), then I started to replace the communication layer (Web Service calls) and last I replaced the entire Server implementation (it was a thick client / server kind of application).
A couple years later and this application has turned into a beautiful key tool used by the entire company. I'm 100% sure that would've never been possible had I not rewritten the whole thing.
Even though I was able to do it the important part is that the stakeholders approved it and I was able to convince them it was worth the money. So while you have to maintain the existing application just do your best not to break anything and if you're able to convince the owners about the benefit of rewriting it then try to do it like Jack the Ripper: by pieces.
I'll go out on a limb and say: No, this is a terrible idea.
It's just a special case of reusing a variable, which is a bad idea - mainly because it makes it hard to understand what a variable contains at any given point in the program flow. See e.g. Should I reuse variables?
About your points: The points you raise are valid, it's just that reusing the variable is not a good solution :-).
a) it conveys that the response variable contains basically the same
information, just 'transformed' into a different type
Providing this information is a good idea. However, don't do this by using the same variable, because then you obscure the fact that it the information was transformed. Rather, use names with a common pre-/postfix. In your example:
rawResponse = urlopen(some_url)
[...]
jsonResponse = response.read()
[...]
responseData = json.loads(response)
[...]
This makes it clear that the variables are closely related, but also that they do not contain the same data.
b) it conveys that the earlier objects aren't going to be needed any
further down the function, since by reassigning over their variable
I've made them unavailable to later code.
Again, communicating this "no longer needed" is good, but don't do it by reusing the variable: The reuse assignement will usually be hard to see, so you only confuse the reader.
Rather, if a variable lives long after its last use, that is an indication the method/function is too long. Split the part with the short-lived variables into a sub-function; that makes the code easier to read, and limits the variable lifetime.
Note: I usually even go one step further than not reusing variables, and try to even only assign a value once (i.e. never change the value, make it immutable). This is an idea mainly from functional languages, but I found it can make code much clearer. Of course, in non-functional languages, you sometimes need to change a variable (obvious example being a loop variable), but once you start looking, you'll see that in most cases a "fresh" variable makes for more readable and less bug-prone code.
Best Answer
Blocks are perfectly reasonable if you're using them to scope some resource. Files, network connections, memory allocations, database transactions, whatever. In those cases, the block is actually part of the logical structure of the code: you spawn a resource, it exists for some period of time, and then it goes away at a designated time.
But if all you're doing is scoping a name, then I would say that they are bad practice. Generally speaking, of course; special circumstances can apply.
For example, if this function were generated by some code generation system, testing framework, or the like, then blocks for the sake of name scoping is a reasonable thing. But you'd be talking about code written for the purpose of a machine, not a human.
If a human is writing code where they need to reuse names within the same function, I would say that those blocks probably need to be separate functions. Especially if those names are being used with different types and/or meaning within those blocks.