[Python-ideas] New scope for exception handlers
Steven D'Aprano
steve at pearwood.info
Sat Apr 9 03:44:44 EDT 2016
On Fri, Apr 08, 2016 at 05:03:05PM -0400, Joseph Jevnik wrote:
> I would like to propose a change to exception handlers to make it harder to
> accidently leak names defined only in the exception handler blocks. This
> change follows from the decision to delete the name of an exception at the
> end of a handler. The goal of this change is to prevent people from relying
> on names that are defined only in a handler.
An interesting proposal, but you're missing one critical point: why is
it harmful to create names inside an except block?
There is a concrete reason why Python 3, and not Python 2, deletes the
"except Exception as err" name when the except block leaves: because
exceptions now hold on to a lot more call info, which can prevent
objects from being garbage-collected. But the same doesn't apply to
arbitrary names.
At the moment, only a few block statements create a new scope: def and
class mostly. In particular, no flow control statement does: if, elif,
else, for, while, try, except all use the existing scope. This is a nice
clean design, and in my opinion must better than the rule that any
indented block is a new scope. I would certainly object to making
"except" the only exception (pun intended) and I would object even more
to making *all* the block statements create a new scope.
Here is an example of how your proposal would bite people. Nearly all by
code is hybrid 2+3 code, so I often have a construct like this at the
start of modules:
try:
import builtins # Python 3.x
except ImportError:
# Python 2.x
import __builtin__ as builtins
Nice and clean. But what if try and except introduced a new scope? I
would have to write:
builtins = None
try:
global builtins
import builtins
except ImportError:
global builtins
import __builtin__ as builtins
assert builtins is not None
Since try and except are different scopes, I need a separate global
declaration in each. If you think this second version is an improvement
over the first, then our ideas of what makes good looking code are so
far apart that I don't think its worth discussing this further :-)
If only except is a different scope, then I have this shorter version:
try: # global scope
import builtins
except ImportError: # local scope
global builtins
import __builtin__ as builtins
> As an example, let's looks at a function with a try except:
>
>
> def f():
> try:
> ...
> except:
> a = 1
> return a
>
>
> This function will only work if the body raises some exception, otherwise
> we will get an UnBoundLocalError.
Not necessary. It depends on what is hidden by the ... dots. For
example:
def f():
try:
a = sequence.pop()
except AttributeError:
a = -1
return a
It might not be the most Pythonic code around, but it works, and your
proposal will break it.
Bottom line is, there's nothing fundamentally wrong with except blocks
*not* starting a new scope. I'm not sure if there's any real benefit to
the proposal, but even if there is, I doubt it's worth the cost of
breaking existing working code.
So if you still want to champion your proposal, it's not enough to
demonstrate that it could be done. You're going to have to demonstrate
not only a benefit from the change, but that the benefit is worth
breaking other people's code.
--
Steve
More information about the Python-ideas
mailing list