[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:

    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
    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 

def f():
        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.


More information about the Python-ideas mailing list