Python: costruire stringhe, `+=` nel ciclo, `join()` e f-string a confronto
Le stringhe in Python sono immutabili: ogni concatenazione produce un oggetto nuovo. Per questo si ripete che += in un ciclo è lento. È vero solo in parte, vediamo i numeri.
Molti pezzi: += contro join()
parti = [str(i) for i in range(20_000)]
def plus():
s = ""
for p in parti:
s += p + ","
return s
def join():
return ",".join(parti) + ","
Misure (Python 3.12.15, migliore di 5 serie, 20.000 pezzi):
+=nel ciclo: circa 2,1 ms",".join(parti): circa 0,34 msappendsu lista e poi"".join: circa 1,3 ms"".joincon generatore di f-string: circa 1,9 ms
join() è circa 6 volte più veloce. Nota: CPython ottimizza s += x quando la stringa ha un solo riferimento, quindi la differenza non è quadratica come in teoria; ma l'ottimizzazione non è garantita in altre implementazioni (PyPy, ad esempio) e salta se la stringa è referenziata altrove. join() è sicuro ovunque.
Il generatore dentro join non è gratis: se hai già una lista di stringhe, passa quella; se devi trasformare ogni elemento, la differenza tra lista e generatore è piccola.
Pochi pezzi: f-string, format, %
Per comporre una stringa corta le differenze sono nell'ordine dei nanosecondi (500.000 ripetizioni, 3 valori):
f"{a}-{b}-{c}": circa 468 ns"{}-{}-{}".format(...): circa 527 ns"%s-%s-%s" % (...): circa 409 nsa + "-" + str(b) + "-" + str(c): circa 468 ns
Sorpresa: % è la più veloce in questo test. Ma stiamo parlando di decimi di microsecondo: non è un motivo per scegliere. Usa le f-string per leggibilità; resta su % solo nel logging (logger.info("x=%s", x)), dove la formattazione viene rimandata se il livello è disattivato.
Regola pratica
- Costruisci un testo da molti pezzi (righe di un CSV, un report): raccogli in una lista e fai
"".join(...). - Componi una riga con pochi valori: f-string.
- Non ottimizzare le stringhe corte: misura prima.
Lo script di verifica è in verifica/stringhe_join.py. I tempi dipendono dalla macchina, conta il rapporto.