Pythonで最も効率的な文字列連結方法は何ですか?


148

(のようなPythonで任意の効率的な大量の文字列の連結方法があるのStringBuilder C#または中StringBufferの Javaでは)?私はここに以下の方法を見つけまし

  • を使用した単純な連結 +
  • 文字列リストとjoinメソッドの使用
  • モジュールUserStringからの使用MutableString
  • 文字配列とarrayモジュールの使用
  • モジュールcStringIOからの使用StringIO

しかし、専門家は何を使用または提案していますか、そしてその理由は何ですか?

[ ここに関連する質問 ]



既知のフラグメントを1つに連結するために、Python 3.6にはf''、以前のバージョンのPythonのどの代替手段よりも高速なフォーマット文字列があります。
Antti Haapala 16

回答:


127

あなたはこれに興味があるかもしれません:グイドによる最適化の逸話。これは古い記事であり、次のようなものの存在よりも古いことを覚えておく価値はありますが''.joinstring.joinfields多かれ少なかれ同じだと思いますが)

その長所として、問題を突き詰めることができれば、arrayモジュール最速になる可能性があります。しかし''.join、おそらく十分高速であり、慣用的であるという利点があるため、他のpythonプログラマーが理解しやすくなります。

最後に、最適化の黄金律:必要な場合を除いて最適化しないでください。推測ではなく測定します。

timeitモジュールを使用して、さまざまな方法を測定できます。これにより、インターネット上のランダムな見知らぬ人が推測する代わりに、どちらが最も速いかを知ることができます


1
最適化のタイミングに関するポイントを追加したい:最悪のケースに対してテストするようにしてください。たとえば、サンプルを増やして、現在のコードが0.17秒から170秒に実行されるようにすることができます。さて、変動が少ないので、より大きなサンプルサイズでテストしたいと思います。
フリッパー

2
「必要があることがわかるまで最適化しないでください。」名目上異なるイディオムを使用している場合を除き、追加の労力をほとんどかけずにコードの再作成を回避できます。
jeremyjjbrown

1
知っておくべきことの1つはインタビューです(これは常に理解を深める絶好の機会です)。残念ながら、これに関する最新の記事は見つかりませんでした。(1)Java / C#文字列は2017年でもそれほど悪いのですか?(2)C ++はどうですか?(3)次に、何百万もの連結を実行する必要がある場合に焦点を当てて、Pythonの最新かつ最高の機能について説明します。結合が線形時間で機能することを信頼できますか?
user1854182 2017年

「十分に速い」とはどういう意味.join()ですか?主な質問は、a)連結用の文字列のコピーを作成する(と同様s = s + 'abc')(O(n)ランタイムが必要)、またはb)コピーを作成せずに既存の文字列に単に追加する(O(1)が必要) ?
CGFoX

64

''.join(sequenceofstrings) は、通常、最も簡単に、最も速く機能するものです。


3
@ mshsayem、Pythonでは、シーケンスは任意の列挙可能なオブジェクト(関数も含む)にすることができます。
ニックダンドゥラキス09

2
''.join(sequence)イディオムが大好きです。コンマで区切られたリストを生成することは特に便利です:', '.join([1, 2, 3])文字列を与えます'1, 2, 3'
Andrew Keeton、

7
@mshsayem:"".join(chr(x) for x in xrange(65,91))---この場合、joinの引数はジェネレーター式で作成されたイテレーターです。作成される一時的なリストはありません。
balpha 2009

2
@balpha:それでもジェネレーターのバージョンはリスト内包バージョンより遅い:C:\ temp> python -mtimeit "'' .join(chr(x)for x in xrange(65,91))" 100000ループ、最高3:ループごとに9.71 usec C:\ temp> python -mtimeit "'' .join([chr(x)for x in xrange(65,91)])" 100000ループ、ベスト3:ループごとに7.1 usec
hughdbrown

1
@hughdbrown、はい、wazoo(通常のtimeitの場合)に空きメモリがある場合、listcompはgenexpよりも多く、多くの場合20〜30%最適化できます。メモリのタイトなものが異なる場合-でもタイムリーに再現するのは難しい!-)
Alex Martelli

58

Python 3.6は、リテラル文字列補間による既知のコンポーネントの文字列連結のゲームを変更しました。

