Pythonで文字列を連結するための好ましい方法はどれですか?


358

Pythonはstring変更できないので、文字列をより効率的に連結する方法を考えていましたか?

私はそれのように書くことができます:

s += stringfromelsewhere

またはこのように:

s = []
s.append(somestring)

later

s = ''.join(s)

この質問を書いているときに、このトピックについて話している良い記事を見つけました。

http://www.skymind.com/~ocrow/python_string/

しかし、それはPython 2.xにあるので、問題はPython 3で何か変更があったのでしょうか?


回答:


433

最高の文字列変数に文字列を追加する方法は、使用することです++=。これは、読みやすく、高速だからです。それらも同じくらい速く、どちらを選択するかは好みの問題であり、後者が最も一般的です。timeitモジュールのタイミングは次のとおりです。

a = a + b:
0.11338996887207031
a += b:
0.11040496826171875

ただし、リストを作成してリストに追加してからそれらを結合することをお勧めする場合は、リストに文字列を追加する方が、文字列を拡張する場合と比較して非常に高速であるためです。そして、これはいくつかのケースで本当である場合があります。たとえば、ここでは1文字の文字列を最初に文字列に、次にリストに100万回追加しています。

a += b:
0.10780501365661621
a.append(b):
0.1123361587524414

OK、結果の文字列が100万文字である場合でも、追加はさらに高速でした。

次に、1000文字の長い文字列を10万回追加してみましょう。

a += b:
0.41823482513427734
a.append(b):
0.010656118392944336

したがって、終了文字列は最終的に約100MBになります。これはかなり遅く、リストへの追加ははるかに高速でした。そのタイミングにはファイナルが含まれていないことa.join()。それでは、どれくらい時間がかかりますか?

a.join(a):
0.43739795684814453

おっと。この場合でも判明しますが、追加/結合は遅くなります。

それで、この勧告はどこから来たのですか?Python 2?

a += b:
0.165287017822
a.append(b):
0.0132720470428
a.join(a):
0.114929914474

まあ、極端に長い文字列を使用している場合は、追加/結合がわずかに高速になります(通常は使用しませんが、メモリ内で100MBの文字列は何でしょうか?)

しかし、実際のクリンチャーはPython 2.3です。タイミングが遅いのでまだ終わっていないので、タイミングは表示しません。これらのテストは突然数分かかります。後のPythonと同じくらい高速な追加/結合を除きます。

うん。文字列の連結は、石器時代のPythonでは非常に低速でした。しかし、2.4では(または少なくともPython 2.4.7)でなくなったため、append / joinの使用に関する推奨事項は、Python 2.3の更新が中止された2008年に古くなり、使用を中止する必要がありました。:-)

(更新:Python 2.3で2つの文字列を使用する+とテストがより慎重に行われたときに判明し、+=2つの文字列の方が高速であることがわかりました。使用するための推奨''.join()は誤解であるに違いありません)

ただし、これはCPythonです。他の実装には他の懸念があるかもしれません。そしてこれは、時期尚早な最適化がすべての悪の根源であるもう1つの理由です。最初に測定しない限り、「より高速」であると思われる手法を使用しないでください。

したがって、文字列連結を行うための「最良の」バージョンは、+または+ =を使用することです。そして、それがあなたにとって遅くなることが判明した場合、それはかなりありそうもないことですが、他のことをしてください。

では、なぜコードで多くの追加/結合を使用するのですか?時々それが実際にはっきりしているからです。特に、一緒に連結する必要があるものはすべて、スペース、コンマ、または改行で区切る必要があります。


10
複数の文字列がある場合(n> 10) "" .join(list_of_strings)はさらに高速です
Mikko Ohtamaa

11
+ =が高速である理由は、refcountが1の場合、cpythonにパフォーマンスハックがあることです-かなり特別な構成済みのpypyビルドを除いて、他のほとんどすべてのpython実装でバラバラになります
Ronny

17
なぜこれがそんなに賛成されているのですか?1つの特定の実装でのみ効率的であり、基本的には二次時間アルゴリズムを修正するための脆弱なハックに相当するアルゴリズムを使用する方が良いでしょうか。また、「時期尚早な最適化はすべての悪の根源である」という点を完全に誤解しています。その引用は小さな最適化について話している。これはO(n ^ 2)からO(n)へと進みますが、これは小さな最適化ではありません。
2012

12
実際の引用は次のとおりです。「小さな効率を忘れる必要があります。たとえば、97%程度の時間です。時期尚早な最適化がすべての悪の根源です。しかし、その重要な3%で機会を逃さないでください。優れたプログラマは、そのような推論によって自己満足に落ち着くと、彼は重要なコードを注意深く見るのが賢明でしょう;しかし、そのコードが識別された後にのみ」
Wes

2
a + bが遅いと言っている人はいません。a = a + bを2回以上実行する場合は、2次式です。a + b + cは低速ではありません。各文字列を1回トラバースするだけので、繰り返しはありませんが、a = a + bアプローチで以前の文字列を何度も再トラバースする必要があります(ループ内にあると想定)ある種の)。文字列は不変であることを忘れないでください。
ウェス

