2012-04-06 7 views
95

Un antipattern comune in Python consiste nel concatenare una sequenza di stringhe utilizzando + in un ciclo. Questo è male perché l'interprete Python deve creare un nuovo oggetto stringa per ogni iterazione e finisce per prendere tempo quadratico. (Le versioni recenti di CPython possono apparentemente ottimizzarlo in alcuni casi, ma altre implementazioni non possono, quindi i programmatori sono scoraggiati dal basarsi su questo.) ''.join è il modo giusto per farlo.Qualsiasi motivo per non utilizzare '+' per concatenare due stringhe?

Tuttavia, ho sentito dire (including here on Stack Overflow) che si dovrebbe mai, mai uso + per concatenazione di stringhe, ma invece utilizzare sempre ''.join o una stringa di formato. Non capisco perché questo è il caso se si concatenano solo due stringhe. Se la mia comprensione è corretta, non dovrebbe richiedere un tempo quadratico e penso che a + b sia più pulito e più leggibile rispetto a ''.join((a, b)) o '%s%s' % (a, b).

È buona norma utilizzare + per concatenare due stringhe? O c'è un problema di cui non sono a conoscenza?

+0

Il suo più ordinato e si ha un maggiore controllo per non fare la concatenazione. Ma la sua battuta di corda è leggermente più lenta: P –

+0

Stai dicendo che '+' è più veloce o più lento? E perché? – Taymon

+1

+ è più veloce, 'In [2]:% timeit "a" * 80 + "b" * 80' ' 1000000 loop, migliore di 3: 356 ns per loop' 'In [3]:% timeit "% s% s"% ("a" * 80, "b" * 80) ' ' 1000000 cicli, meglio di 3: 907 ns per ciclo' –

risposta

83

Non c'è niente di sbagliato nel concatenare due stringhe con +. Infatti è più facile da leggere rispetto a ''.join([a, b]).

Hai ragione anche se concatenare più di 2 stringhe con + è un'operazione O (n^2) (rispetto a O (n) per join) e diventa quindi inefficiente. Tuttavia questo non ha a che fare con l'uso di un ciclo. Anche a + b + c + ... è O (n^2), la ragione è che ogni concatenazione produce una nuova stringa.

CPython2.4 e versioni successive tentano di attenuarlo, ma è comunque consigliabile utilizzare join quando si concatenano più di 2 stringhe.

+4

vuoi dire '' .join ((a, b)) giusto? – Mutant

+2

@Mutant: '.join' prende un iterabile, quindi entrambi' .join ([a, b]) 'e' .join ((a, b)) 'sono validi. – foundling

+0

Gli intervalli interessanti suggeriscono l'uso di '+' o '+ =' nella risposta accettata (dal 2013) su http://stackoverflow.com/a/12171382/378826 (di Lennart Regebro) anche per CPython 2.3+ e per scegliere il modello "aggiungi/aggiungi" se questo chiaro espone l'idea della soluzione del problema. – Dilettant

42

L'operatore Plus è la soluzione perfetta per concatenare due stringhe di Python. Ma se continui ad aggiungere più di due stringhe (n> 25), potresti voler pensare ad altro.

''.join([a, b, c]) il trucco è un ottimizzazione delle prestazioni.

+2

Non sarebbe una tupla essere meglio di una lista? – ThiefMaster

+5

Tuple sarebbe più veloce - il codice era solo un esempio :) Di solito lunghi input di stringhe multiple sono dinamici. –

+0

@Mikko Ohtamaa: le stringhe dinamiche possono essere inserite in tuple. – martineau

5

L'assunto che non si dovrebbe mai, mai usare + per la concatenazione di stringhe, ma invece usare sempre '' .join può essere un mito. È vero che l'utilizzo di + crea copie temporanee non necessarie di oggetto stringa immutabile, ma l'altro fatto non frequentemente citato è che chiamare join in un ciclo generalmente aggiungerebbe il sovraccarico di function call. Prendiamo il tuo esempio.

