web-dev-qa-db-fra.com

Une raison de ne pas utiliser '+' pour concaténer deux chaînes?

Un anti-modèle commun en Python consiste à concaténer une séquence de chaînes en utilisant + dans une boucle. Cela est grave car l'interpréteur Python doit créer un nouvel objet chaîne pour chaque itération, et cela prend finalement du temps quadratique. (Les versions récentes de CPython peuvent apparemment optimiser cela dans certains cas, mais d'autres implémentations ne le peuvent pas, donc les programmeurs sont découragés de s'en fier.) ''.join est la bonne façon de procéder.

Cependant, j'ai entendu dire ( y compris ici sur Stack Overflow ) que vous devriez jamais, jamais utiliser + pour la concaténation de chaînes, mais plutôt toujours utiliser ''.join ou une chaîne de format. Je ne comprends pas pourquoi c'est le cas si vous ne concaténez que deux chaînes. Si ma compréhension est correcte, cela ne devrait pas prendre de temps quadratique, et je pense que a + b est plus propre et plus lisible que ''.join((a, b)) ou '%s%s' % (a, b).

Est-ce une bonne pratique d'utiliser + pour concaténer deux chaînes? Ou y a-t-il un problème dont je ne suis pas au courant?

109
Taymon

Il n'y a rien de mal à concaténer deux chaînes avec +. En effet, il est plus facile à lire que ''.join([a, b]).

Vous avez raison de dire que concaténer plus de 2 chaînes avec + est une opération O (n ^ 2) (comparée à O(n) pour join) et devient donc inefficace. Cependant, cela n'a rien à voir avec l'utilisation d'une boucle. Même a + b + c + ... est O (n ^ 2), la raison étant que chaque concaténation produit une nouvelle chaîne.

CPython2.4 et les versions ultérieures tentent de résoudre ce problème, mais il est toujours recommandé d'utiliser join lors de la concaténation de plus de 2 chaînes.

101
ggozad

Plus est une solution parfaite pour concaténer deux chaînes de caractères Python. Mais si vous continuez à ajouter plus de deux chaînes (n> 25), vous voudrez peut-être penser à autre chose.

L'astuce ''.join([a, b, c]) est une optimisation des performances. 

45
Mikko Ohtamaa

L'hypothèse selon laquelle il ne faut jamais, jamais utiliser + pour la concaténation de chaînes, mais plutôt toujours utiliser '' .join peut être un mythe. Il est vrai que l'utilisation de + crée des copies temporaires inutiles d'un objet chaîne immuable, mais l'autre fait souvent cité est que l'appel de join dans une boucle ajouterait généralement la surcharge de function call. Prenons votre exemple.

Créez deux listes, l’une à partir de la question SO liée et l’autre plus grosse

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

Permet de créer deux fonctions, UseJoin et UsePlus pour utiliser les fonctionnalités respectives join et +.

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

Permet d'exécuter timeit avec la première liste

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

Ils ont presque le même temps d'exécution.

Permet d'utiliser 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}

Et il semble qu'en utilisant Join, il en résulte des appels de fonction inutiles qui pourraient ajouter à la surcharge.

Revenons maintenant à la question. Faut-il décourager l'utilisation de + sur join dans tous les cas?

Je crois que non, les choses devraient être prises en compte

  1. Longueur de la chaîne en question
  2. Nombre d'opérations de concaténation.

Et bien sûr, une optimisation prématurée du développement est néfaste. 

7
Abhijit

Lorsque vous travaillez avec plusieurs personnes, il est parfois difficile de savoir exactement ce qui se passe. L'utilisation d'une chaîne de format au lieu d'une concaténation peut éviter un désagrément particulier qui nous est arrivé une tonne de fois:

Disons qu'une fonction nécessite un argument et que vous l'écrivez en espérant obtenir une chaîne:

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

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

Donc, cette fonction peut être utilisée assez souvent dans le code. Vos collègues savent peut-être exactement ce que cela fait, mais ne sont pas nécessairement parfaitement au courant des tâches internes et peuvent ne pas savoir que la fonction attend une chaîne. Et alors ils peuvent se retrouver avec ceci:

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

Il n'y aurait pas de problème si vous venez d'utiliser une chaîne de format:

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

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

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

Il en va de même pour tous les types d'objets qui définissent __str__, qui peuvent également être transmis:

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

Donc oui: si vous pouvez utiliser une chaîne de format faites-le et profitez de ce que Python a à offrir.

6
Izkata

Selon la documentation Python, l'utilisation de str.join () vous assurera la cohérence des performances dans diverses implémentations de Python. Bien que CPython optimise le comportement quadratique de s = s + t, d’autres implémentations Python ne le peuvent pas.

CPython implémentation: Si s et t sont tous les deux des chaînes, certaines Les implémentations Python telles que CPython peuvent généralement effectuer une installation sur place optimisation pour les assignations de la forme s = s + t ou s + = t. Quand applicable, cette optimisation rend le temps d’exécution quadratique beaucoup moins important probable. Cette optimisation est à la fois une version et une implémentation dépendant. Pour le code sensible aux performances, il est préférable d’utiliser le fichier Méthode str.join () qui assure une concaténation linéaire cohérente performances à travers les versions et les implémentations.

Types de séquence dans les documents Python (voir la note de bas de page [6])

2
Duke

J'ai fait un test rapide:

import sys

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

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

et chronométré:

mslade@mickpc:/binks/micks/Ruby/tests$ time python /binks/micks/junk/strings.py  8000000
8000000 times

real    0m2.165s
user    0m1.620s
sys     0m0.540s
mslade@mickpc:/binks/micks/Ruby/tests$ time python /binks/micks/junk/strings.py  16000000
16000000 times

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

Il y a apparemment une optimisation pour le cas a = a + b. Il ne présente pas le temps O (n ^ 2) comme on pourrait le penser.

Donc, au moins en termes de performances, utiliser + convient.

2
Michael Slade