mkoistinenの答えからのテストケースを考えると、文字列があります

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'

候補者は

  • f'http://{domain}/{lang}/{path}'- 0.151マイクロ秒

  • 'http://%s/%s/%s' % (domain, lang, path) -0.321 µs

  • 'http://' + domain + '/' + lang + '/' + path -0.356 µs

  • ''.join(('http://', domain, '/', lang, '/', path))- 0.249マイクロ秒(一定の長さの組を構築する若干速い一定の長さのリストを構築するよりもあることに注意してください)。

したがって、現在可能な限り最も短く、最も美しいコードも最速です。

Python 3.6のアルファバージョンでf''は、文字列の実装が最も遅くなりました。実際には、生成されたバイトコードは、引数なしで変更されずに返さ''.join()れる不要な呼び出しの場合とstr.__format__ほぼself同じです。これらの非効率性は3.6 finalの前に対処されました。

この速度は+、私のコンピューターでの連結であるPython 2の最速の方法と対照的です。それはとり0.203 8ビットの文字列を含むマイクロ秒、および0.259文字列は、すべてのUnicodeであればマイクロ秒を。


38

それはあなたが何をしているかに依存します。

Python 2.5以降では、+演算子を使用した文字列の連結はかなり高速です。いくつかの値を連結するだけの場合は、+演算子の使用が最適です。

>>> x = timeit.Timer(stmt="'a' + 'b'")
>>> x.timeit()
0.039999961853027344

>>> x = timeit.Timer(stmt="''.join(['a', 'b'])")
>>> x.timeit()
0.76200008392333984

ただし、文字列をループにまとめる場合は、リスト結合方法を使用する方がよいでしょう。

>>> join_stmt = """
... joined_str = ''
... for i in xrange(100000):
...   joined_str += str(i)
... """
>>> x = timeit.Timer(join_stmt)
>>> x.timeit(100)
13.278000116348267

>>> list_stmt = """
... str_list = []
... for i in xrange(100000):
...   str_list.append(str(i))
... ''.join(str_list)
... """
>>> x = timeit.Timer(list_stmt)
>>> x.timeit(100)
12.401000022888184

...しかし、違いが顕著になる前に、比較的多数の文字列をまとめる必要があることに注意してください。


2
1)最初の測定では、時間がかかるのはおそらくリストの構築です。タプルで試してください。2)CPythonの実行均一良いが、しかし、他のPython実装は+で悪い方法を実行し、+ =
u0b34a0f6ae

22

John Fouhyの回答によると、必要がある場合を除いて最適化しないでください。ここにいて、この質問をする場合は、正確に行う必要があります。私の場合、文字列変数からいくつかのURLを組み立てる必要がありました...高速です。(これまでのところ)文字列形式の方法を検討している人はいないようです。私はそれを試してみるつもりだと思ったのですが、主に穏やかな関心のために、文字列補間演算子をそこに入れて十分に測定したいと思いました。正直に言うと、これらのどちらも直接の「+」演算や '' .join()までスタックするとは思いませんでした。しかし、何だと思いますか?私のPython 2.7.5システムでは、文字列補間演算子がすべてルール化し、string.format()が最悪のパフォーマーです。

# concatenate_test.py

from __future__ import print_function
import timeit

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'
iterations = 1000000

def meth_plus():
    '''Using + operator'''
    return 'http://' + domain + '/' + lang + '/' + path

def meth_join():
    '''Using ''.join()'''
    return ''.join(['http://', domain, '/', lang, '/', path])

def meth_form():
    '''Using string.format'''
    return 'http://{0}/{1}/{2}'.format(domain, lang, path)

def meth_intp():
    '''Using string interpolation'''
    return 'http://%s/%s/%s' % (domain, lang, path)

plus = timeit.Timer(stmt="meth_plus()", setup="from __main__ import meth_plus")
join = timeit.Timer(stmt="meth_join()", setup="from __main__ import meth_join")
form = timeit.Timer(stmt="meth_form()", setup="from __main__ import meth_form")
intp = timeit.Timer(stmt="meth_intp()", setup="from __main__ import meth_intp")

plus.val = plus.timeit(iterations)
join.val = join.timeit(iterations)
form.val = form.timeit(iterations)
intp.val = intp.timeit(iterations)

