There is a belief that a list comprehension is syntactic sugar for passing a generator expression to list (1, 2). While I can't pinpoint the internal dynamics to prove this isn't the case, I've been able to show that there is an O(n) difference between these two methods:
n = 100000
%timeit [i for i in range(n)]          # 6.03 ms per loop
%timeit list(i for i in range(n))      # 10 ms per loop
%timeit [i for i in range(n*10)]       # 117 ms per loop
%timeit list(i for i in range(n*10))   # 157 ms per loop
%timeit [i for i in range(n*100)]      # 1.18 s per loop
%timeit list(i for i in range(n*100))  # 1.66 s per loop
O(n) calculation:
Scaling = 1, differential = 4ms
Scaling = 10, differential = 40ms
Scaling = 100, differential = 480ms
I have looked at the output of dis.dis. This shows a different order of processing in the latter, but it's not clear to me what these function calls reference:
import dis
dis.dis('[i for i in range(n)]')
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x0000000004E82300, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (range)
              8 LOAD_NAME                1 (n)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE
dis.dis('list(i for i in range(n))')
  1           0 LOAD_NAME                0 (list)
              2 LOAD_CONST               0 (<code object <genexpr> at 0x0000000004EF94B0, file "<dis>", line 1>)
              4 LOAD_CONST               1 ('<genexpr>')
              6 MAKE_FUNCTION            0
              8 LOAD_NAME                1 (range)
             10 LOAD_NAME                2 (n)
             12 CALL_FUNCTION            1
             14 GET_ITER
             16 CALL_FUNCTION            1
             18 CALL_FUNCTION            1
             20 RETURN_VALUE
Can someone clarify precisely what's happening here?
