2つの文字列を連結するために '+'を使用しない理由は何ですか?


123

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

ただし、(ここではStack Overflowを含めて)文字列の連結には決して使用すべきではなく、+常に''.joinまたはフォーマット文字列を使用するように言われたと聞きました。2つの文字列のみを連結しているのに、なぜこれが当てはまるのか理解できません。私の理解が正しければ、二次時間は必要ありません。またa + b''.join((a, b))またはよりも読みやすく、読みやすくなっています'%s%s' % (a, b)

+2つの文字列を連結するのに使用するのは良い習慣ですか?または、私が気付いていない問題はありますか?


そのすっきりと、連結を行わないように制御できます。しかし、その少し遅いストリングバッシングトレードオフ:P
Jakob Bowyer

あなた+は速いですか遅いですか?なぜ?
タイモン

1
+高速です In [2]: %timeit "a"*80 + "b"*80 1000000 loops, best of 3: 356 ns per loop In [3]: %timeit "%s%s" % ("a"*80, "b"*80) 1000000 loops, best of 3: 907 ns per loop
Jakob Bowyer

4
In [3]: %timeit "%s%s" % (a, b) 1000000 loops, best of 3: 590 ns per loop In [4]: %timeit a + b 10000000 loops, best of 3: 147 ns per loop
Jakob Bowyer

1
@JakobBowyerなど:「文字列の連結は不正です」の引数は速度とはほとんど関係ありませんが、を使用した自動型変換を利用してい__str__ます。例については、私の回答を参照してください。
イズカタ、

回答:


119

2つの文字列をで連結しても問題はありません+。確かにそれよりも読みやすいです''.join([a, b])

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

CPython2.4以降はそれを軽減しようとしますが、join3つ以上の文字列を連結する場合は、それを使用することをお勧めします。


5
@Mutant:.join反復可能になりますので、両方.join([a,b]).join((a,b))有効です。
捨て子

1
興味深いタイミングは、CPython 2.3以降の場合でも、(2013年から)stackoverflow.com/a/12171382/378826(Lennart Regebroから)で受け入れられた回答を使用する++=、またはこの中に明確に表示される場合にのみ「追加/結合」パターンを選択することを示唆しています。目の前の問題解決策のアイデア。
Dilettant

49

Plus演算子は、2つの Python文字列を連結するための完全に優れたソリューションです。しかし、3つ以上の文字列(n> 25)を追加し続ける場合は、別のことを考える必要があるかもしれません。

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


2
タプルはリストよりも優れていませんか?
ThiefMaster 2012

7
タプルはより速くなります-コードは単なる例です:)通常、長い複数の文字列入力は動的です。
Mikko Ohtamaa 2012

5
@martineau彼は動的にappend()文字列を生成してリストに入れるという意味だと思います。
Peter C

5
ここで言う必要があります:タプルは通常、特に成長している場合はSLOWER構造です。リストを使用すると、list.extend(list_of_items)とlist.append(item)を使用できます。
Antti Haapala 2012

6
の+1 n > 25。人間はどこかから始めるために基準点を必要とします。
n611x007 2013

8

文字列の連結に+を決して使用してはならないという仮定ではなく、常に '' .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それぞれの使用する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を使用すると、不要な関数呼び出しが発生し、オーバーヘッドが増える可能性があります。

質問に戻ります。1は使用落胆すべきである+オーバーjoinすべてのケースありますか?

いいえ、考えなければなりません

  1. 問題のストリングの長さ
  2. 連結操作はありません。

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


7
もちろん、アイデアはjoinループ自体の内部で使用するのではなく、ループはjoinに渡されるシーケンスを生成します。
jsbueno 2012

7

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

たとえば、関数は引数を必要とし、文字列を取得することを期待してそれを記述します。

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

だからはい:フォーマット文字列を使用できる場合は、それ実行し、Pythonが提供する機能を利用してください。


1
十分な理由のある反対意見の場合は+1。私はまだ私が好むと思い+ます。
タイモン

1
fooメソッドを次のように定義しないのはなぜですか:print 'bar:' + str(zeta)?
EngineerWithJava54321

@ EngineerWithJava54321たとえば、エラーが発生しないようにするには、zeta = u"a\xac\u1234\u20ac\U00008000"を使用print 'bar: ' + unicode(zeta)する必要があります。 %sそれについて考える必要なしにそれを正しく行い、はるかに短くなります
イズカタ2015

@ EngineerWithJava54321他の例はここではあまり関係が"bar: %s"ありませんが、たとえば、"zrb: %s br"他の言語に翻訳される場合があります。%sバージョンはちょうど仕事が、文字列化連結バージョンは、すべてのケースを処理するために、混乱になるだろうと、あなたの翻訳者は今に対処するには、2つの別々の翻訳だろう
Izkata

fooの実装が何であるかを知らない場合、anyでこのエラーが発生しますdef
Insides '19

3

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

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)時間を示しません。

したがって、少なくともパフォーマンスの点では、使用+は問題ありません。


3
ここで「参加」のケースと比較できます。そして、pypy、jython、ironpythonなどの他のPython実装の問題もあります
。– jsbueno

3

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

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

Pythonドキュメントのシーケンスタイプ脚注 [6]を参照)



0

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

コードは、Pythonの他の実装(PyPy、Jython、IronPython、Cython、Psycoなど)を損なわない方法で作成する必要があるため

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

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


1
a += bPythonのすべての実装で機能しますが、一部の実装では、ループ内で実行すると 2次時間がかかるだけです。問題は、ループの文字列連結についてでした。
タイモン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.