Creare due liste, una dal legata domanda SO e un altro una più grande fabbricato

>>> myl1 = ['A','B','C','D','E','F'] 
>>> myl2=[chr(random.randint(65,90)) for i in range(0,10000)] 

Creiamo due funzioni, UseJoin e UsePlus di utilizzare la rispettiva funzionalità join e +.

>>> def UsePlus(): 
    return [myl[i] + myl[i + 1] for i in range(0,len(myl), 2)] 

>>> def UseJoin(): 
    [''.join((myl[i],myl[i + 1])) for i in range(0,len(myl), 2)] 

Consente di eseguire timeit con il primo elenco

>>> myl=myl1 
>>> t1=timeit.Timer("UsePlus()","from __main__ import UsePlus") 
>>> t2=timeit.Timer("UseJoin()","from __main__ import UseJoin") 
>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=100000)/100000) 
2.48 usec/pass 
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=100000)/100000) 
2.61 usec/pass 
>>> 

Hanno quasi la stessa fase di esecuzione.

Consente di utilizzare Cprofile

>>> myl=myl2 
>>> cProfile.run("UsePlus()") 
     5 function calls in 0.001 CPU seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.001 0.001 0.001 0.001 <pyshell#1376>:1(UsePlus) 
     1 0.000 0.000 0.001 0.001 <string>:1(<module>) 
     1 0.000 0.000 0.000 0.000 {len} 
     1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 
     1 0.000 0.000 0.000 0.000 {range} 


>>> cProfile.run("UseJoin()") 
     5005 function calls in 0.029 CPU seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.015 0.015 0.029 0.029 <pyshell#1388>:1(UseJoin) 
     1 0.000 0.000 0.029 0.029 <string>:1(<module>) 
     1 0.000 0.000 0.000 0.000 {len} 
     1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 
    5000 0.014 0.000 0.014 0.000 {method 'join' of 'str' objects} 
     1 0.000 0.000 0.000 0.000 {range} 

e sembra che l'uso di aderire, si traduce in chiamate di funzione non necessarie che potrebbero aggiungere alla testa.

Ora tornando alla domanda. Si dovrebbe scoraggiare l'uso di + su join in tutti i casi?

Credo di no, le cose dovrebbero essere prese in considerazione

  1. lunghezza della stringa in questione
  2. No di concatenazione di funzionamento.

E fuori rotta in uno sviluppo l'ottimizzazione pre-matura è malvagia.

+7

Ovviamente, l'idea sarebbe di non usare 'join' all'interno del ciclo stesso - piuttosto il ciclo genererebbe una sequenza che sarebbe passata per unirsi. – jsbueno

2

Ho fatto un test rapido:

import sys 

str = e = "a xxxxxxxxxx very xxxxxxxxxx long xxxxxxxxxx string xxxxxxxxxx\n" 

for i in range(int(sys.argv[1])): 
    str = str + e 

e contati:

[email protected]:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py 8000000 
8000000 times 

real 0m2.165s 
user 0m1.620s 
sys  0m0.540s 
[email protected]:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py 16000000 
16000000 times 

real 0m4.360s 
user 0m3.480s 
sys  0m0.870s 

C'è evidentemente un'ottimizzazione per il caso a = a + b. Non mostra il tempo O (n^2) come si potrebbe sospettare.

Quindi, almeno in termini di prestazioni, utilizzando + va bene.

+2

Qui puoi confrontare il caso "join". E c'è la questione di altre implementazioni Python, come pypy, jython, ironpython, ecc ... – jsbueno

6

Quando si lavora con più persone, a volte è difficile sapere esattamente cosa sta succedendo. Utilizzando una stringa di formato, invece di concatenazione può evitare un particolare fastidio che è successo una tonnellata intera di volte per noi:

Say, una funzione richiede un argomento, e si scrive in attesa di ottenere una stringa:

In [1]: def foo(zeta): 
    ...:  print 'bar: ' + zeta 

