文字列には+=演算子があって、これを使えば、カンタンに2つの文字列を結合できますよね。でも、使い過ぎには注意が必要です。たくさんの文字列をサクッと結合する方法、分かりますか?
以下はリストwordsに格納されている文字列要素を結合して、その結果は変数resultに代入するコードだ。このコードは何をするかが一目瞭然で分かりやすいといえる。これよりも良い記述方法を考えてみよう。
words = ['foo', 'bar', 'baz', 'qux', 'quux']
result = ''
for word in words:
result += word
print(result)
以下に正解のコード例を示します。
words = ['foo', 'bar', 'baz', 'qux', 'quux']
result = ''.join(words)
print(result)
文字列にはjoinメソッドがあります。これは引数として与えた反復可能オブジェクトの文字列要素を、そのメソッドの呼び出しで使用した文字列を区切り文字として使って結合するものです。上のコード例では、空文字列「''」が区切り文字なので、実際にはリストの要素がそのまま結合されて「foobarbazquxquux」という文字列が得られます。
他にもやり方はあるでしょうが、ここでは正解例として上のコードを挙げました。
問題文のコードは次のようなものでした。
words = ['foo', 'bar', 'baz', 'qux', 'quux']
result = ''
for word in words:
result += word
print(result)
+=演算子による文字列の結合は分かりやすいのですが、問題点もあります。Pythonの文字列はイミュータブル(変更不可能)なオブジェクトです。つまり、for文の中で「result += word」行が実行されるたびに「新たな文字列オブジェクトが作成され、以前の文字列は捨てられる」という非効率的な処理が行われているのです。これは処理速度の面でもメモリの消費量という面でもあまりよろしくはないでしょう。
一方、joinメソッドには次のような特徴があります。
こうした特徴から+=演算子で文字列を何度も結合していくよりも、処理速度/メモリ消費量の両面で効率的なやり方だといえます。
実際に試してみましょう。問題文のコードではその差が見えにくいかもしれないので、以下では10万個の文字列要素を持つリストを例として見ます。
from time import time
words = ['foo'] * 100000
result = ''
st = time()
for word in words:
result += word
ed = time()
print(f'by += op: {ed - st}')
st = time()
result = ''.join(words)
ed = time()
print(f'by join: {ed - st}')
筆者の手元の環境で、このコードを実行した結果を以下に示します。
joinメソッドを使った方法が圧倒的に速いことが分かりました。ではメモリの消費量はどうでしょうか? ここではPythonに標準添付のtracemallocモジュールを使って確認してみます。
import tracemalloc
words = ['foo'] * 100000
result = ''
tracemalloc.start()
for word in words:
result += word
mem_size_now, mem_size_peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f'by += op: now {mem_size_now}, peak {mem_size_peak}')
tracemalloc.start()
result = ''.join(words)
mem_size_now, mem_size_peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f'by join: now {mem_size_now}, peak {mem_size_peak}')
tracemallocモジュールのstart関数はメモリ割り当てのトレースを開始し、stop関数はトレースを終了します。get_traced_memory関数は割り当てられたメモリブロックの現在のサイズと最大のサイズを取得するものです。上のコードでは、これらの関数を使って、+=演算子による文字列結合で割り当てられたメモリのサイズと、joinメソッドによる文字列結合で割り当てられたメモリのサイズを確認しています。
実行結果は次の通りです。
現在のサイズは似たようなものになっています。これは最終的な結果が同じだからでしょう。しかし、最大サイズを見るとその差は歴然としています。+=演算子を使う方法では、文字列の生成と破棄がそれだけ多く行われているということです。
というわけで、メモリ消費量の面でもjoinメソッドの方が効率的であることがよく分かりました。文字列の結合には可能であればjoinメソッドを使うことをオススメします。
実は上で挙げた以外の方法も試してみました。例えば、リスト内包表記やジェネレータ式やmap関数を使う方法などがそうです(リストの要素が文字列以外だと、str関数で要素を文字列化するためにそうした方法を採ることになるでしょう)。でも、これらの方法でも、最終的にjoinメソッドで結合することに落ち着いちゃうんですよね。
正解例のjoinメソッドを使うコードに追加の処理が加わるという意味では、実行速度が向上することはないでしょう。さらに、メモリ消費量を考えると、実はjoinメソッドの内部では全ての要素をいったん取得しないと、最終的な文字列のサイズが分かりません。なので、ジェネレータ式やmap関数でメモリ消費量を抑えるというつもりがそうでもない結果になってしまいました。そのために書いたコードはここには登場しません(SSDのゴミとしてどこかにガベージコレクションされました)。
興味のある方はご自分でも試してみてください。そして、「もっと効率的な方法があるよ!」という方はぜひその方法を教えてくれると幸いです(今回は記事へのリンクはなしです。関連がありそうな記事を思い付きませんでした)。
初心者向け、データ分析・AI・機械学習・Pythonの勉強方法 @ITのDeep Insiderで学ぼう
Copyright© Digital Advantage Corp. All Rights Reserved.
Deep Insider 鬯ッ�ッ�ス�ッ�ス�ス�ス�ッ�ス�ス�ス�ス�ス�ス�ス�ョ�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ォ�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ェ鬯ッ�ッ�ス�ョ�ス�ス�ス�ッ鬮ッ蜈キ�ス�ケ�ス�ス�ス�コ�ス�ス�ス�ス�ス�ス�ス�サ鬩幢ス「�ス�ァ髫ー�ス竏橸ソス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ソ�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�コ鬯ッ�ッ�ス�ョ�ス�ス�ス�」鬮ッ蜈キ�ス�ケ�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�オ鬯ッ�ョ�ス�ォ�ス�ス�ス�エ鬩包スカ隰ォ�セ�ス�ス�ス�オ�ス�ス�ス�ス�ス�ス�ス�コ�ス�ス�ス�ス�ス�ス�ス�キ�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ク�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�キ�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ケ鬯ッ�ョ�ス�ォ�ス�ス�ス�エ鬯ョ�ョ隲幢スカ�ス�ス�ス�」�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�「�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ウ鬯ッ�ッ�ス�ッ�ス�ス�ス�ゥ鬮ッ譎「�ス�キ�ス�ス�ス�「�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�「�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ァ�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ュ鬯ッ�ッ�ス�ッ�ス�ス�ス�ゥ鬮ッ譎「�ス�キ�ス�ス�ス�「�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�「鬯ッ�ョ�ス�ォ�ス�ス�ス�エ鬯ョ�ョ隲幢スカ�ス�ス�ス�」�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�「�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ウ鬯ッ�ッ�ス�ッ�ス�ス�ス�ゥ鬮ッ譎「�ス�キ�ス�ス�ス�「�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�「�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ァ�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ス�ー