Почему копирование списка с использованием среза [:] быстрее, чем использование очевидного пути?

Почему мелкое копирование списка с использованием среза происходит намного быстрее, чем при использовании list builtin?

In [1]: x = range(10)

In [2]: timeit x_ = x[:]
10000000 loops, best of 3: 83.2 ns per loop

In [3]: timeit x_ = list(x)
10000000 loops, best of 3: 147 ns per loop

Обычно, когда я вижу такие странные вещи, они фиксируются в python3, но это несоответствие все еще существует:

In [1]: x = list(range(10))

In [2]: timeit x_ = x[:]
10000000 loops, best of 3: 100 ns per loop

In [3]: timeit x_ = list(x)
10000000 loops, best of 3: 178 ns per loop

Ответы

Ответ 1

Разница заключается в дополнительном вызове функции (только SLICE+0 vs CALL_FUNCTION 1 с дополнительными операциями стека):

>>> import dis
>>> def f(lst):
...  return lst[:]
... 
>>> def f1(lst):
...  return list(lst)
... 
>>> dis.dis(f)
  2           0 LOAD_FAST                0 (lst)
              3 SLICE+0             
              4 RETURN_VALUE        
>>> dis.dis(f1)
  2           0 LOAD_GLOBAL              0 (list)
              3 LOAD_FAST                0 (lst)
              6 CALL_FUNCTION            1
              9 RETURN_VALUE 

Из dis docs:

ЛОМТИК + 0()
Реализует TOS = TOS [:].

(TOS - верхняя часть стека)

call_function (ARGC)
Вызывает функцию. Низкий байт argc указывает количество позиционных параметров, высокий байт - количество параметры ключевых слов. В стеке код операции находит ключевое слово параметров. Для каждого аргумента ключевого слова значение находится поверх ключ. Ниже параметров ключевого слова позиционные параметры в стеке, с самым верным параметром сверху. Ниже параметры, объект функции для вызова находится в стеке. Поп все аргументы функции и сама функция из стека, и толкает возвращаемое значение.