Лучший и/или быстрый способ создания списков в python

В python, насколько мне известно, существует как минимум 3-4 способа создания и инициализации списков заданного размера:

Простой цикл с append:

my_list = []
for i in range(50):
    my_list.append(0)

Простой цикл с +=:

my_list = []
for i in range(50):
    my_list += [0]

Учет списка:

my_list = [0 for i in range(50)]

Список и целочисленное умножение:

my_list = [0] * 50

В этих примерах я не думаю, что была бы разница в производительности, учитывая, что в списках есть только 50 элементов, но что, если мне нужен список из миллиона элементов? Будет ли использование xrange сделать какие-либо улучшения? Какой предпочтительный/самый быстрый способ создания и инициализации списков в python?

Ответы

Ответ 1

Позвольте запустить несколько тестов времени с помощью timeit.timeit:

>>> from timeit import timeit
>>>
>>> # Test 1
>>> test = """
... my_list = []
... for i in xrange(50):
...     my_list.append(0)
... """
>>> timeit(test)
22.384258893239178
>>>
>>> # Test 2
>>> test = """
... my_list = []
... for i in xrange(50):
...     my_list += [0]
... """
>>> timeit(test)
34.494779364416445
>>>
>>> # Test 3
>>> test = "my_list = [0 for i in xrange(50)]"
>>> timeit(test)
9.490926919482774
>>>
>>> # Test 4
>>> test = "my_list = [0] * 50"
>>> timeit(test)
1.5340533503559755
>>>

Как вы можете видеть выше, последний метод является самым быстрым на сегодняшний день.


Однако он должен использоваться только с неизменяемыми элементами (такими как целые числа). Это связано с тем, что он создаст список со ссылками на один и тот же элемент.

Ниже приведена демонстрация:

>>> lst = [[]] * 3
>>> lst
[[], [], []]
>>> # The ids of the items in `lst` are the same
>>> id(lst[0])
28734408
>>> id(lst[1])
28734408
>>> id(lst[2])
28734408
>>>

Такое поведение очень часто нежелательно и может привести к ошибкам в коде.

Если у вас есть изменяемые элементы (например, списки), вы должны использовать по-прежнему очень быстрое понимание списка:

>>> lst = [[] for _ in xrange(3)]
>>> lst
[[], [], []]
>>> # The ids of the items in `lst` are different
>>> id(lst[0])
28796688
>>> id(lst[1])
28796648
>>> id(lst[2])
28736168
>>>

* Примечание. Во всех тестах я заменил range на xrange. Поскольку последний возвращает итератор, он всегда должен быть быстрее первого.

Ответ 2

Если вы хотите увидеть зависимость с длиной списка n:

Чистый питон

enter image description here

Я тестировал длину списка до n = 10000, и поведение оставалось неизменным. Таким образом, метод целочисленного умножения является самым быстрым с разницей.

Numpy

Для списков с более чем 300 элементами вы должны рассмотреть numpy.

enter image description here

Код контрольной точки:

import time

def timeit(f):

    def timed(*args, **kwargs):
        start = time.clock()
        for _ in range(100):
            f(*args, **kwargs)
        end = time.clock()
        return end - start
    return timed

@timeit
def append_loop(n):
    """Simple loop with append"""
    my_list = []
    for i in xrange(n):
        my_list.append(0)

@timeit
def add_loop(n):
    """Simple loop with +="""
    my_list = []
    for i in xrange(n):
        my_list += [0]

@timeit   
def list_comprehension(n):        
    """List comprehension"""
    my_list = [0 for i in xrange(n)]

@timeit
def integer_multiplication(n):
    """List and integer multiplication"""
    my_list = [0] * n


import numpy as np

@timeit
def numpy_array(n):
    my_list = np.zeros(n)


import pandas as pd 

df = pd.DataFrame([(integer_multiplication(n), numpy_array(n)) for n in range(1000)], 
                  columns=['Integer multiplication', 'Numpy array'])
df.plot()

Gist здесь.

Ответ 3

Есть еще один метод, который, будучи странным, удобен в правильных условиях. Если вам нужно создать один и тот же список много раз (инициализируя матрицу для roguelike pathfinding и связанные с ней вещи в моем случае), вы можете сохранить копию список в кортеже, затем переведите его в список, когда вам это нужно. Это заметно быстрее, чем генерация списка с помощью понятий и, в отличие от умножения списков, работает с вложенными структурами данных.

#  In class definition
def __init__(self):
    self.l = [[1000 for x in range(1000)] for y in range(1000)]
    self.t = tuple(self.l)

def some_method(self):
    self.l = list(self.t)
    self._do_fancy_computation()
    #  self.l is changed by this method

#  Later in code:
for a in range(10):
    obj.some_method()

Voila, на каждой итерации у вас есть свежая копия того же списка в кратчайшие сроки!

Отказ от ответственности:

У меня нет ни малейшего понятия, почему это так быстро или работает независимо от CPython 3.4.