Como criar uma lista com um tamanho específico em Python

  1. Pré-alocação de armazenamento para listas
  2. Pré-alocação de armazenamento para outras estruturas de dados sequenciais

A pré-alocação de armazenamento para listas ou arrays é um padrão típico entre os programadores quando eles sabem o número de elementos antes do tempo.

Ao contrário do C++ e Java, em Python, você tem que inicializar todo o seu armazenamento pré-alocado com alguns valores. Normalmente, desenvolvedores utilizam valores falsos para esse fim, como None, '', False, e 0.

Python oferece várias maneiras de criar uma lista de tamanho fixo, cada uma com características de desempenho diferentes.

Para comparar desempenhos de diferentes abordagens, usaremos o padrão Python módulo timeit. Ele fornece uma maneira útil de medir tempos de execução de pequenos pedaços de código Python.

Pré-alocação de armazenamento para listas

A primeira e mais rápida forma de utilizar o operador *, que repete uma lista de número de vezes.

>>> [None] * 10
[None, None, None, None, None, None, None, None, None, None]

Um milhão de iterações (valor padrão das iterações no timeit) leva aproximadamente 117 ms.

>>> timeit("[None] * 10")
0.11655918900214601

Outra abordagem é utilizar a função embutida range com uma lista de compreensão.

>>> [None for _ in range(10)]
[None, None, None, None, None, None, None, None, None, None]

É quase seis vezes mais lento e leva 612 ms de segundo por milhão de iterações.

>>> timeit("[None for _ in range(10)]")
0.6115895550028654

A terceira abordagem é utilizar um simples loop for junto com o list.append().

>>> a = []
>>> for _ in range(10):
...   a.append(None)
...
>>> a
[None, None, None, None, None, None, None, None, None, None]

A utilização de loops é o método mais lento e leva 842 ms para completar um milhão de iterações.

>>> timeit("for _ in range(10): a.append(None)", setup="a=[]")
0.8420009529945673

Pré-alocação de armazenamento para outras estruturas de dados sequenciais

Como você está pré-alocando o armazenamento para uma estrutura de dados sequencial, pode fazer muito sentido utilizar a estrutura de dados embutida Array em vez de uma lista.

>>> from array import array
>>> array('i',(0,)*10)
array('i', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

Como vemos abaixo, esta abordagem é a segunda mais rápida depois de [None] * 10.

>>> timeit("array('i',(0,)*10)", setup="from array import array")
0.4557597979946877

Vamos comparar as abordagens Python puras acima com o pacote NumPy Python para computação científica.

>>> from numpy import empty
>>> empty(10)
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

O modo NumPy leva 589 ms por milhão de iterações.

>>> timeit("empty(10)", setup="from numpy import empty")
0.5890094790011062

Entretanto, o modo NumPy será muito mais rápido para listas mais massivas.

>>> timeit("[None]*10000")
16.059584009999526
>>> timeit("empty(10000)", setup="from numpy import empty")
1.1065983309963485

A conclusão é que é melhor se ater a [None] * 10 para listas pequenas, mas trocar para NumPy’s empty() ao lidar com dados seqüenciais mais maciços.

Artigo relacionado - Python List

  • Compreensão de lista aninhada em Python
  • if...else em Python List Comprehension