osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Python-Dev] Compilation of "except FooExc as var" adds useless store


Hello,

As explained in
https://docs.python.org/3/reference/compound_stmts.html#the-try-statement ,

except E as N:
    foo

is actually compiled as:

except E as N:
    try:
        foo
    finally:
        del N

But looking at the generated bytecode, it's worse than that:

             16 STORE_NAME               1 (N)
             18 POP_TOP
             20 SETUP_FINALLY            8 (to 30)

  4          22 LOAD_NAME                2 (foo)
             24 POP_TOP
             26 POP_BLOCK
             28 LOAD_CONST               0 (None)
        >>   30 LOAD_CONST               0 (None)
             32 STORE_NAME               1 (N)
             34 DELETE_NAME              1 (N)
             36 END_FINALLY


It's clear that what happens there is that first None is stored to N,
just to del it as the next step. Indeed, that's what done in the
compile.c:

https://github.com/python/cpython/blob/master/Python/compile.c#L2905

Storing None looks superfluous. For example, DELETE_FAST explicitly
stores NULL on delete.
https://github.com/python/cpython/blob/master/Python/ceval.c#L2249 

Likewise, for DELETE_NAME/DELETE_GLOBAL, PyObject_DelItem() is
called which translates to:

m->mp_ass_subscript(o, key, (PyObject*)NULL);

So hopefully any compliant mapping implementation actually clears
stored value, not leaving it dangling.

The "store None" behavior can be traced down to introduction of the
"del except target var" behavior back in 2007:
https://github.com/python/cpython/commit/b940e113bf90ff71b0ef57414ea2beea9d2a4bc0#diff-cb296cc5109f5640ff3f6d7198a6abeeR1999

There's no clear explanation why it's done like that, so probably an
artifact of the initial implementation. Note that even
https://github.com/python/cpython/commit/520b7ae27e39d1c77ea74ccd1b184d7cb43f9dcb
which did quite a bunch of refactoring to "except" implementation, and
reformatted this code, otherwise left it in place.

P.S.
I actually wanted to argue that such an implementation is hardly ideal
at all. Consider:

----
e = "my precious data"

try:
    1/0
except Exception as e:
    pass

# Where's my *global* variable?
# Worse, my variable can be gone or not, depending on whether exception
# triggered or not.
print(e)
----

So, perhaps the change should be not removing "e = None" part, but
conversely, removing the "del e" part.


-- 
Best regards,
 Paul                          mailto:pmiscml at gmail.com