[Python-ideas] If branch merging

Chris Angelico rosuav at gmail.com
Sun Jun 7 17:29:01 CEST 2015

On Mon, Jun 8, 2015 at 1:04 AM, <random832 at fastmail.us> wrote:
> On Sun, Jun 7, 2015, at 08:20, Chris Angelico wrote:
> > with expr as name:
> > except expr as name:
> > if expr as name:
> >
> > Three parallel ways to do something and capture it. It makes
> > reasonable sense
> The problem is, "with" and "except" create a variable whose scope is
> limited to the enclosed block. The proposed "if... as..." does not, so
> it's misleading.

Actually, they don't. A with block tends to create a broad expectation
that the object will be used within that block, but it isn't scoped,
and sometimes there's "one last use" of something outside of the block
- for instance, a Timer context manager which uses __enter__ to start
timing, __exit__ to stop timing, and then has attributes "wall" and
"cpu" to tell you how much wall time and CPU time were used during
that block. There are a lot of context managers that might as well
have disappeared at the end of the with block (open files, psycopg2
cursors (but not connections), thread locks, etc), but they are
technically still around.

The "except" case is a slightly different one. Yes, the name is valid
only within that block - but it's not a matter of scope, it's a
deliberate unsetting.

>>> e = 2.718281828
>>> try: 1/0
... except Exception as e: pass
>>> e
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'e' is not defined

There's no concept of nested/limited scope here, although I'm sure
that this particular case could be turned into a subscope without
breaking anyone's code (I honestly cannot imagine there being ANY code
that depends on the name getting unset!), if Python ever grows support
for subscopes that aren't associated with nested functions.
Comprehensions do actually create their own scopes, but that's
actually implemented with a function:

>>> e = 2.718281828
>>> [e*2 for e in range(3)]
[0, 2, 4]
>>> e
>>> dis.dis(lambda: [e*2 for e in range(3)])
  1           0 LOAD_CONST               1 (<code object <listcomp> at
0x7fa4f1dae5d0, file "<stdin>", line 1>)
              3 LOAD_CONST               2 ('<lambda>.<locals>.<listcomp>')
              6 MAKE_FUNCTION            0
              9 LOAD_GLOBAL              0 (range)
             12 LOAD_CONST               3 (3)
             15 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             18 GET_ITER
             19 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             22 RETURN_VALUE

Hence, the most logical way to handle conditions with 'as' clauses is
to have them in the same scope. The special case for exceptions is
because tracebacks would create refloops with the locals, and catching
exceptions is extremely common. Nothing else needs that special case,
so everything else can follow the 'with' block model and leave the
name bound.


More information about the Python-ideas mailing list