Qual è il modo preferito per concatenare una stringa in Python?


358

Dato che Python stringnon può essere modificato, mi chiedevo come concatenare una stringa in modo più efficiente?

Posso scrivere così:

s += stringfromelsewhere

o in questo modo:

s = []
s.append(somestring)

later

s = ''.join(s)

Mentre scrivevo questa domanda, ho trovato un buon articolo che parlava dell'argomento.

http://www.skymind.com/~ocrow/python_string/

Ma è in Python 2.x., quindi la domanda sarebbe: qualcosa è cambiato in Python 3?


Risposte:


433

Il modo migliore di aggiungere una stringa a una variabile stringa è usare +o +=. Questo perché è leggibile e veloce. Sono anche altrettanto veloci, quello che scegli è una questione di gusti, l'ultimo è il più comune. Ecco i tempi con il timeitmodulo:

a = a + b:
0.11338996887207031
a += b:
0.11040496826171875

Tuttavia, coloro che consigliano di avere elenchi e aggiungerli e quindi unirli a tali elenchi, lo fanno perché l'aggiunta di una stringa a un elenco è presumibilmente molto veloce rispetto all'estensione di una stringa. E questo può essere vero, in alcuni casi. Ecco, ad esempio, un milione di accodamenti di una stringa di un carattere, prima a una stringa, quindi a un elenco:

a += b:
0.10780501365661621
a.append(b):
0.1123361587524414

OK, risulta che anche quando la stringa risultante è lunga un milione di caratteri, l'aggiunta è stata ancora più veloce.

Ora proviamo ad aggiungere una stringa lunga di mille caratteri centomila volte:

a += b:
0.41823482513427734
a.append(b):
0.010656118392944336

La stringa di fine, quindi, finisce per essere lunga circa 100 MB. È stato piuttosto lento, l'aggiunta di un elenco è stata molto più veloce. Che quel tempismo non includa il finale a.join(). Quindi quanto ci vorrebbe?

a.join(a):
0.43739795684814453

Oups. Risulta anche in questo caso, aggiungere / unire è più lento.

Da dove viene questa raccomandazione? Python 2?

a += b:
0.165287017822
a.append(b):
0.0132720470428
a.join(a):
0.114929914474

Bene, aggiungere / unire è marginalmente più veloce lì se stai usando stringhe estremamente lunghe (cosa che di solito non sei, cosa avresti una stringa che ha 100 MB in memoria?)

Ma il vero copertoncino è Python 2.3. Dove non ti mostrerò nemmeno i tempi, perché è così lento che non è ancora finito. Questi test all'improvviso richiedono pochi minuti . Tranne l'appendice / join, che è veloce come in seguito Pythons.

Sì. La concatenazione di stringhe era molto lenta in Python all'età della pietra. Ma su 2.4 non è più (o almeno Python 2.4.7), quindi la raccomandazione di utilizzare append / join è diventata obsoleta nel 2008, quando Python 2.3 ha smesso di essere aggiornato e avresti dovuto smettere di usarlo. :-)