min_val = min([plus.val, join.val, form.val, intp.val])

print('plus %0.12f (%0.2f%% as fast)' % (plus.val, (100 * min_val / plus.val), ))
print('join %0.12f (%0.2f%% as fast)' % (join.val, (100 * min_val / join.val), ))
print('form %0.12f (%0.2f%% as fast)' % (form.val, (100 * min_val / form.val), ))
print('intp %0.12f (%0.2f%% as fast)' % (intp.val, (100 * min_val / intp.val), ))

結果:

# python2.7 concatenate_test.py
plus 0.360787868500 (90.81% as fast)
join 0.452811956406 (72.36% as fast)
form 0.502608060837 (65.19% as fast)
intp 0.327636957169 (100.00% as fast)

短いドメインと短いパスを使用した場合でも、補間が優先されます。ただし、文字列が長いほど違いは顕著になります。

すばらしいテストスクリプトができたので、Python 2.6、3.3、3.4でもテストしました。結果は次のとおりです。Python 2.6では、plus演算子が最速です!Python 3では、joinが優先されます。注:これらのテストは私のシステムで非常に再現可能です。したがって、2.6では「plus」が常に高速になり、2.7では「intp」が常に高速になり、Python 3.xでは「join」が常に高速になります。

# python2.6 concatenate_test.py
plus 0.338213920593 (100.00% as fast)
join 0.427221059799 (79.17% as fast)
form 0.515371084213 (65.63% as fast)
intp 0.378169059753 (89.43% as fast)

# python3.3 concatenate_test.py
plus 0.409130576998 (89.20% as fast)
join 0.364938726001 (100.00% as fast)
form 0.621366866995 (58.73% as fast)
intp 0.419064424001 (87.08% as fast)

# python3.4 concatenate_test.py
plus 0.481188605998 (85.14% as fast)
join 0.409673971997 (100.00% as fast)
form 0.652010936996 (62.83% as fast)
intp 0.460400978001 (88.98% as fast)

# python3.5 concatenate_test.py
plus 0.417167026084 (93.47% as fast)
join 0.389929617057 (100.00% as fast)
form 0.595661019906 (65.46% as fast)
intp 0.404455224983 (96.41% as fast)

学んだ教訓:

  • 時々、私の仮定は全く間違っています。
  • システム環境に対してテストします。本番環境で実行されます。
  • 文字列補間はまだ終わっていません!

tl; dr:

  • 2.6を使用する場合は、+演算子を使用します。
  • 2.7を使用している場合は、「%」演算子を使用します。
  • 3.xを使用している場合は、 ''。join()を使用します。

2
注:リテラル文字列の補間は、3.6以上ではさらに高速です。– f'http://{domain}/{lang}/{path}'
TemporalWolf

1
また、.format():速いから遅いへ順に三つの形式、持っている"{}".format(x)"{0}".format(x)"{x}".format(x=x)
TemporalWolf

本当の教訓:問題のドメインが小さい場合、たとえば短い文字列を作成する場合、メソッドはほとんどの場合重要ではありません。そして、それが重要な場合でも、たとえば実際に100万個の文字列を構築している場合は、オーバーヘッドが重要になることがよくあります。これは、間違った問題を心配する典型的な症状です。本全体を文字列として作成する場合など、オーバーヘッドが重要でない場合のみ、メソッドの違いが重要になります。
Hui Zhou

7

新しい連結が行われるたびに、新しい文字列の相対的なサイズに大きく依存します。+オペレータ、すべての連結のための新しい文字列が作られています。中間文字列が比較的長い場合、+新しい中間文字列が格納されるため、はますます遅くなります。

この場合を考えてみましょう:

from time import time
stri=''
a='aagsdfghfhdyjddtyjdhmfghmfgsdgsdfgsdfsdfsdfsdfsdfsdfddsksarigqeirnvgsdfsdgfsdfgfg'
l=[]
#case 1
t=time()
for i in range(1000):
    stri=stri+a+repr(i)
print time()-t

#case 2
t=time()
for i in xrange(1000):
    l.append(a+repr(i))
z=''.join(l)
print time()-t

#case 3
t=time()
for i in range(1000):
    stri=stri+repr(i)
print time()-t

#case 4
t=time()
for i in xrange(1000):
    l.append(repr(i))
