O caminho pitônico para isso é:
x = [None] * numElements
ou qualquer valor padrão com o qual você deseja pré-pop-up, por exemplo
bottles = [Beer()] * 99
sea = [Fish()] * many
vegetarianPizzas = [None] * peopleOrderingPizzaNotQuiche
[EDIT: Caveat Emptor A [Beer()] * 99
sintaxe cria uma Beer
e preenche uma matriz com 99 referências à mesma instância única]
A abordagem padrão do Python pode ser bastante eficiente, embora essa eficiência decaia à medida que você aumenta o número de elementos.
Comparar
import time
class Timer(object):
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *args):
end = time.time()
secs = end - self.start
msecs = secs * 1000 # millisecs
print('%fms' % msecs)
Elements = 100000
Iterations = 144
print('Elements: %d, Iterations: %d' % (Elements, Iterations))
def doAppend():
result = []
i = 0
while i < Elements:
result.append(i)
i += 1
def doAllocate():
result = [None] * Elements
i = 0
while i < Elements:
result[i] = i
i += 1
def doGenerator():
return list(i for i in range(Elements))
def test(name, fn):
print("%s: " % name, end="")
with Timer() as t:
x = 0
while x < Iterations:
fn()
x += 1
test('doAppend', doAppend)
test('doAllocate', doAllocate)
test('doGenerator', doGenerator)
com
#include <vector>
typedef std::vector<unsigned int> Vec;
static const unsigned int Elements = 100000;
static const unsigned int Iterations = 144;
void doAppend()
{
Vec v;
for (unsigned int i = 0; i < Elements; ++i) {
v.push_back(i);
}
}
void doReserve()
{
Vec v;
v.reserve(Elements);
for (unsigned int i = 0; i < Elements; ++i) {
v.push_back(i);
}
}
void doAllocate()
{
Vec v;
v.resize(Elements);
for (unsigned int i = 0; i < Elements; ++i) {
v[i] = i;
}
}
#include <iostream>
#include <chrono>
using namespace std;
void test(const char* name, void(*fn)(void))
{
cout << name << ": ";
auto start = chrono::high_resolution_clock::now();
for (unsigned int i = 0; i < Iterations; ++i) {
fn();
}
auto end = chrono::high_resolution_clock::now();
auto elapsed = end - start;
cout << chrono::duration<double, milli>(elapsed).count() << "ms\n";
}
int main()
{
cout << "Elements: " << Elements << ", Iterations: " << Iterations << '\n';
test("doAppend", doAppend);
test("doReserve", doReserve);
test("doAllocate", doAllocate);
}
No meu Windows 7 i7, o Python de 64 bits oferece
Elements: 100000, Iterations: 144
doAppend: 3587.204933ms
doAllocate: 2701.154947ms
doGenerator: 1721.098185ms
Enquanto o C ++ fornece (construído com MSVC, 64 bits, otimizações ativadas)
Elements: 100000, Iterations: 144
doAppend: 74.0042ms
doReserve: 27.0015ms
doAllocate: 5.0003ms
A compilação de depuração do C ++ produz:
Elements: 100000, Iterations: 144
doAppend: 2166.12ms
doReserve: 2082.12ms
doAllocate: 273.016ms
O ponto aqui é que, com o Python, você pode obter uma melhoria de desempenho de 7 a 8%; se você acha que está escrevendo um aplicativo de alto desempenho (ou se está escrevendo algo que é usado em um serviço da Web ou algo assim), então isso não deve ser desprezado, mas pode ser necessário repensar sua escolha de idioma.
Além disso, o código Python aqui não é realmente um código Python. Mudar para o código verdadeiramente Pythonesque aqui oferece melhor desempenho:
import time
class Timer(object):
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *args):
end = time.time()
secs = end - self.start
msecs = secs * 1000 # millisecs
print('%fms' % msecs)
Elements = 100000
Iterations = 144
print('Elements: %d, Iterations: %d' % (Elements, Iterations))
def doAppend():
for x in range(Iterations):
result = []
for i in range(Elements):
result.append(i)
def doAllocate():
for x in range(Iterations):
result = [None] * Elements
for i in range(Elements):
result[i] = i
def doGenerator():
for x in range(Iterations):
result = list(i for i in range(Elements))
def test(name, fn):
print("%s: " % name, end="")
with Timer() as t:
fn()
test('doAppend', doAppend)
test('doAllocate', doAllocate)
test('doGenerator', doGenerator)
Que dá
Elements: 100000, Iterations: 144
doAppend: 2153.122902ms
doAllocate: 1346.076965ms
doGenerator: 1614.092112ms
(no doGenerator de 32 bits é melhor que doAllAllate).
Aqui, a diferença entre doAppend e doAllocate é significativamente maior.
Obviamente, as diferenças aqui realmente se aplicam apenas se você estiver fazendo isso mais do que algumas vezes ou se estiver fazendo isso em um sistema muito carregado, em que esses números serão redimensionados por ordens de magnitude ou se você estiver lidando com listas consideravelmente maiores.
O ponto aqui: faça da maneira pitônica o melhor desempenho.
Mas se você está preocupado com o desempenho geral de alto nível, o Python é a linguagem errada. O problema mais fundamental é que as chamadas de função do Python tradicionalmente são até 300 vezes mais lentas que outras linguagens devido a recursos do Python, como decoradores etc. ( https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Data_Aggregation#Data_Aggregation ).