Hacker News new | comments | show | ask | jobs | submit login
A Python Mystery (github.com)
30 points by dcrosta 1554 days ago | hide | past | web | 10 comments | favorite



The issue seems to be that CPython is optimizing the locals access for the exec_code_object_and_return_x by using indices into the array, instead of the normal lookup by name (which, by incident, is what the compiled code uses).

If I understand http://bugs.python.org/issue4831 correctly, CPython 2.7 should not be optimizing the locals access in this way with an exec statement present.


[deleted]


And this:

    >>> dis.dis(exec_code_object_and_return_x)
      2       0 LOAD FAST           0 (codeobj)
              3 LOAD_CONST          0 (None)
              6 DUP_TOP
              7 EXEC_STMT

      3       8 LOAD_FAST           1 (x)
             11 RETURN_VALUE
The interesting case is the second one, in which the compiled codeobj uses DELETE_NAME, which doesn't throw an error (if "del x" was in a function, it would use DELETE_FAST, which would throw an UnboundLocalError if it was used before x was assigned,) and the function then uses LOAD_FAST, which references the function argument directly, and so retrieves the value for return.

By compiling and injecting bytecode like this, the program ends up using two different methods of variable access, which seem to sidestep each other completely.


Is it possible x is referring to the memory where x had previously been allocated?


It turns out that this is not actually as surprising as I had first thought -- del unbinds a variable from the (local) scope. Since exec results in a new Python frame, it is in that frame's scope that x is deleted, not in the outer frame (of the function exec_code_object_and_return_x).

I'm in the process of blogging about this and other quirks of exec -- I'll post back here once that's up.


At least for Python 2.7, you would expect the code run with exec to share the locals and globals with the environment where you called exec from. Thats why the x = x + 1 works, after all.


After reading through the bug you linked, now I agree with your statement and not with my first comment here. Will the madness never end?


Speaking of CPython optimizations

> x = 124236

> y = 124237

> y -= 1

> id(x)

140474467900408

> id(y)

140474467900360

> x is y

False

> x = 1

> y = 2

> y -= 1

> id(x)

140558102245816

> id(y)

140558102245816

> x is y

True


This is because all integers between -5 and 256 in CPython are "cached", so they all have the same identity (hence the True return value of the `is` of the second comparison -- it's between the range).

http://stackoverflow.com/questions/306313/python-is-operator...


CPython interns ints in [-5, 256]. Seems like a reasonable common-case optimization to me.


Agreed. It didn't surprise me much, but it makes things less consistent.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: