La forma pitónica para esto es:
x = [None] * numElements
o cualquier valor predeterminado con el que desee completar, p. ej.
bottles = [Beer()] * 99
sea = [Fish()] * many
vegetarianPizzas = [None] * peopleOrderingPizzaNotQuiche
[EDITAR: Caveat Emptor La [Beer()] * 99
sintaxis crea uno Beer
y luego llena una matriz con 99 referencias a la misma instancia]
El enfoque predeterminado de Python puede ser bastante eficiente, aunque esa eficiencia disminuye a medida que aumenta el 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)
con
#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);
}
En mi Windows 7 i7, Python de 64 bits da
Elements: 100000, Iterations: 144
doAppend: 3587.204933ms
doAllocate: 2701.154947ms
doGenerator: 1721.098185ms
Mientras que C ++ da (construido con MSVC, 64 bits, optimizaciones habilitadas)
Elements: 100000, Iterations: 144
doAppend: 74.0042ms
doReserve: 27.0015ms
doAllocate: 5.0003ms
La compilación de depuración de C ++ produce:
Elements: 100000, Iterations: 144
doAppend: 2166.12ms
doReserve: 2082.12ms
doAllocate: 273.016ms
El punto aquí es que con Python puede lograr una mejora del rendimiento del 7-8%, y si cree que está escribiendo una aplicación de alto rendimiento (o si está escribiendo algo que se utiliza en un servicio web o algo así) eso no debe ser rastreado, pero es posible que deba repensar su elección de idioma.
Además, el código de Python aquí no es realmente el código de Python. Cambiar a código verdaderamente Pythonesque aquí ofrece un mejor rendimiento:
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)
Lo que da
Elements: 100000, Iterations: 144
doAppend: 2153.122902ms
doAllocate: 1346.076965ms
doGenerator: 1614.092112ms
(en 32 bits doGenerator funciona mejor que doAllocate).
Aquí la brecha entre doAppend y doAllocate es significativamente mayor.
Obviamente, las diferencias aquí solo se aplican si está haciendo esto más de un puñado de veces o si está haciendo esto en un sistema muy cargado donde esos números van a ser escalados por órdenes de magnitud, o si está tratando con Listas considerablemente más grandes.
El punto aquí: hágalo de la manera pitónica para obtener el mejor rendimiento.
Pero si le preocupa el rendimiento general de alto nivel, Python es el lenguaje incorrecto. El problema más fundamental es que las llamadas a funciones de Python han sido tradicionalmente hasta 300 veces más lentas que otros lenguajes debido a características de Python como decoradores, etc. ( https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Data_Aggregation#Data_Aggregation ).