1

Following up from: Create List of Single Item Repeated n Times in Python

python -m timeit '[0.5]*100000'
1000 loops, best of 3: 382 usec per loop

python -m timeit '[0.5 for i in range(100000)]'
100 loops, best of 3: 3.07 msec per loop

Obviously, the second one is slower due to range(). I am not aware why [e] * n is so fast (or how it's implemented internally in Python).

Community
  • 1
  • 1
yang5
  • 1,125
  • 11
  • 16

2 Answers2

6

dis.dis lets you look at the operations Python performs when evaluating each expression:

In [57]: dis.dis(lambda: [0.5]*100000)
  1           0 LOAD_CONST               1 (0.5)
              3 BUILD_LIST               1
              6 LOAD_CONST               2 (100000)
              9 BINARY_MULTIPLY     
             10 RETURN_VALUE        

In [58]: dis.dis(lambda: [0.5 for i in range(100000)])
  1           0 BUILD_LIST               0
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (100000)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                12 (to 28)
             16 STORE_FAST               0 (i)
             19 LOAD_CONST               2 (0.5)
             22 LIST_APPEND              2
             25 JUMP_ABSOLUTE           13
        >>   28 RETURN_VALUE        

The list comprehension is performing a loop, loading the constant 0.5 each time, and appending it to a result list.

The expression [0.5]*100000 requires only one BINARY_MULTIPLY.


Also note that [obj]*N makes a list of length N, with N reference to the exact same obj.

The list comprehension [expr for i in range(N)] evaluates expr N times -- even if expr evaluates to the same value each time.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
2

Adding on to what @unutbu said, BINARY_MULTIPLY ends up executing this tight loop in listobject.c:

if (Py_SIZE(a) == 1) {
    elem = a->ob_item[0];
    for (i = 0; i < n; i++) {
        items[i] = elem;
        Py_INCREF(elem);
    }
    return (PyObject *) np;
}

This is pretty self-explanatory: it makes a bunch of references to the same object in a tight C loop. Thus nearly 100% of [obj] * N executes in native code, meaning it goes really fast.

Standard caveats about doing this with mutable objects apply (ie: don't do it with mutable objects), since you make a boatload of references to the same object.

roippi
  • 25,533
  • 4
  • 48
  • 73