52

多数の値を連結している場合は、どちらも連結しません。リストの追加にはコストがかかります。そのためにStringIOを使用できます。特に、多くの操作でそれを構築している場合。

from cStringIO import StringIO
# python3:  from io import StringIO

buf = StringIO()

buf.write('foo')
buf.write('foo')
buf.write('foo')

buf.getvalue()
# 'foofoofoo'

他の操作から完全なリストが既に返されている場合は、 ''.join(aList)

Python FAQから:多くの文字列を連結する最も効率的な方法は何ですか?

strオブジェクトとbytesオブジェクトは不変です。そのため、多くの文字列を連結すると、それぞれの連結によって新しいオブジェクトが作成されるため、非効率的です。一般的なケースでは、合計ランタイムコストは、文字列全体の長さの2次です。

多くのstrオブジェクトを蓄積するには、それらをリストに配置し、最後にstr.join()を呼び出すことをお勧めします。

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(適度に効率的な別のイディオムはio.StringIOを使用することです)

多くのバイトオブジェクトを蓄積するための推奨慣用法は、インプレース連結(+ =演算子)を使用してbytearrayオブジェクトを拡張することです。

result = bytearray()
for b in my_bytes_objects:
    result += b

編集:私はばかげていて、結果が逆に貼り付けられていたため、リストへの追加がcStringIOよりも高速であるように見えました。また、bytearray / str concatのテスト、およびより大きな文字列を含むより大きなリストを使用した2回目のテストも追加しました。(python 2.7.3)

文字列の大きなリストのipythonテストの例

try:
    from cStringIO import StringIO
except:
    from io import StringIO

source = ['foo']*1000

%%timeit buf = StringIO()
for i in source:
    buf.write(i)
final = buf.getvalue()
# 1000 loops, best of 3: 1.27 ms per loop

%%timeit out = []
for i in source:
    out.append(i)
final = ''.join(out)
# 1000 loops, best of 3: 9.89 ms per loop

%%timeit out = bytearray()
for i in source:
    out += i
# 10000 loops, best of 3: 98.5 µs per loop

%%timeit out = ""
for i in source:
    out += i
# 10000 loops, best of 3: 161 µs per loop

## Repeat the tests with a larger list, containing
## strings that are bigger than the small string caching 
## done by the Python
source = ['foo']*1000

# cStringIO
# 10 loops, best of 3: 19.2 ms per loop

# list append and join
# 100 loops, best of 3: 144 ms per loop

# bytearray() +=
# 100 loops, best of 3: 3.8 ms per loop

# str() +=
# 100 loops, best of 3: 5.11 ms per loop

2
cStringIOPy3には存在しません。io.StringIO代わりに使用してください。
lvc 2012

2
文字列への繰り返しの追加が高価になる理由については、joelonsoftware.com
Wes

36

Python> = 3.6では、新しいf-stringは文字列を連結する効率的な方法です。

>>> name = 'some_name'
>>> number = 123
>>>
>>> f'Name is {name} and the number is {number}.'
'Name is some_name and the number is 123.'

8

推奨される方法は、追加と結合を使用することです。


1
私の答えからわかるように、これは連結する文字列の数によって異なります。私はこれについていくつかのタイミングをとりました(私の回答に関するコメントで私がリンクした講演を参照してください)。一般的に、10を超えない限り、+を使用します。
Lennart Regebro 2013

1
PEP8はこれについて言及しています(python.org/dev/peps/pep-0008/#programming-recommendations)。合理的な点は、CPythonには+ =を使用した文字列連結のための特別な最適化がありますが、他の実装ではそうでない場合があります。
Quantum7

8

連結する文字列がリテラルの場合は、文字列リテラル連結を使用します

re.compile(
        "[A-Za-z_]"       # letter or underscore
        "[A-Za-z0-9_]*"   # letter, digit or underscore
    )

これは、(上記のように)文字列の一部にコメントを付ける場合、またはすべてではなくリテラルの一部に未加工の文字列または三重引用符を使用する場合に便利です。

これは構文層で発生するため、ゼロ連結演算子を使用します。


7

あなたはこの関数を書きます

def str_join(*args):
    return ''.join(map(str, args))

その後、どこにいても簡単に電話をかけることができます

str_join('Pine')  # Returns : Pine
str_join('Pine', 'apple')  # Returns : Pineapple
str_join('Pine', 'apple', 3)  # Returns : Pineapple3

1
str_join = lambda *str_list: ''.join(s for s in str_list)
リックは2017

7

「+」による文字列連結をインプレースで使用することは、すべての値をサポートするわけではないため、安定性とクロス実装の点で最も優れた連結方法です。PEP8標準はこれを推奨せず、長期間使用するためにformat()、join()およびappend()の使用を推奨しています。

リンクされた「プログラミングの推奨事項」セクションから引用したように:

たとえば、a + = bまたはa = a + bの形式のステートメントに対して、CPythonのインプレース文字列連結の効率的な実装に依存しないでください。この最適化はCPythonでも脆弱であり(一部のタイプでのみ機能します)、refcountingを使用しない実装にはまったく存在しません。ライブラリのパフォーマンスに敏感な部分では、代わりに '' .join()形式を使用する必要があります。これにより、さまざまな実装間で連結が線形時間で行われることが保証されます。


5
参照リンクは良かったでしょう:)

