web-dev-qa-db-fra.com

Quel est le moyen préféré de concaténer une chaîne en Python?

Comme string de Python ne peut pas être modifié, je me demandais comment concaténer une chaîne plus efficacement?

Je peux écrire comme ça:

s += stringfromelsewhere

ou comme ceci:

s = []
s.append(somestring)

later

s = ''.join(s)

En écrivant cette question, j'ai trouvé un bon article sur le sujet.

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

Mais c'est dans Python 2.x., donc la question serait de savoir si quelque chose a changé dans Python 3?

304
Max

Le meilleur moyen d'ajouter une chaîne à une variable chaîne consiste à utiliser + ou +=. C'est parce que c'est lisible et rapide. Ils sont également tout aussi rapides, celui que vous choisissez est une question de goût, le dernier est le plus commun. Voici les timings avec le module timeit:

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

Cependant, ceux qui recommandent d'avoir des listes et de les ajouter, puis de les joindre, le font, car ajouter une chaîne à une liste est probablement très rapide par rapport à l'extension d'une chaîne. Et cela peut être vrai, dans certains cas. Voici, par exemple, un million d’ajouts d’une chaîne d’un caractère, d’abord à une chaîne, puis à une liste:

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

OK, s’avère que même lorsque la chaîne résultante contient un million de caractères, l’ajout était encore plus rapide.

Essayons maintenant avec une chaîne de mille caractères cent mille fois:

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

Par conséquent, la chaîne de fin fait environ 100 Mo de long. C'était assez lent, l'ajout à une liste était beaucoup plus rapide. Que ce timing n'inclue pas la finale a.join(). Alors, combien de temps cela prendrait-il?

a.join(a):
0.43739795684814453

Oups. Même dans ce cas, append/join est plus lent.

Alors d'où vient cette recommandation? Python 2?

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

