web-dev-qa-db-ja.com

Pythonで文字列を連結するのに適した方法はどれですか。

Pythonのstringは変更できないので、文字列をもっと効率的に連結する方法を疑問に思いましたか?

私はそれを書くことができます:

s += stringfromelsewhere

またはこのように:

s = []
s.append(somestring)

later

s = ''.join(s)

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

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

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

285
Max

文字列変数に文字列を追加する best の方法は、+または+=を使用することです。これは読みやすくて速いからです。それらも同じくらい速く、あなたが選ぶものは好みの問題であり、後者は最も一般的です。これが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

非常に長い文字列を使用している場合は、/ joinを marginally より高速に追加します(通常は使用しません。メモリ内に100MBの文字列があるとしたらどうでしょうか)。

しかし本当のクリンチャーはPython 2.3です。タイミングがあまりにも遅いのでまだ終わっていないので、私はあなたにもタイミングをあなたに示さないでしょう。これらのテストは突然かかります。 append/joinを除いて、これは後のPythonsと同じくらい速いです。

うん。石器時代のPythonでは、文字列の連結は非常に低速でした。しかし2.4ではそれはもう(あるいは少なくともPython 2.4.7)ではないので、append/joinを使用することは2008年に時代遅れになりました。Python2.3は更新されなくなり、使用をやめるべきでした。 :-)

(更新:私がより慎重にテストを行ったとき、Python 2.3でも++=を使用する方が2つの文字列のほうが速いことがわかりました。''.join()の使用を誤解する必要があります)

しかし、これはCPythonです。他の実施形態は他の懸念を有し得る。そして、これが時期尚早の最適化がすべての悪の根源であるもう一つの理由です。あなたが最初にそれを測定しない限り「より速い」と思われるテクニックを使用しないでください。

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

では、なぜ私は自分のコードでたくさんのappend/joinを使っているのでしょうか?時にはそれが実際にはっきりしているからです。特に、連結する必要があるものはすべて、スペース、コンマ、または改行で区切る必要があります。

364
Lennart Regebro

たくさんの値を連結しているのであれば、どちらもそうではありません。リストを追加することは高価です。それには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)

(もう1つの合理的に効率的なイディオムはio.StringIOを使用することです)

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

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

編集:リストへの追加がcStringIOより速いように、愚かで結果が逆に貼り付けられていました。また、bytearray/str concatのためのテストを追加しました。大きな文字列を含むリスト(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
40
jdi

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.'
13
SuperNova

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

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

これは、(上記のように)文字列の一部にコメントを付けたい場合、またはリテラルの一部に 生の文字列 または三重引用符を使用したい場合に便利です。

これは構文層で行われるので、ゼロ連結演算子を使用します。

7
droid

推奨される方法はまだappendとjoinを使うことです。

7
MRAB

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

6
badslacks

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

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

このコードは、二次的な時間の複雑さのために、数時間文学的に実行できます。以下は、推奨されるデータ構造の代替案です。

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

micro-benchmark

5
saaj

やや古くなっていますが、 Pythonistaのようなコード:慣用的なPython+このセクションでは よりもjoin()を推奨します。 PythonSpeedPerformanceTips文字列の連結 の節と同様に、以下の免責事項があります。

このセクションの正確さはPythonのそれ以降のバージョンに関して論じられています。 CPython 2.5では、文字列の連結はかなり速いですが、他のPythonの実装には同じようには当てはまりません。議論はConcatenationTestCodeを見てください。

5
Levon

この関数を書く

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

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

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

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

2

私のユースケースは少し異なっていました。私は20以上のフィールドが動的であるクエリを構築しなければなりませんでした。私はフォーマット方法を使用するというこのアプローチに従った

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

+や他の方法を使うよりも、これは私にとっては比較的簡単でした。

2
ishwar rimal

これを(もっと効率的に)使うこともできます。 ( https://softwareengineering.stackexchange.com/questions/304445/why-is-s-better-than-for-concatenation

s += "%s" %(stringfromelsewhere)
1
SuperNova