6

多少古いですが、Pythonに似たコード:このセクションでは慣用的なPythonを推奨join()+ ています文字列連結に関するセクションのPythonSpeedPerformanceTipsと同様に、次の免責事項があります。

このセクションの正確性は、Pythonの以降のバージョンに関しては異議があります。CPython 2.5では、文字列の連結はかなり高速ですが、これは他のPython実装には同様に適用されない場合があります。説明については、ConcatenationTestCodeを参照してください。


6

@jdiはPythonドキュメントに言及したように使用することが提案されているstr.joinか、io.StringIO文字列の連結のために。また+=、Python 2.4以降に最適化が行われている場合でも、開発者はループ内で2次時間を期待する必要があると述べています。同様に、この答えはこう述べています。

Pythonは、左の引数に他の参照がないことを検出すると、realloc適切な位置に文字列のサイズを変更してコピーを回避しようと試みます。これは実装の詳細でありrealloc、文字列を頻繁に移動する必要がある場合、いずれにしてもパフォーマンスがO(n ^ 2)に低下するため、これは信頼できるものではありません。

+=この最適化に単純に依存していた実際のコードの例を示しますが、適用されませんでした。以下のコードは、反復可能な短い文字列を、バルクAPIで使用される大きなチャンクに変換します。

def test_concat_chunk(seq, split_by):
    result = ['']
    for item in seq:
        if len(result[-1]) + len(item) > split_by: 
            result.append('')
        result[-1] += item
    return result

このコードは、2次の時間の複雑さのため、文字通り数時間実行できます。以下は、推奨されるデータ構造を持つ代替案です。

import io

def test_stringio_chunk(seq, split_by):
    def chunk():
        buf = io.StringIO()
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                size += buf.write(item)
            else:
                yield buf.getvalue()
                buf = io.StringIO()
                size = buf.write(item)
        if size:
            yield buf.getvalue()

    return list(chunk())

def test_join_chunk(seq, split_by):
    def chunk():
        buf = []
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                buf.append(item)
                size += len(item)
            else:
                yield ''.join(buf)                
                buf.clear()
                buf.append(item)
                size = len(item)
        if size:
            yield ''.join(buf)

    return list(chunk())

そして、マイクロベンチマーク:

import timeit
import random
import string
import matplotlib.pyplot as plt

line = ''.join(random.choices(
    string.ascii_uppercase + string.digits, k=512)) + '\n'
x = []
y_concat = []
y_stringio = []
y_join = []
n = 5
for i in range(1, 11):
    x.append(i)
    seq = [line] * (20 * 2 ** 20 // len(line))
    chunk_size = i * 2 ** 20
    y_concat.append(
        timeit.timeit(lambda: test_concat_chunk(seq, chunk_size), number=n) / n)
    y_stringio.append(
        timeit.timeit(lambda: test_stringio_chunk(seq, chunk_size), number=n) / n)
    y_join.append(
        timeit.timeit(lambda: test_join_chunk(seq, chunk_size), number=n) / n)
plt.plot(x, y_concat)
plt.plot(x, y_stringio)
plt.plot(x, y_join)
plt.legend(['concat', 'stringio', 'join'], loc='upper left')
plt.show()

マイクロベンチマーク


5

さまざまな方法で行うことができます。

str1 = "Hello"
str2 = "World"
str_list = ['Hello', 'World']
str_dict = {'str1': 'Hello', 'str2': 'World'}

# Concatenating With the + Operator
print(str1 + ' ' + str2)  # Hello World

# String Formatting with the % Operator
print("%s %s" % (str1, str2))  # Hello World

# String Formatting with the { } Operators with str.format()
print("{}{}".format(str1, str2))  # Hello World
print("{0}{1}".format(str1, str2))  # Hello World
print("{str1} {str2}".format(str1=str_dict['str1'], str2=str_dict['str2']))  # Hello World
print("{str1} {str2}".format(**str_dict))  # Hello World

# Going From a List to a String in Python With .join()
print(' '.join(str_list))  # Hello World

# Python f'strings --> 3.6 onwards
print(f"{str1} {str2}")  # Hello World

以下の記事を通じて、この小さな要約を作成しました。


3

私のユースケースは少し異なりました。20を超えるフィールドが動的であるクエリを作成する必要がありました。私はフォーマット方法を使用するこのアプローチに従いました

query = "insert into {0}({1},{2},{3}) values({4}, {5}, {6})"
query.format('users','name','age','dna','suzan',1010,'nda')

+や他の方法を使用する代わりに、これは私にとっては比較的簡単でした


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