web-dev-qa-db-fra.com

Quelle est la méthode de concaténation de chaînes la plus efficace en python?

Existe-t-il une méthode efficace de concaténation de chaînes de masse en Python (comme StringBuilder en C # ou StringBuffer en Java)? J'ai trouvé les méthodes suivantes ici :

  • Concaténation simple utilisant +
  • Utilisation de la liste de chaînes et de la méthode join
  • Utilisation du module UserString à partir du module MutableString
  • Utilisation du tableau de caractères et du module array 
  • Utilisation du module cStringIO à partir du module StringIO

Mais qu'est-ce que les experts utilisent ou suggèrent et pourquoi? 

[ Une question connexe ici ]

119
mshsayem

Vous pouvez être intéressé par ceci: Une anecdote d'optimisation de Guido. Bien qu'il soit utile de rappeler également qu'il s'agit d'un ancien article et qu'il date d'avant l'existence d'éléments tels que ''.join (bien que je suppose que string.joinfields soit plus ou moins identique)

Fort de cela, le module arraypeut sera le plus rapide si vous pouvez résoudre votre problème en détail. Mais ''.join est probablement assez rapide et présente l'avantage d'être idiomatique et donc plus facile à comprendre pour les autres programmeurs python.

Enfin, la règle d'or de l'optimisation: n'optimisez que si vous savez que vous en avez le besoin, et mesurez plutôt que de deviner.

Vous pouvez mesurer différentes méthodes à l'aide du module timeit. Cela peut vous dire qui est le plus rapide, au lieu d'inconnus sur Internet sur des suppositions.

108
John Fouhy

''.join(sequenceofstrings) est ce qui fonctionne habituellement le mieux - le plus simple et le plus rapide.

54
Alex Martelli

Cela dépend de ce que vous faites.

Après Python 2.5, la concaténation de chaînes avec l'opérateur + est assez rapide. Si vous ne faites que concaténer quelques valeurs, il est préférable d’utiliser l’opérateur +:

>>> x = timeit.Timer(stmt="'a' + 'b'")
>>> x.timeit()
0.039999961853027344

>>> x = timeit.Timer(stmt="''.join(['a', 'b'])")
>>> x.timeit()
0.76200008392333984

Cependant, si vous assemblez une chaîne dans une boucle, vous feriez mieux d'utiliser la méthode de jointure de liste:

>>> join_stmt = """
... joined_str = ''
... for i in xrange(100000):
...   joined_str += str(i)
... """
>>> x = timeit.Timer(join_stmt)
>>> x.timeit(100)
13.278000116348267

>>> list_stmt = """
... str_list = []
... for i in xrange(100000):
...   str_list.append(str(i))
... ''.join(str_list)
... """
>>> x = timeit.Timer(list_stmt)
>>> x.timeit(100)
12.401000022888184

... mais notez que vous devez assembler un nombre relativement élevé de chaînes avant que la différence ne devienne perceptible.

35
Jason Baker

Python 3.6 a changé le jeu de la concaténation des composants connus avec Interpolation de chaîne littérale .

Vu le cas de test de réponse de mkoistinen , avoir des chaînes

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'

Les prétendants sont 

  • f'http://{domain}/{lang}/{path}' - 0,151 µs 

  • 'http://%s/%s/%s' % (domain, lang, path) - 0,321 µs

  • 'http://' + domain + '/' + lang + '/' + path - 0,356 µs

  • ''.join(('http://', domain, '/', lang, '/', path)) - 0.249 µs (notez que la construction d'un tuple de longueur constante est légèrement plus rapide que la construction d'une liste de constantes).

Ainsi, actuellement, le code le plus court et le plus beau possible est aussi le plus rapide.

Dans les versions alpha de Python 3.6, l'implémentation pour les chaînes f'' était le le plus lent possible. En fait, le code d'octet généré est à peu près équivalent au cas ''.join() avec des appels inutiles à str.__format__ qui, sans arguments, ne renverrait que self. Ces inefficacités ont été corrigées avant la version 3.6 finale.

La vitesse peut être mise en contraste avec la méthode la plus rapide pour Python 2, qui est la concaténation + sur mon ordinateur; et cela prend 0,203 µs avec des chaînes de 8 bits et 0,259 µs si les chaînes sont toutes en Unicode.

33
Antti Haapala

Selon la réponse de John Fouhy, n'optimisez pas à moins que vous n'ayez à le faire, mais si vous êtes ici et posez cette question, c'est peut-être précisément parce que vous devez. Dans mon cas, j'avais besoin d'assembler des URL à partir de variables chaîne ... rapidement. J'ai remarqué que personne ne semblait (jusqu'à présent) considérer la méthode du format de chaîne, alors je me suis dit que j'essaierais cela et, surtout pour un intérêt modéré, je pensais que je jetterais l'opérateur d'interpolation de chaîne pour une meilleure évaluation. Pour être honnête, je ne pensais pas que l’un ou l’autre de ceux-ci aboutirait à une opération directe '+' ou à un '' .join (). Mais devinez quoi? Sur mon système Python 2.7.5, l'opérateur d'interpolation de chaîne les règle tous et string.format () est celui qui a le plus mauvais rendement:

# concatenate_test.py

from __future__ import print_function
import timeit

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'
iterations = 1000000

def meth_plus():
    '''Using + operator'''
    return 'http://' + domain + '/' + lang + '/' + path

def meth_join():
    '''Using ''.join()'''
    return ''.join(['http://', domain, '/', lang, '/', path])

def meth_form():
    '''Using string.format'''
    return 'http://{0}/{1}/{2}'.format(domain, lang, path)

def meth_intp():
    '''Using string interpolation'''
    return 'http://%s/%s/%s' % (domain, lang, path)

plus = timeit.Timer(stmt="meth_plus()", setup="from __main__ import meth_plus")
join = timeit.Timer(stmt="meth_join()", setup="from __main__ import meth_join")
form = timeit.Timer(stmt="meth_form()", setup="from __main__ import meth_form")
intp = timeit.Timer(stmt="meth_intp()", setup="from __main__ import meth_intp")

plus.val = plus.timeit(iterations)
join.val = join.timeit(iterations)
form.val = form.timeit(iterations)
intp.val = intp.timeit(iterations)

min_val = min([plus.val, join.val, form.val, intp.val])

print('plus %0.12f (%0.2f%% as fast)' % (plus.val, (100 * min_val / plus.val), ))
print('join %0.12f (%0.2f%% as fast)' % (join.val, (100 * min_val / join.val), ))
print('form %0.12f (%0.2f%% as fast)' % (form.val, (100 * min_val / form.val), ))
print('intp %0.12f (%0.2f%% as fast)' % (intp.val, (100 * min_val / intp.val), ))

Les resultats:

# python2.7 concatenate_test.py
plus 0.360787868500 (90.81% as fast)
join 0.452811956406 (72.36% as fast)
form 0.502608060837 (65.19% as fast)
intp 0.327636957169 (100.00% as fast)

Si j'utilise un domaine et un chemin plus courts, l'interpolation reste gagnante. La différence est plus prononcée, cependant, avec des chaînes plus longues.

Maintenant que je dispose d’un script de test Nice, j’ai également testé sous Python 2.6, 3.3 et 3.4, voici les résultats. En Python 2.6, l'opérateur plus est le plus rapide! Sur Python 3, rejoindre gagne. Remarque: ces tests sont très reproductibles sur mon système. Donc, "plus" est toujours plus rapide sur 2.6, "intp" est toujours plus rapide sur 2.7 et "rejoindre" est toujours plus rapide sur Python 3.x.

# python2.6 concatenate_test.py
plus 0.338213920593 (100.00% as fast)
join 0.427221059799 (79.17% as fast)
form 0.515371084213 (65.63% as fast)
intp 0.378169059753 (89.43% as fast)

# python3.3 concatenate_test.py
plus 0.409130576998 (89.20% as fast)
join 0.364938726001 (100.00% as fast)
form 0.621366866995 (58.73% as fast)
intp 0.419064424001 (87.08% as fast)

# python3.4 concatenate_test.py
plus 0.481188605998 (85.14% as fast)
join 0.409673971997 (100.00% as fast)
form 0.652010936996 (62.83% as fast)
intp 0.460400978001 (88.98% as fast)

# python3.5 concatenate_test.py
plus 0.417167026084 (93.47% as fast)
join 0.389929617057 (100.00% as fast)
form 0.595661019906 (65.46% as fast)
intp 0.404455224983 (96.41% as fast)

Leçon apprise:

  • Parfois, mes suppositions sont complètement fausses.
  • Testez contre le système env. vous serez en cours de production.
  • L'interpolation de chaîne n'est pas encore morte!

tl; dr:

  • Si vous utilisez 2.6, utilisez l'opérateur +.
  • si vous utilisez 2.7, utilisez l'opérateur '%'.
  • si vous utilisez 3.x, utilisez '' .join ().
13
mkoistinen

cette URL contient les comparaisons des différentes approches et des analyses comparatives:

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


Remarque: Il s'agit d'une très ancienne comparaison datant d'avant 2009 basée sur Python 2.2 et qui devrait donc, dans la plupart des cas, être ignorée.

7
fengshaun

cela dépend à peu près de la taille relative de la nouvelle chaîne après chaque nouvelle concaténation . Avec l'opérateur +, une nouvelle chaîne est créée pour chaque concaténation. Si les chaînes intermédiaires sont relativement longues, le + devient de plus en plus lent, car la nouvelle chaîne intermédiaire est en cours de stockage.

Considérons ce cas:

from time import time
stri=''
a='aagsdfghfhdyjddtyjdhmfghmfgsdgsdfgsdfsdfsdfsdfsdfsdfddsksarigqeirnvgsdfsdgfsdfgfg'
l=[]
#case 1
t=time()
for i in range(1000):
    stri=stri+a+repr(i)
print time()-t

#case 2
t=time()
for i in xrange(1000):
    l.append(a+repr(i))
z=''.join(l)
print time()-t

#case 3
t=time()
for i in range(1000):
    stri=stri+repr(i)
print time()-t

#case 4
t=time()
for i in xrange(1000):
    l.append(repr(i))
z=''.join(l)
print time()-t

Résultats

1 0.00493192672729

2 0.000509023666382

3 0.00042200088501

4 0.000482797622681

Dans le cas de 1 & 2, nous ajoutons une grande chaîne et join () effectue environ 10 fois plus vite . Dans les cas 3 et 4, nous ajoutons une petite chaîne, et '+' est légèrement plus rapide

4
David Bielen

Un an plus tard, testons la réponse de mkoistinen avec Python 3.4.3:

  • plus 0,963564149000 (95,83% aussi vite)
  • rejoindre 0,923408469000 (100,00% aussi vite)
  • formulaire 1.501130934000 (61,51% aussi vite)
  • intp 1.019677452000 (90,56% aussi vite)

Rien n'a changé. La jointure reste la méthode la plus rapide. Intp étant sans doute le meilleur choix en termes de lisibilité, vous voudrez peut-être quand même utiliser intp.

2
ramsch

J'ai rencontré une situation où il me fallait une chaîne annexable de taille inconnue. Ce sont les résultats de référence (python 2.7.3):

$ python -m timeit -s 's=""' 's+="a"'
10000000 loops, best of 3: 0.176 usec per loop
$ python -m timeit -s 's=[]' 's.append("a")'
10000000 loops, best of 3: 0.196 usec per loop
$ python -m timeit -s 's=""' 's="".join((s,"a"))'
100000 loops, best of 3: 16.9 usec per loop
$ python -m timeit -s 's=""' 's="%s%s"%(s,"a")'
100000 loops, best of 3: 19.4 usec per loop

Cela semble montrer que '+ =' est le plus rapide. Les résultats du lien skymind sont un peu dépassés. 

(Je me rends compte que le deuxième exemple n'est pas complet, il faudrait joindre la liste finale. Cela montre cependant que la préparation de la liste prend plus de temps que la concat.

2
MattK

Pour un petit ensemble de chaînes courtes (c'est-à-dire 2 ou 3 chaînes de quelques caractères au maximum), plus est encore bien plus rapide. En utilisant le merveilleux script de mkoistinen dans Python 2 et 3:

plus 2.679107467004 (100.00% as fast)
join 3.653773699996 (73.32% as fast)
form 6.594011374000 (40.63% as fast)
intp 4.568015249999 (58.65% as fast)

Ainsi, lorsque votre code effectue un grand nombre de petites concaténations distinctes, le plus est la méthode préférée si la vitesse est cruciale.

0
user7505681

Inspiré des points de repère de @ JasonBaker, voici un exemple simple comparant 10 chaînes "abcdefghijklmnopqrstuvxyz", montrant que .join() est plus rapide. même avec cette petite augmentation de variables:

Caténation

>>> x = timeit.Timer(stmt='"abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz"')
>>> x.timeit()
0.9828147209324385

Joindre

>>> x = timeit.Timer(stmt='"".join(["abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz"])')
>>> x.timeit()
0.6114138159765048
0
A T

Probablement "nouvelles f-strings dans Python 3.6" est le moyen le plus efficace de concaténer des chaînes.

Utiliser% s

>>> timeit.timeit("""name = "Some"
... age = 100
... '%s is %s.' % (name, age)""", number = 10000)
0.0029734770068898797

Utiliser .format 

>>> timeit.timeit("""name = "Some"
... age = 100
... '{} is {}.'.format(name, age)""", number = 10000)
0.004015227983472869

En utilisant f

>>> timeit.timeit("""name = "Some"
... age = 100
... f'{name} is {age}.'""", number = 10000)
0.0019175919878762215

Source: https://realpython.com/python-f-strings/

0
SuperNova