(Aggiornamento: Risulta quando ho eseguito i test con più attenzione rispetto all'uso +ed +=è più veloce anche per due stringhe su Python 2.3. La raccomandazione da usare ''.join()deve essere un malinteso)

Tuttavia, questo è CPython. Altre implementazioni possono avere altre preoccupazioni. E questo è solo un altro motivo per cui l'ottimizzazione prematura è la radice di tutti i mali. Non utilizzare una tecnica che si suppone "più veloce" a meno che non la si misuri per prima.

Pertanto la versione "migliore" per eseguire la concatenazione di stringhe è utilizzare + o + = . E se questo risulta essere lento per te, il che è abbastanza improbabile, allora fai qualcos'altro.

Quindi perché uso molto append / join nel mio codice? Perché a volte è in realtà più chiaro. Soprattutto quando tutto ciò che dovresti concatenare insieme dovrebbe essere separato da spazi o virgole o newline.


10
Se hai più stringhe (n> 10) "" .join (list_of_strings) è ancora più veloce
Mikko Ohtamaa,

11
il motivo per cui + = è veloce è che c'è un hack delle prestazioni in cpython se il refcount è 1 - cade a pezzi praticamente su tutte le altre implementazioni di python (ad eccezione di una build pypy configurata piuttosto speciale)
Ronny

17
Perché questo è stato votato così tanto? Come è meglio usare un algoritmo che è efficiente solo su una specifica implementazione e che ha essenzialmente un fragile hack per riparare un algoritmo quadratico? Inoltre fraintendi completamente il punto "l'ottimizzazione prematura è la radice di tutti i mali". Quella citazione parla di piccole ottimizzazioni. Sta andando da O (n ^ 2) a O (n) che NON è una piccola ottimizzazione.
Wes,

12
Ecco la citazione attuale: "Dovremmo dimenticare piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali. Eppure non dovremmo rinunciare alle nostre opportunità in quel 3% critico. Un buon programmatore non lo farà essere cullato dall'accompagnamento da tale ragionamento, sarà saggio esaminare attentamente il codice critico, ma solo dopo che questo codice sarà stato identificato "
Wes,

2
Nessuno sta dicendo che a + b sia lento. È quadratico quando stai facendo a = a + b più di una volta. a + b + c non è lento, ripeto non lento poiché deve attraversare una sola volta ogni stringa, mentre deve attraversare più volte le stringhe precedenti con l'approccio a = a + b (supponendo che sia in un ciclo di qualche tipo). Ricorda che le stringhe sono immutabili.
Wes,

52

Se stai concatenando molti valori, nessuno dei due. L'aggiunta di un elenco è costosa. Puoi usare StringIO per questo. Soprattutto se lo stai costruendo in molte operazioni.

from cStringIO import StringIO
# python3:  from io import StringIO

buf = StringIO()

buf.write('foo')
buf.write('foo')
buf.write('foo')

buf.getvalue()
# 'foofoofoo'

Se hai già un elenco completo che ti è stato restituito da qualche altra operazione, usa semplicemente il ''.join(aList)

Dalle FAQ di Python: qual è il modo più efficiente per concatenare molte stringhe insieme?

Gli oggetti str e byte sono immutabili, quindi concatenare molte stringhe insieme è inefficace poiché ogni concatenazione crea un nuovo oggetto. Nel caso generale, il costo totale di runtime è quadratico nella lunghezza totale della stringa.

Per accumulare molti oggetti str, l'idioma raccomandato è di metterli in un elenco e chiamare str.join () alla fine:

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(un altro idioma ragionevolmente efficiente è usare io.StringIO)

Per accumulare molti oggetti byte, il linguaggio consigliato è estendere un oggetto bytearray usando la concatenazione sul posto (l'operatore + =):

result = bytearray()
for b in my_bytes_objects:
    result += b

Modifica: ero sciocco e avevo i risultati incollati all'indietro, facendomi sembrare che accodare un elenco fosse più veloce di cStringIO. Ho anche aggiunto test per bytearray / str concat, nonché un secondo round di test usando un elenco più grande con stringhe più grandi. (python 2.7.3)

esempio di test ipython per grandi elenchi di stringhe

try:
    from cStringIO import StringIO
except:
    from io import StringIO

source = ['foo']*1000

%%timeit buf = StringIO()
for i in source:
    buf.write(i)
final = buf.getvalue()
# 1000 loops, best of 3: 1.27 ms per loop

%%timeit out = []
for i in source:
    out.append(i)
final = ''.join(out)
# 1000 loops, best of 3: 9.89 ms per loop

%%timeit out = bytearray()
for i in source:
    out += i
# 10000 loops, best of 3: 98.5 µs per loop

%%timeit out = ""
for i in source:
    out += i
# 10000 loops, best of 3: 161 µs per loop

## Repeat the tests with a larger list, containing
## strings that are bigger than the small string caching 
## done by the Python
source = ['foo']*1000

# cStringIO
# 10 loops, best of 3: 19.2 ms per loop

# list append and join
# 100 loops, best of 3: 144 ms per loop

# bytearray() +=
# 100 loops, best of 3: 3.8 ms per loop

# str() +=
# 100 loops, best of 3: 5.11 ms per loop

2
cStringIOnon esiste in Py3. Usa io.StringIOinvece.
PVC

2
Per quanto riguarda il motivo per cui aggiungere ripetutamente una stringa può essere costoso: joelonsoftware.com/articles/fog0000000319.html
Wes

36

In Python> = 3.6, la nuova f-stringa è un modo efficiente per concatenare una stringa.

>>> name = 'some_name'
>>> number = 123
>>>
>>> f'Name is {name} and the number is {number}.'
'Name is some_name and the number is 123.'

8

Il metodo raccomandato è ancora quello di utilizzare append e join.


1
Come vedi dalla mia risposta, questo dipende da quante stringhe stai concatenando. Ho fatto un po 'di tempo su questo (vedi il discorso a cui mi sono collegato nei miei commenti sulla mia risposta) e generalmente a meno che non sia più di dieci, usa +.
Lennart Regebro,

1
PEP8 menziona questo ( python.org/dev/peps/pep-0008/#programming-recommendations ). La logica è che mentre CPython ha delle ottimizzazioni speciali per la concatenazione di stringhe con + =, altre implementazioni potrebbero non esserlo.
Quantum7

8

Se le stringhe che stai concatenando sono letterali, usa la concatenazione letterale di String

re.compile(
        "[A-Za-z_]"       # letter or underscore
        "[A-Za-z0-9_]*"   # letter, digit or underscore
    )

Ciò è utile se si desidera commentare parte di una stringa (come sopra) o se si desidera utilizzare stringhe non elaborate o virgolette triple per parte di un valore letterale ma non tutto.

Poiché ciò accade a livello di sintassi, utilizza operatori di concatenazione zero.


7

Scrivi questa funzione

def str_join(*args):
    return ''.join(map(str, args))

Quindi puoi chiamare semplicemente dove vuoi

str_join('Pine')  # Returns : Pine
str_join('Pine', 'apple')  # Returns : Pineapple
str_join('Pine', 'apple', 3)  # Returns : Pineapple3

1
str_join = lambda *str_list: ''.join(s for s in str_list)
Rick supporta Monica il

7

L'utilizzo della concatenazione di stringhe in atto con '+' è il peggior metodo di concatenazione in termini di stabilità e implementazione incrociata poiché non supporta tutti i valori. Lo standard PEP8 lo scoraggia e incoraggia l'uso di format (), join () e append () per un uso a lungo termine.

Come riportato nella sezione "Suggerimenti per la programmazione" collegata:

Ad esempio, non fare affidamento sull'implementazione efficiente di CPython della concatenazione di stringhe sul posto per le istruzioni nella forma a + = b o a = a + b. Questa ottimizzazione è fragile anche in CPython (funziona solo per alcuni tipi) e non è presente affatto nelle implementazioni che non usano il conteggio. In parti della libreria sensibili alle prestazioni, è invece necessario utilizzare il modulo '' .join (). Ciò garantirà che la concatenazione si verifichi in tempo lineare attraverso varie implementazioni.


5
Il link di riferimento sarebbe stato bello :)

6

Mentre un po 'datato, codice come un Pythonista: Idiomatic Python raccomanda join()sopra + in questa sezione . Come fa PythonSpeedPerformanceTips nella sua sezione sulla concatenazione di stringhe , con la seguente dichiarazione di non responsabilità:

L'accuratezza di questa sezione è contestata rispetto alle versioni successive di Python. In CPython 2.5, la concatenazione di stringhe è abbastanza veloce, sebbene ciò non si applichi allo stesso modo ad altre implementazioni di Python. Vedi ConcatenationTestCode per una discussione.


6

Come menziona @jdi la documentazione di Python suggerisce di usare str.joino io.StringIOper concatenare le stringhe. E dice che uno sviluppatore dovrebbe aspettarsi un tempo quadratico da +=in un ciclo, anche se c'è un'ottimizzazione da Python 2.4. Come dice questa risposta:

Se Python rileva che l'argomento sinistro non ha altri riferimenti, chiama reallocper tentare di evitare una copia ridimensionando la stringa in posizione. Questo non è qualcosa su cui dovresti mai fare affidamento, perché è un dettaglio di implementazione e perché se reallocfinisce per dover spostare frequentemente la stringa, le prestazioni diminuiscono comunque in O (n ^ 2).

Mostrerò un esempio di codice del mondo reale che si basava ingenuamente su +=questa ottimizzazione, ma non si applicava. Il codice seguente converte un iterabile di stringhe brevi in ​​blocchi più grandi da utilizzare in un'API di massa.

def test_concat_chunk(seq, split_by):
    result = ['']
    for item in seq:
        if len(result[-1]) + len(item) > split_by: 
            result.append('')
        result[-1] += item
    return result

Questo codice può funzionare letteralmente per ore a causa della complessità temporale quadratica. Di seguito sono riportate alternative con le strutture dati suggerite:

import io

def test_stringio_chunk(seq, split_by):
    def chunk():
        buf = io.StringIO()
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                size += buf.write(item)
            else:
                yield buf.getvalue()
                buf = io.StringIO()
                size = buf.write(item)
        if size:
            yield buf.getvalue()

    return list(chunk())

def test_join_chunk(seq, split_by):
    def chunk():
        buf = []
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                buf.append(item)
                size += len(item)
            else:
                yield ''.join(buf)                
                buf.clear()
                buf.append(item)
                size = len(item)
        if size:
            yield ''.join(buf)

    return list(chunk())

E un micro-benchmark:

import timeit
import random
import string
import matplotlib.pyplot as plt

line = ''.join(random.choices(
    string.ascii_uppercase + string.digits, k=512)) + '\n'
x = []
y_concat = []
y_stringio = []
y_join = []
n = 5
for i in range(1, 11):
    x.append(i)
    seq = [line] * (20 * 2 ** 20 // len(line))
    chunk_size = i * 2 ** 20
    y_concat.append(
        timeit.timeit(lambda: test_concat_chunk(seq, chunk_size), number=n) / n)
    y_stringio.append(
        timeit.timeit(lambda: test_stringio_chunk(seq, chunk_size), number=n) / n)
    y_join.append(
        timeit.timeit(lambda: test_join_chunk(seq, chunk_size), number=n) / n)
plt.plot(x, y_concat)
plt.plot(x, y_stringio)
plt.plot(x, y_join)
plt.legend(['concat', 'stringio', 'join'], loc='upper left')
plt.show()

micro-benchmark


5

Puoi farlo in diversi modi.

str1 = "Hello"
str2 = "World"
str_list = ['Hello', 'World']
str_dict = {'str1': 'Hello', 'str2': 'World'}

# Concatenating With the + Operator
print(str1 + ' ' + str2)  # Hello World

# String Formatting with the % Operator
print("%s %s" % (str1, str2))  # Hello World

# String Formatting with the { } Operators with str.format()
print("{}{}".format(str1, str2))  # Hello World
print("{0}{1}".format(str1, str2))  # Hello World
print("{str1} {str2}".format(str1=str_dict['str1'], str2=str_dict['str2']))  # Hello World
print("{str1} {str2}".format(**str_dict))  # Hello World

# Going From a List to a String in Python With .join()
print(' '.join(str_list))  # Hello World

# Python f'strings --> 3.6 onwards
print(f"{str1} {str2}")  # Hello World

Ho creato questo piccolo riassunto attraverso i seguenti articoli.


3

il mio caso d'uso era leggermente diverso. Ho dovuto costruire una query in cui più di 20 campi erano dinamici. Ho seguito questo approccio all'utilizzo del metodo di formattazione

query = "insert into {0}({1},{2},{3}) values({4}, {5}, {6})"
query.format('users','name','age','dna','suzan',1010,'nda')

questo è stato relativamente più semplice per me invece di usare + o altri modi


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.