Bien, append/join est marginalement plus rapidement si vous utilisez des chaînes extrêmement longues (ce que vous n'êtes généralement pas, que feriez-vous avec une chaîne de 100 Mo) en mémoire?)

Mais le vrai décisif est Python 2.3. Où je ne vais même pas vous montrer les horaires, parce que c'est tellement lent que ça n'a pas encore fini. Ces tests prennent soudainement minutes . Sauf pour append/join, qui est aussi rapide que dans les versions ultérieures de Pythons.

Ouaip. La concaténation de chaînes était très lente en Python à l'âge de pierre. Mais sur 2.4, ce n'est plus (ou au moins Python 2.4.7), donc la recommandation d'utiliser append/join est devenue obsolète en 2008, lorsque Python 2.3 n'a plus été mis à jour, et vous devriez avoir cessé de l'utiliser. :-)

(Mise à jour: lorsque j'ai fait le test plus attentivement, l'utilisation de + et de += est plus rapide pour deux chaînes sur Python 2.3. La recommandation d'utiliser ''.join() doit être un malentendu)

Cependant, c'est CPython. D'autres implémentations peuvent avoir d'autres préoccupations. Et ceci n’est qu’une autre raison pour laquelle une optimisation prématurée est la racine de tout mal. N'utilisez pas une technique supposée "plus rapide" à moins de la mesurer au préalable.

Par conséquent, la "meilleure" version pour effectuer la concaténation de chaînes consiste à utiliser + ou + =. Et si cela s'avère lent pour vous, ce qui est assez improbable, faites autre chose.

Alors pourquoi est-ce que j'utilise beaucoup de append/join dans mon code? Parce que parfois c'est plus clair. Surtout quand tout ce que vous devriez concaténer ensemble devrait être séparé par des espaces, des virgules ou des nouvelles lignes.

379
Lennart Regebro

Si vous concaténez beaucoup de valeurs, alors ni l'un ni l'autre. Ajouter une liste coûte cher. Vous pouvez utiliser StringIO pour cela. Surtout si vous le construisez sur de nombreuses opérations.

from cStringIO import StringIO
# python3:  from io import StringIO

buf = StringIO()

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

buf.getvalue()
# 'foofoofoo'

Si vous avez déjà une liste complète renvoyée par une autre opération, utilisez simplement la ''.join(aList)

De la python FAQ: Quel est le moyen le plus efficace de concaténer plusieurs chaînes ensemble?

les objets str et bytes étant immuables, la concaténation de plusieurs chaînes est inefficace car chaque concaténation crée un nouvel objet. Dans le cas général, le coût total d'exécution est quadratique dans la longueur totale de la chaîne.

Pour accumuler de nombreux objets str, il est recommandé de les placer dans une liste et d’appeler str.join () à la fin:

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

(un autre langage relativement efficace consiste à utiliser io.StringIO)

Pour accumuler de nombreux objets octets, l'idiome recommandé est d'étendre un objet bytearray à l'aide de la concaténation sur place (l'opérateur + =):

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

Edit: J'étais idiot et les résultats étaient collés à l'envers, donnant l'impression que l'ajout à une liste était plus rapide que cStringIO. J'ai également ajouté des tests pour bytearray/str concat, ainsi qu'une deuxième série de tests utilisant une liste plus longue avec des chaînes plus grandes. (python 2.7.3)

Exemple de test ipython pour les grandes listes de chaînes

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
43
jdi

Dans Python> = 3.6, le nouveau f-string est un moyen efficace de concaténer une chaîne.

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

Si les chaînes que vous concaténez sont des littéraux, utilisez Concaténation littérale de chaîne

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

Ceci est utile si vous souhaitez commenter une partie d'une chaîne (comme ci-dessus) ou si vous souhaitez utiliser chaînes brutes ou des guillemets triples pour une partie d'un littéral mais pas pour tous.

Comme cela se produit au niveau de la couche de syntaxe, il utilise zéro opérateur de concaténation.

8
droid

La méthode recommandée consiste toujours à utiliser append et join.

8
MRAB

L'utilisation de la concaténation de chaînes sur place par '+' est la méthode de concaténation THE WORST en termes de stabilité et d'implémentation croisée, car elle ne prend pas en charge toutes les valeurs. La norme PEP8 décourage cela et encourage l'utilisation du format (), join () et append () pour une utilisation à long terme.

6
badslacks

Bien que quelque peu daté, Code comme un Pythonista: Python idiomatique recommande join() sur +dans cette section . Comme PythonSpeedPerformanceTips dans sa section sur concaténation de chaînes , avec l'avertissement suivant:

La précision de cette section est contestée par rapport aux versions ultérieures de Python. Dans CPython 2.5, la concaténation de chaînes est assez rapide, bien que cela puisse ne pas s'appliquer de la même manière à d'autres implémentations Python. Voir ConcatenationTestCode pour une discussion.

6
Levon

Vous écrivez cette fonction

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

Ensuite, vous pouvez appeler simplement où vous voulez

str_join('Pine')  # Returns : Pine
str_join('Pine', 'Apple')  # Returns : Pineapple
str_join('Pine', 'Apple', 3)  # Returns : Pineapple3
6
Shameem

Comme @jdi mentionne Python, la documentation suggère d'utiliser str.join ou io.StringIO pour la concaténation de chaînes. Et dit qu'un développeur doit s'attendre à un temps quadratique de la part de += dans une boucle, même s'il existe une optimisation depuis Python 2.4. Comme this réponse dit:

Si Python détecte que l'argument de gauche n'a pas d'autre référence, il appelle realloc pour tenter d'éviter une copie en redimensionnant la chaîne. Ce n'est pas quelque chose sur lequel vous devriez jamais compter, car c'est un détail d'implémentation et parce que si realloc finit par avoir besoin de déplacer la chaîne fréquemment, les performances se dégradent à O (n ^ 2) quand même.

Je vais montrer un exemple de code du monde réel qui s'appuyait naïvement sur += cette optimisation, mais cela ne s'appliquait pas. Le code ci-dessous convertit une chaîne de chaînes courtes itérable en gros morceaux à utiliser dans une API en bloc.

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

Ce code peut être littéraire pendant des heures en raison de la complexité temporelle quadratique. Vous trouverez ci-dessous des alternatives avec des structures de données suggérées:

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())

Et 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
saaj

mon cas d'utilisation était légèrement différent. Je devais construire une requête où plus de 20 champs étaient dynamiques. J'ai suivi cette approche d'utilisation de la méthode du format

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

c'était relativement plus simple pour moi au lieu d'utiliser + ou d'autres moyens

3
ishwar rimal

Vous pouvez faire de différentes manières.

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

J'ai créé ce petit résumé à travers les articles suivants.

2
Kushan Gunasekera

Vous pouvez aussi utiliser ceci (plus efficace). ( https://softwareengineering.stackexchange.com/questions/304445/why-is-s-better-than-for-concatenation )

s += "%s" %(stringfromelsewhere)
2
SuperNova