Y at-il quelque chose dans Python comme le StringBuffer
de Java??) Les chaînes étant immuables dans Python aussi, les éditer dans des boucles serait inefficace.
La concaténation efficace de chaînes en Python est un article plutôt ancien et son énoncé principal selon lequel la concaténation naïve est beaucoup plus lente que la jointure n'est plus valide, car cette partie a été optimisée dans CPython depuis:
Détail de l’implémentation CPython: Si s et t sont deux chaînes, certaines implémentations Python telles que CPython peuvent généralement effectuer une optimisation sur place pour les assignations de la forme s = s + t ou s + = t Cette optimisation dépend de la version et de l’implémentation. Pour le code sensible aux performances, il est préférable d’utiliser la méthode str.join () qui garantit des performances de concaténation linéaire cohérentes entre les versions et implémentations. @ http://docs.python.org/2/library/stdtypes.html
J'ai un peu adapté leur code et obtenu les résultats suivants sur ma machine:
from cStringIO import StringIO
from UserString import MutableString
from array import array
import sys, timeit
def method1():
out_str = ''
for num in xrange(loop_count):
out_str += `num`
return out_str
def method2():
out_str = MutableString()
for num in xrange(loop_count):
out_str += `num`
return out_str
def method3():
char_array = array('c')
for num in xrange(loop_count):
char_array.fromstring(`num`)
return char_array.tostring()
def method4():
str_list = []
for num in xrange(loop_count):
str_list.append(`num`)
out_str = ''.join(str_list)
return out_str
def method5():
file_str = StringIO()
for num in xrange(loop_count):
file_str.write(`num`)
out_str = file_str.getvalue()
return out_str
def method6():
out_str = ''.join([`num` for num in xrange(loop_count)])
return out_str
def method7():
out_str = ''.join(`num` for num in xrange(loop_count))
return out_str
loop_count = 80000
print sys.version
print 'method1=', timeit.timeit(method1, number=10)
print 'method2=', timeit.timeit(method2, number=10)
print 'method3=', timeit.timeit(method3, number=10)
print 'method4=', timeit.timeit(method4, number=10)
print 'method5=', timeit.timeit(method5, number=10)
print 'method6=', timeit.timeit(method6, number=10)
print 'method7=', timeit.timeit(method7, number=10)
Résultats:
2.7.1 (r271:86832, Jul 31 2011, 19:30:53)
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)]
method1= 0.171155929565
method2= 16.7158739567
method3= 0.420584917068
method4= 0.231794118881
method5= 0.323612928391
method6= 0.120429992676
method7= 0.145267963409
Conclusions:
join
l'emporte toujours sur concat, mais marginalementPeut-être utiliser un bytearray :
In [1]: s = bytearray('Hello World')
In [2]: s[:5] = 'Bye'
In [3]: s
Out[3]: bytearray(b'Bye World')
In [4]: str(s)
Out[4]: 'Bye World'
L'attrait de l'utilisation d'un bytearray est son efficacité de mémoire et sa syntaxe pratique. Cela peut aussi être plus rapide que d’utiliser une liste temporaire:
In [36]: %timeit s = list('Hello World'*1000); s[5500:6000] = 'Bye'; s = ''.join(s)
1000 loops, best of 3: 256 µs per loop
In [37]: %timeit s = bytearray('Hello World'*1000); s[5500:6000] = 'Bye'; str(s)
100000 loops, best of 3: 2.39 µs per loop
Notez que la différence de vitesse est en grande partie imputable à la création du conteneur:
In [32]: %timeit s = list('Hello World'*1000)
10000 loops, best of 3: 115 µs per loop
In [33]: %timeit s = bytearray('Hello World'*1000)
1000000 loops, best of 3: 1.13 µs per loop
Cela dépend de ce que vous voulez faire. Si vous voulez une séquence mutable, le type intégré list
est votre ami, et aller de str à list et back est aussi simple que:
mystring = "abcdef"
mylist = list(mystring)
mystring = "".join(mylist)
Si vous souhaitez créer une chaîne de grande taille à l'aide d'une boucle for, la méthode Pythonic consiste généralement à créer une liste de chaînes, puis à les associer avec le séparateur approprié (saut de ligne ou autre).
Sinon, vous pouvez également utiliser un système de modèle de texte, un analyseur syntaxique ou tout autre outil spécialisé qui convient le mieux au travail.
Les réponses fournies précédemment sont presque toujours les meilleures. Cependant, la chaîne est parfois constituée de nombreux appels de méthodes et/ou de boucles, il n'est donc pas naturel de créer une liste de lignes puis de les joindre. Et comme rien ne garantit que vous utilisez CPython ou que l'optimisation de CPython sera appliquée, une autre approche consiste simplement à utiliser print!
Voici un exemple de classe d'assistance, bien que la classe d'aide soit triviale et probablement inutile, elle sert à illustrer l'approche (Python 3):
import io
class StringBuilder(object):
def __init__(self):
self._stringio = io.StringIO()
def __str__(self):
return self._stringio.getvalue()
def append(self, *objects, sep=' ', end=''):
print(*objects, sep=sep, end=end, file=self._stringio)
sb = StringBuilder()
sb.append('a')
sb.append('b', end='\n')
sb.append('c', 'd', sep=',', end='\n')
print(sb) # 'ab\nc,d\n'
Juste un test sur lequel je lance python 3.6.2 montrant que "rejoindre" gagne toujours BIG!
from time import time
def _with_format(i):
_st = ''
for i in range(0, i):
_st = "{}{}".format(_st, "0")
return _st
def _with_s(i):
_st = ''
for i in range(0, i):
_st = "%s%s" % (_st, "0")
return _st
def _with_list(i):
l = []
for i in range(0, i):
l.append("0")
return "".join(l)
def _count_time(name, i, func):
start = time()
r = func(i)
total = time() - start
print("%s done in %ss" % (name, total))
return r
iterationCount = 1000000
r1 = _count_time("with format", iterationCount, _with_format)
r2 = _count_time("with s", iterationCount, _with_s)
r3 = _count_time("with list and join", iterationCount, _with_list)
if r1 != r2 or r2 != r3:
print("Not all results are the same!")
Et la sortie était:
with format done in 17.991968870162964s
with s done in 18.36879801750183s
with list and join done in 0.12142801284790039s
ce lien pourrait être utile pour la concaténation en python
http://pythonadventures.wordpress.com/2010/09/27/stringbuilder/
exemple du lien ci-dessus:
def g():
sb = []
for i in range(30):
sb.append("abcdefg"[i%7])
return ''.join(sb)
print g()
# abcdefgabcdefgabcdefgabcdefgab
J'ai ajouté au code de Roee Gavirel 2 tests supplémentaires qui montrent de manière concluante que joindre des listes à des chaînes n'est pas plus rapide que s + = "quelque chose".
Résultats:
Python 2.7.15rc1
Iterations: 100000
format done in 0.317540168762s
%s done in 0.151262044907s
list+join done in 0.0055148601532s
str cat done in 0.00391721725464s
Python 3.6.7
Iterations: 100000
format done in 0.35594654083251953s
%s done in 0.2868080139160156s
list+join done in 0.005924701690673828s
str cat done in 0.0054128170013427734s
f str done in 0.12870001792907715s
Code:
from time import time
def _with_cat(i):
_st = ''
for i in range(0, i):
_st += "0"
return _st
def _with_f_str(i):
_st = ''
for i in range(0, i):
_st = f"{_st}0"
return _st
def _with_format(i):
_st = ''
for i in range(0, i):
_st = "{}{}".format(_st, "0")
return _st
def _with_s(i):
_st = ''
for i in range(0, i):
_st = "%s%s" % (_st, "0")
return _st
def _with_list(i):
l = []
for i in range(0, i):
l.append("0")
return "".join(l)
def _count_time(name, i, func):
start = time()
r = func(i)
total = time() - start
print("%s done in %ss" % (name, total))
return r
iteration_count = 100000
print('Iterations: {}'.format(iteration_count))
r1 = _count_time("format ", iteration_count, _with_format)
r2 = _count_time("%s ", iteration_count, _with_s)
r3 = _count_time("list+join", iteration_count, _with_list)
r4 = _count_time("str cat ", iteration_count, _with_cat)
r5 = _count_time("f str ", iteration_count, _with_f_str)
if len(set([r1, r2, r3, r4, r5])) != 1:
print("Not all results are the same!")