web-dev-qa-db-ja.com

「+」を使用して2つの文字列を連結しない理由は何ですか?

Pythonの一般的なアンチパターンは、ループで+を使用して文字列のシーケンスを連結することです。 Pythonインタープリターは反復ごとに新しい文字列オブジェクトを作成する必要があり、最終的に2次時間がかかるため、これは悪いことです。 (CPythonの最近のバージョンは明らかにこれを最適化できる場合もありますが、他の実装は最適化できないため、プログラマはこれに依存することをお勧めしません。)''.joinはこれを行う正しい方法です。

ただし、( スタックオーバーフローに関するここを含む )と言ったことを聞いたことがあります絶対に、文字列には+連結しますが、代わりに常に''.joinまたはフォーマット文字列を使用します。 2つの文字列を連結するだけの場合、これが当てはまる理由はわかりません。私の理解が正しければ、二次的な時間はかからないはずです。また、a + b''.join((a, b))または'%s%s' % (a, b)のいずれよりもクリーンで読みやすいと思います。

+を使用して2つの文字列を連結することをお勧めしますか?または、私が知らない問題がありますか?

114
Taymon

two文字列を+で連結しても何も問題はありません。実際、''.join([a, b])よりも読みやすいです。

ただし、3つ以上の文字列を+と連結することはO(n ^ 2)操作であり(joinのO(n)と比較)、非効率的です。ただし、これはループの使用とは関係ありません。 a + b + c + ...でさえO(n ^ 2)です。これは、各連結が新しい文字列を生成するためです。

CPython2.4以降はそれを緩和しようとしますが、3つ以上の文字列を連結するときにjoinを使用することをお勧めします。

105
ggozad

Plus演算子は、two Python文字列を連結するための完全に素晴らしいソリューションです。ただし、3つ以上の文字列(n> 25)を追加し続ける場合は、何か他のものを考えてみてください。

''.join([a, b, c])トリックはパフォーマンスの最適化です。

46
Mikko Ohtamaa

文字列の連結に+を使用してはならないが、代わりに '' .joinを常に使用する必要があるという仮定は神話かもしれません。 +を使用すると不変の文字列オブジェクトの不必要な一時コピーが作成されるのは事実ですが、引用されていない事実は、ループでjoinを呼び出すと一般にfunction callのオーバーヘッドが追加されることです。例を見てみましょう。

2つのリストを作成します。1つはリンクされたSO質問から、もう1つはより大きな作成

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

UseJoinおよびUsePlusの2つの関数を作成して、それぞれのjoinおよび+機能を使用してみましょう。

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

最初のリストでtimeitを実行できます

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

これらのランタイムはほぼ同じです。

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}

また、Joinを使用すると、不要な関数呼び出しが発生し、オーバーヘッドが増加する可能性があるようです。

質問に戻りましょう。すべての場合において、joinよりも+の使用をやめるべきですか?

いいえ、物事を考慮する必要があります

  1. 問題の文字列の長さ
  2. 連結操作なし。

そして、開発の時期尚早な最適化のコース外は悪です。

7
Abhijit

複数の人と作業する場合、何が起こっているのかを正確に知ることが難しい場合があります。連結の代わりにフォーマット文字列を使用することで、私たちに何トンも発生する特定の煩わしさを回避できます。

たとえば、関数には引数が必要で、文字列を取得することを想定して記述します。

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

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

そのため、この関数はコード全体でかなり頻繁に使用されます。あなたの同僚はそれが何をするかを正確に知っているかもしれませんが、必ずしも内部で完全に最新であるとは限らず、関数が文字列を期待していることを知らないかもしれません。そして、彼らはこれで終わるかもしれません:

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

書式文字列を使用した場合は問題ありません。

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

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

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

同じことが、__str__を定義するすべてのタイプのオブジェクトにも当てはまります。

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

はい。フォーマット文字列do itを使用して、Pythonが提供するものを利用できる場合。

6
Izkata

Pythonのドキュメントによると、str.join()を使用すると、Pythonのさまざまな実装でパフォーマンスの一貫性が得られます。 CPythonはs = s + tの2次動作を最適化しますが、他のPython実装はそうでない場合があります。

CPython実装の詳細:sとtが両方とも文字列の場合、CPythonなどの一部のPython実装は通常、インプレース最適化を実行できますs = s + tまたはs + = tの形式の割り当ての場合。該当する場合、この最適化により、2次実行時間が非常に少なくなります。この最適化は、バージョンと実装の両方に依存しています。パフォーマンスに敏感なコードの場合、バージョンおよび実装全体で一貫した線形連結パフォーマンスを保証するstr.join()メソッドを使用することをお勧めします。

Python docsのシーケンスタイプ (脚注[6]を参照)

2
Duke

私は簡単なテストを行いました:

import sys

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

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

そして時間を計った:

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

a = a + bの場合には明らかに最適化があります。疑わしいほどO(n ^ 2)時間を示しません。

したがって、少なくともパフォーマンスの観点からは、+を使用するのが適切です。

2
Michael Slade

''。join([a、b])+よりも優れたソリューションです。

コードは、他のPython実装(PyPy、Jython、IronPython、Cython、Psycoなど)を不利にしない方法で記述する必要があるため

form a + = bまたはa = a + bはCPythonでも壊れやすく、実装にはまったく存在しません使用しないrefcounting(参照カウントは、オブジェクト、メモリブロック、ディスクスペース、その他のリソースなどのリソースへの参照、ポインター、またはハンドルの数を格納する手法です

https://www.python.org/dev/peps/pep-0008/#programming-recommendations

0
muhammad ali