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?
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.
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.
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
Et bien sûr, une optimisation prématurée du développement est néfaste.
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.
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])
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.