La manière pythonique pour cela est:
x = [None] * numElements
ou quelle que soit la valeur par défaut avec laquelle vous souhaitez pré-remplir, par exemple
bottles = [Beer()] * 99
sea = [Fish()] * many
vegetarianPizzas = [None] * peopleOrderingPizzaNotQuiche
[EDIT: Caveat Emptor La [Beer()] * 99
syntaxe en crée un Beer
, puis remplit un tableau avec 99 références à la même instance]
L'approche par défaut de Python peut être assez efficace, bien que cette efficacité diminue à mesure que vous augmentez le nombre d'éléments.
Comparer
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)
avec
#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);
}
Sur mon Windows 7 i7, Python 64 bits donne
Elements: 100000, Iterations: 144
doAppend: 3587.204933ms
doAllocate: 2701.154947ms
doGenerator: 1721.098185ms
Alors que C ++ donne (construit avec MSVC, 64 bits, optimisations activées)
Elements: 100000, Iterations: 144
doAppend: 74.0042ms
doReserve: 27.0015ms
doAllocate: 5.0003ms
La génération de débogage C ++ produit:
Elements: 100000, Iterations: 144
doAppend: 2166.12ms
doReserve: 2082.12ms
doAllocate: 273.016ms
Le point ici est qu'avec Python, vous pouvez obtenir une amélioration des performances de 7 à 8%, et si vous pensez que vous écrivez une application haute performance (ou si vous écrivez quelque chose qui est utilisé dans un service Web ou autre), alors ce n'est pas à renifler, mais vous devrez peut-être repenser votre choix de langue.
De plus, le code Python ici n'est pas vraiment du code Python. Le passage au code vraiment Pythonesque donne ici de meilleures performances:
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)
Qui donne
Elements: 100000, Iterations: 144
doAppend: 2153.122902ms
doAllocate: 1346.076965ms
doGenerator: 1614.092112ms
(en 32 bits, doGenerator fait mieux que doAllocate).
Ici, l'écart entre doAppend et doAllocate est nettement plus grand.
De toute évidence, les différences ici ne s'appliquent vraiment que si vous faites cela plus d'une poignée de fois ou si vous le faites sur un système très chargé où ces nombres vont être réduits par ordre de grandeur, ou si vous avez affaire à listes considérablement plus grandes.
Le point ici: faites-le de manière pythonique pour la meilleure performance.
Mais si vous vous inquiétez des performances générales de haut niveau, Python n'est pas le bon langage. Le problème le plus fondamental est que les appels de fonction Python ont traditionnellement été jusqu'à 300 fois plus lents que les autres langages en raison des fonctionnalités Python telles que les décorateurs, etc. ( https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Data_Aggregation#Data_Aggregation ).