La forma Pythonic de esto es:
x = [None] * numElements
o cualquier valor predeterminado que deseen prepop con, por ejemplo,
bottles = [Beer()] * 99
sea = [Fish()] * many
vegetarianPizzas = [None] * peopleOrderingPizzaNotQuiche
enfoque por defecto de Python puede ser bastante eficiente, aunque que la eficiencia decae a medida que aumenta el número de elementos.
Compare
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 i7 Windows 7, 64 bits de Python da
Elements: 100000, Iterations: 144
doAppend: 3587.204933ms
doAllocate: 2701.154947ms
doGenerator: 1721.098185ms
Aunque C++ da (construido con MSVC, de 64 bits, Optimizaciones habilitadas)
Elements: 100000, Iterations: 144
doAppend: 74.0042ms
doReserve: 27.0015ms
doAllocate: 5.0003ms
C++ versión de depuración produce:
Elements: 100000, Iterations: 144
doAppend: 2166.12ms
doReserve: 2082.12ms
doAllocate: 273.016ms
El punto aquí es que con Python se puede lograr una mejora del rendimiento del 7-8%, y si usted piensa que usted está escribiendo una aplicación de alto rendimiento (o si está escribiendo algo que se usa en un servicio web o algo así), entonces eso no se debe olfatear, pero es posible que deba reconsiderar su elección de idioma.
Además, el código de Python aquí no es realmente el código de Python.El cambio a código verdaderamente Pythonesque aquí da 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)
que da
Elements: 100000, Iterations: 144
doAppend: 2153.122902ms
doAllocate: 1346.076965ms
doGenerator: 1614.092112ms
(en 32 bits doGenerator hace mejor que doAllocate).
Aquí la brecha entre doAppend y doAllocate es significativamente mayor.
Obviamente, las diferencias aquí realmente solo se aplican si lo hace más de una vez o si lo hace en un sistema muy cargado en el que esos números se ampliarán por órdenes de magnitud, o si estás tratando con listas considerablemente más grandes.
El punto aquí: hazlo de manera pitónica para obtener el mejor rendimiento.
Pero si te preocupa el rendimiento general de alto nivel, Python es el idioma equivocado. El problema más fundamental es que las llamadas a función de Python han sido tradicionalmente hasta 300 veces más lentas que otros lenguajes debido a las características de Python como decoradores, etc. (https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Data_Aggregation#Data_Aggregation).
Por lo que yo sé, son similares a ArrayLists en que el doble de su tamaño cada vez. El tiempo amortizado de esta operación es constante. No es tan grande como un golpe de rendimiento como se podría pensar. – mmcdole
parece que tienes razón! – Claudiu
Quizás la preinicialización no sea estrictamente necesaria para el escenario del PO, pero a veces es definitivamente necesario: tengo varios elementos preindicados que deben insertarse en un índice específico, pero salen de orden. Necesito hacer crecer la lista antes de tiempo para evitar IndexErrors. Gracias por esta pregunta –