z=''.join(l)
print time()-t

結果

1 0.00493192672729

2 0.000509023666382

3 0.00042200088501

4 0.000482797622681

1&2の場合、大きな文字列を追加し、join()は約10倍速く実行されます。ケース3と4の場合、小さな文字列を追加し、「+」はわずかに速く実行されます


3

不明なサイズの追加可能な文字列が必要な状況に遭遇しました。これらはベンチマーク結果です(python 2.7.3):

$ python -m timeit -s 's=""' 's+="a"'
10000000 loops, best of 3: 0.176 usec per loop
$ python -m timeit -s 's=[]' 's.append("a")'
10000000 loops, best of 3: 0.196 usec per loop
$ python -m timeit -s 's=""' 's="".join((s,"a"))'
100000 loops, best of 3: 16.9 usec per loop
$ python -m timeit -s 's=""' 's="%s%s"%(s,"a")'
100000 loops, best of 3: 19.4 usec per loop

これは「+ =」が最速であることを示しているようです。skymindリンクの結果は少し古くなっています。

(2番目の例は完全ではないことがわかります。最終的なリストを結合する必要があります。ただし、リストを準備するだけでは文字列連結よりも時間がかかることを示しています。)


3番目と4番目のテストで1秒未満の時間を取得しています。なぜそんなに高い時間を過ごすのですか?pastebin.com/qabNMCHS
bad_keypoints

@ronnieaka:彼はすべてのテストで1秒未満の時間を得ています。彼は3番目と4番目に1 µs超えていますが、あなたはそうではありませんでした。また、これらのテストでは時間がかかります(Python 2.7.5、Linux)。CPU、バージョン、ビルドフラグなどがわかります。
タナトス2013

これらのベンチマーク結果は役に立ちません。特に、文字列の連結を行わない最初のケースでは、2番目の文字列値をそのまま返します。
Antti Haapala

3

1年後、mkoistinenの答えをpython 3.4.3でテストしてみましょう。

  • プラス0.963564149000(95.83%高速)
  • 0.923408469000に参加(100.00%高速)
  • フォーム1.501130934000(61.51%高速)
  • intp 1.019677452000(90.56%高速)

何も変わっていません。結合はまだ最速の方法です。読みやすさの点では間違いなくintpが最良の選択であるため、それでもintpを使用することができます。


1
多分それは完全な答えより少し短いので(または少なくともあなたが使っているコードを追加するので)mkoistinenの答えへの追加かもしれません。
Trilarion、2015年

1

@JasonBakerのベンチマークに触発されて、10個の"abcdefghijklmnopqrstuvxyz"文字列を比較する単純なものを示し.join()ます。変数のこのわずかな増加でさえ:

カテネーション

>>> x = timeit.Timer(stmt='"abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz"')
>>> x.timeit()
0.9828147209324385

参加する

>>> x = timeit.Timer(stmt='"".join(["abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz"])')
>>> x.timeit()
0.6114138159765048

受け入れ答えを見て、この質問の(長いスクロールダウン):stackoverflow.com/questions/1349311/...
mshsayem

1

以下のための小さなセット短い文字列(数文字を超えないの、すなわち2つのまたは3列)、プラスの方法より速く、まだです。mkoistinenのすばらしいスクリプトをPython 2および3で使用する:

plus 2.679107467004 (100.00% as fast)
join 3.653773699996 (73.32% as fast)
form 6.594011374000 (40.63% as fast)
intp 4.568015249999 (58.65% as fast)

したがって、コードが多数の個別の小さな連結を実行している場合、速度が重要な場合はplusが推奨されます。


1

おそらく「Python 3.6の新しいf-string」は、文字列を連結する最も効率的な方法です。

%sを使用

>>> timeit.timeit("""name = "Some"
... age = 100
... '%s is %s.' % (name, age)""", number = 10000)
0.0029734770068898797

.formatの使用

>>> timeit.timeit("""name = "Some"
... age = 100
... '{} is {}.'.format(name, age)""", number = 10000)
0.004015227983472869

fの使用

>>> timeit.timeit("""name = "Some"
... age = 100
... f'{name} is {age}.'""", number = 10000)
0.0019175919878762215

ソース:https : //realpython.com/python-f-strings/

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.