In [2]: foo('bang') 
bar: bang 

Quindi, questa funzione può essere utilizzata abbastanza spesso in tutto il codice. I tuoi colleghi potrebbero sapere esattamente che cosa fa, ma non necessariamente essere completamente up-to-speed sugli interni e potrebbero non sapere che la funzione si aspetta una stringa. E così può finire con questo:

In [3]: foo(23) 
--------------------------------------------------------------------------- 
TypeError         Traceback (most recent call last) 

/home/izkata/<ipython console> in <module>() 

/home/izkata/<ipython console> in foo(zeta) 

TypeError: cannot concatenate 'str' and 'int' objects 

Non ci sarebbe alcun problema se hai appena usato una stringa di formato:

In [1]: def foo(zeta): 
    ...:  print 'bar: %s' % zeta 
    ...:  
    ...:  

In [2]: foo('bang') 
bar: bang 

In [3]: foo(23) 
bar: 23 

Lo stesso vale per tutti i tipi di oggetti che definiscono __str__, che può essere passato in pure:

In [1]: from datetime import date 

In [2]: zeta = date(2012, 4, 15) 

In [3]: print 'bar: ' + zeta 
--------------------------------------------------------------------------- 
TypeError         Traceback (most recent call last) 

/home/izkata/<ipython console> in <module>() 

TypeError: cannot concatenate 'str' and 'datetime.date' objects 

In [4]: print 'bar: %s' % zeta 
bar: 2012-04-15 

Quindi sì: se è possibile utilizzare una stringa di formato farlo e approfittare di ciò che Python deve offrire.

+1

+1 per un'opinione dissidente ben motivata. Continuo a pensare che preferisco '+' però. – Taymon

+0

Perché non dovresti semplicemente definire il metodo foo come: print 'bar:' + str (zeta)? – EngineerWithJava54321

+0

@ EngineerWithJava54321 Per un esempio, 'zeta = u" a \ xac \ u1234 \ u20ac \ U00008000 "' - quindi dovresti usare la barra 'print': '+ unicode (zeta) 'per assicurarti che non sia un errore . '% s' ha ragione senza pensarci, ed è molto più corto – Izkata

-3

'' .join ([a, b]) è la soluzione migliore rispetto +.

perché il codice deve essere scritto in un modo che non svantaggiare altre implementazioni di Python (PyPy, Jython, IronPython, Cython, Psyco, e simili)

forma a + = b oppure a = a + b è fragile anche in CPython e non è affatto presente nelle implementazioni che non usanoconteggio(il conteggio dei riferimenti è una tecnica di memorizzazione del numero di riferimenti, puntatori o maniglie su una risorsa come un oggetto, blocco di memoria, spazio su disco o altra risorsa)

https://www.python.org/dev/peps/pep-0008/#programming-recommendations

+0

'a + = b' funziona in tutte le implementazioni di Python, è solo che su alcuni di essi ci vuole un tempo quadratico _quando eseguito all'interno di un loop_; la domanda riguardava la concatenazione delle stringhe al di fuori di un ciclo. – Taymon

2

In base ai documenti Python, l'uso di str.join() fornisce la coerenza delle prestazioni tra varie implementazioni di Python. Sebbene CPython ottimizzi il comportamento quadratico di s = s + t, altre implementazioni di Python potrebbero non esserlo.

CPython dettaglio implementativo: Se s e t sono entrambi stringhe, alcuni implementazioni Python come CPython solito può eseguire un ottimizzazione sul posto per le assegnazioni di modulo s = s + t o s + = t. Quando applicabile a , questa ottimizzazione rende probabile un tempo di esecuzione quadratico molto inferiore a . Questa ottimizzazione è dipendente dalla versione e dall'implementazione . Per il codice sensibile alle prestazioni, è preferibile utilizzare il metodo str.join() che assicura la costante concatenazione lineare delle prestazioni tra versioni e implementazioni.

Sequence Types in Python docs (vedi nota a piè [6])