Hacker News new | past | comments | ask | show | jobs | submit login

I took the same quiz, and noticed the exact same. When I saw the first one, my thought was "crap, I don't remember how many integers python interns", and then I saw the next question and figured it out, but it was sort of silly that a lot of their test was stuff like this that can easily be looked up and can change by implementation (e.g. does pypy do the same? ironpython?) Plus, I was a bit mad that they wouldn't tell you which ones you got right or wrong, or even how many. Why did I bother taking it then?



Yeah, they never emailed me the scores either, and told me there was no way to know at the booth... I even joked with them that they were just trying to link it to my fb profile.

I actually didn't catch the trick until I asked someone later if they had gotten the same problem twice, "Almost, but the values were different" which is when I realized it (I blame the beer during opening night). However, as I found out today, because the way they worded the question it was all on a single line (if I remember correctly). So both cases should actually have been True, and I was right both times :P

> (e.g. does pypy do the same? ironpython?)

I wasted... ahem, spent a few hours today playing with this and found a few things, it's pretty neat!

In PyPy, `is` will always be True for primitives. This also means NaN is NaN (but still never == NaN). It doesn't use memory location for things like id() like CPython does, but it guarantees you get a unique representation. [0]

In CPython (3.5.2), it will always cache [-5, 256]. Here's the code that creates that array at startup [1] and each possible init/copy function calls this first to see if it's a cached int [2].

But it can sometimes cache other values! It also depends on if you do it from a module or the repl, and it's different depending on what scope or line each name/value is on. This one weirded out some coworkers, all from one continuous repl session:

    >>> x = 500
    >>> x is 500
    False

    >>> x = 500; x is 500
    True

    >>> def wat():
    ...   x = 500
    ...   return x is 500
    >>> wat()
    True

    >>> x = 500
    >>> def wat_wat():
    ...   return x is 500
    >>> wat_wat()
    False
    
If you run it from a script you instead get True, True, True, False. You can disassemble it to get a hint at why. One uses STORE_FAST and LOAD_FAST, and the other LOAD_GLOBAL.

    >>> disassemble(wat.__code__)
    2           0 LOAD_CONST               1 (500)
                3 STORE_FAST               0 (x)

    3           6 LOAD_FAST                0 (x)
                9 LOAD_CONST               1 (500)
                12 COMPARE_OP               8 (is)
                15 RETURN_VALUE
    >>> disassemble(wat_wat.__code__)
    2           0 LOAD_GLOBAL              0 (x)
                3 LOAD_CONST               1 (500)
                6 COMPARE_OP               8 (is)
                9 RETURN_VALUE
Now I'm still trying to figure out why STORE_FAST and LOAD_FAST cause it to use the same object, and why those only get called when it's interpreted together. When I figure this out I might put together a longer post about all this.

Also don't use `is` for comparing integers. ;)

[0] http://doc.pypy.org/en/latest/cpython_differences.html#objec...

[1] https://github.com/python/cpython/blob/master/Objects/longob...

[2] https://github.com/python/cpython/blob/ba85d69a3e3610bdd05f0...


Wow, this is amazing research! I had no idea the rules actually varied this much. The same like one is especially odd. The LOAD_FAST seems to make sense because it's defined as : "Pushes a reference to the local co_varnames[var_num] onto the stack." (emphasis mine).

Also see the global case:

  In [2]: x = 500
  
  In [7]: def wat_wat_wat():
     ...:     global x
     ...:     x = 600
     ...:     return x is 600
     ...:
  
  In [8]: dis.disassemble(wat_wat_wat.__code__)
    3           0 LOAD_CONST               1 (600)
                2 STORE_GLOBAL             0 (x)
  
    4           4 LOAD_GLOBAL              0 (x)
                6 LOAD_CONST               1 (600)
                8 COMPARE_OP               8 (is)
               10 RETURN_VALUE
  
  In [9]: wat_wat_wat()
  Out[9]: True

But this only goes further to prove my point - it's a bit of a silly question to quiz someone on :-) For one the question didn't seem to be well defined enough, but also it tests something that no one in their right mind would do, and also I don't know if the authors realized these nuances themselves either. I guess it makes for good trivia.


Haha thanks, I was inspired by David Wolever's talk this year. This is the first time I've gone through the disassembler module or the CPython source, it's much less intimidating than I thought it would be.

Also wat! The bytecode for the global case makes sense, but I wasn't expecting True?!


Yeah, now I'm even more confused!




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: