Pythonでの文字列連結と文字列置換


98

Pythonでは、文字列の連結と文字列の置換をどこで、いつ使用するかがわかりません。文字列の連結によってパフォーマンスが大幅に向上したので、これは実用的な決定ではなく、スタイルの決定(より多くなる)ですか?

具体的な例として、柔軟なURIの構築をどのように処理する必要がありますか。

DOMAIN = 'http://stackoverflow.com'
QUESTIONS = '/questions'

def so_question_uri_sub(q_num):
    return "%s%s/%d" % (DOMAIN, QUESTIONS, q_num)

def so_question_uri_cat(q_num):
    return DOMAIN + QUESTIONS + '/' + str(q_num)

編集:文字列のリストを結合すること、および名前付き置換を使用することについての提案もありました。これらは、中心的なテーマのバリエーションです。つまり、どの方法がいつそれを行う正しい方法ですか?回答ありがとうございます!


おかしい、Rubyでは、文字列補間は一般に連結よりも高速です...
Keltia

"" .join([DOMAIN、QUESTIONS、str(q_num)])を返すのを忘れた
ジミー

私はRubyの専門家ではありませんが、Rubyでは文字列が変更可能であるため、補間の方が速いと思います。文字列は、Pythonでは不変のシーケンスです。
gotgenes 2008

1
URIに関するほんの少しのコメント。URIは文字列とまったく同じではありません。URIがあるので、それらを連結または比較するときは非常に注意する必要があります。例:httpを介してポート80でその表現を配信するサーバー。ストリング。
カルコウ

回答:


55

私のマシンによれば、連結は(大幅に)高速です。しかし、文体的には、パフォーマンスが重要でない場合は、代用の代価を払う用意があります。まあ、私がフォーマットが必要な場合は、質問する必要もありません...補間/テンプレートを使用する以外に選択肢はありません。

>>> import timeit
>>> def so_q_sub(n):
...  return "%s%s/%d" % (DOMAIN, QUESTIONS, n)
...
>>> so_q_sub(1000)
'http://stackoverflow.com/questions/1000'
>>> def so_q_cat(n):
...  return DOMAIN + QUESTIONS + '/' + str(n)
...
>>> so_q_cat(1000)
'http://stackoverflow.com/questions/1000'
>>> t1 = timeit.Timer('so_q_sub(1000)','from __main__ import so_q_sub')
>>> t2 = timeit.Timer('so_q_cat(1000)','from __main__ import so_q_cat')
>>> t1.timeit(number=10000000)
12.166618871951641
>>> t2.timeit(number=10000000)
5.7813972166853773
>>> t1.timeit(number=1)
1.103492206766532e-05
>>> t2.timeit(number=1)
8.5206360154188587e-06

>>> def so_q_tmp(n):
...  return "{d}{q}/{n}".format(d=DOMAIN,q=QUESTIONS,n=n)
...
>>> so_q_tmp(1000)
'http://stackoverflow.com/questions/1000'
>>> t3= timeit.Timer('so_q_tmp(1000)','from __main__ import so_q_tmp')
>>> t3.timeit(number=10000000)
14.564135316080637

>>> def so_q_join(n):
...  return ''.join([DOMAIN,QUESTIONS,'/',str(n)])
...
>>> so_q_join(1000)
'http://stackoverflow.com/questions/1000'
>>> t4= timeit.Timer('so_q_join(1000)','from __main__ import so_q_join')
>>> t4.timeit(number=10000000)
9.4431309007150048

10
実際に大きな文字列(100000文字など)でテストを行いましたか?
drnk 2009

24

名前付き置換について忘れないでください。

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()

4
このコードには、少なくとも2つの不適切なプログラミング方法があります。グローバル変数の期待(ドメインと質問は関数内で宣言されていません)と、format()関数に必要以上の変数を渡しています。この回答は悪いコーディング慣行を教えているため、反対投票。
jperelli 16

12

文字列をループで連結することに注意してください。 文字列連結のコストは、結果の長さに比例します。ループすると、まっすぐにN-squaredの土地に移動します。一部の言語では、最後に割り当てられた文字列への連結を最適化しますが、2次アルゴリズムを線形に最適化するためにコンパイラーに頼るのは危険です。join文字列のリスト全体を受け取り、単一の割り当てを行い、それらをすべて一度に連結するプリミティブ(?)を使用するのが最善です。


16
それは最新ではありません。Pythonの最新バージョンでは、ループで文字列を連結すると、非表示の文字列バッファが作成されます。
Seun Osewa

5
@Seun:はい、私が言ったように、一部の言語は最適化されますが、それは危険な慣行です。
ノーマンラムジー

11

「文字列の連結により、パフォーマンスが大幅に向上した...」

パフォーマンスが重要な場合、これは知っておくと役に立ちます。

ただし、私が見たパフォーマンスの問題は、文字列操作に起因するものではありません。一般に、I / O、並べ替え、O(n 2)操作がボトルネックになるという問題に直面しています。

文字列操作がパフォーマンスの制限になるまで、私は明白なことに固執します。ほとんどの場合、1行以下の場合は置換、意味のある場合は連結、大きい場合はMakoなどのテンプレートツールです。


10

何を連結/補間したいか、そしてどのように結果をフォーマットしたいかが決定を左右します。

  • 文字列補間により、フォーマットを簡単に追加できます。実際、文字列補間バージョンは、連結バージョンと同じことはしません。実際には、q_numパラメーターの前に余分なスラッシュが追加されます。同じことをするにはreturn DOMAIN + QUESTIONS + "/" + str(q_num)、その例を書く必要があります。

  • 補間により、数値のフォーマットが簡単になります。"%d of %d (%2.2f%%)" % (current, total, total/current)連結形式では読みにくくなります。

  • 連結は、文字列化するアイテムの数が決まっていない場合に便利です。

また、Python 2.6では文字列テンプレートと呼ばれる新しいバージョンの文字列補間が導入されています

def so_question_uri_template(q_num):
    return "{domain}/{questions}/{num}".format(domain=DOMAIN,
                                               questions=QUESTIONS,
                                               num=q_num)

文字列テンプレートは最終的に%-interpolationに取って代わる予定ですが、それはしばらくは起こらないと思います。


まあ、それはあなたがPython 3.0に移行することを決定するたびに起こります。また、とにかく%演算子を使用して名前付き置換を実行できるという事実については、Peterのコメントを参照してください。
John Fouhy

「連結は、文字列化するアイテムの数が決まっていない場合に便利です。」-リスト/配列ですか?その場合、それらをjoin()するだけではいいのではないでしょうか?
ストレッジャー、2008

「あなたは単にそれらをjoin()しませんか?」-はい(アイテム間のセパレータを均一にする必要があると想定)。リストとジェネレーターの理解はstring.joinでうまく機能します。
Tim Lesher、

1
「まあ、それはあなたがpython 3.0に移行することを決定したときはいつでも起こります」-いいえ、py3kはまだ%演算子をサポートしています。次に考えられる非推奨ポイントは3.1であるため、まだ寿命があります。
Tim Lesher、

2
2年後... python 3.2のリリースが近づいており、%スタイルの補間はまだ問題ありません。
Corey Goldberg、2011年

8

私は、好奇心から、さまざまな文字列の連結/置換方法の速度をテストしていました。この件に関するグーグル検索で私をここに連れてきました。誰かの判断に役立つことを期待して、テスト結果を投稿すると思いました。

    import timeit
    def percent_():
            return "test %s, with number %s" % (1,2)

    def format_():
            return "test {}, with number {}".format(1,2)

    def format2_():
            return "test {1}, with number {0}".format(2,1)

    def concat_():
            return "test " + str(1) + ", with number " + str(2)

    def dotimers(func_list):
            # runs a single test for all functions in the list
            for func in func_list:
                    tmr = timeit.Timer(func)
                    res = tmr.timeit()
                    print "test " + func.func_name + ": " + str(res)

    def runtests(func_list, runs=5):
            # runs multiple tests for all functions in the list
            for i in range(runs):
                    print "----------- TEST #" + str(i + 1)
                    dotimers(func_list)

...を実行した後runtests((percent_, format_, format2_, concat_), runs=5)、これらの小さな文字列で%メソッドが他のメソッドの約2倍の速さであることがわかりました concatメソッドは常に(かろうじて)最も低速でした。format()メソッドで位置を切り替えるときに非常に小さな違いがありましたが、位置の切り替えは常に通常のフォーマットメソッドよりも少なくとも0.01遅くなりました。

テスト結果のサンプル:

    test concat_()  : 0.62  (0.61 to 0.63)
    test format_()  : 0.56  (consistently 0.56)
    test format2_() : 0.58  (0.57 to 0.59)
    test percent_() : 0.34  (0.33 to 0.35)

スクリプトで文字列連結を使用しているため、これらを実行しました。私はそれらを異なる順序で実行して、何も干渉していないことを確認したり、最初または最後にパフォーマンスを向上させたりしました。余談ですが、私はいくつかの長い文字列ジェネレーターをこれらの関数に投入しました。"%s" + ("a" * 1024)通常の連結は、formatおよび%メソッドを使用する場合と比較して、約3倍の速さ(1.1対2.8)でした。それは弦とあなたが達成しようとしていることに依存していると思います。パフォーマンスが本当に重要な場合は、さまざまなことを試してテストすることをお勧めします。スピードが問題にならない限り、スピードよりも読みやすさを選ぶ傾向がありますが、それは私だけです。SOは私のコピー/貼り付けが気に入らなかった、私はそれを正しく見えるようにするためにすべてに8つのスペースを置かなければならなかった。私は通常4を使用します。


1
プロファイリングする対象を真剣に検討する必要があります。1つは、2つのstrキャストが含まれているため、連結が遅くなります。文字列の場合、3つの文字列のみが関係する場合、文字列連結は実際にはすべての代替手段よりも高速であるため、結果は逆になります。
Justus Wingert、2015

@JustusWingert、これは今2歳です。この「テスト」を投稿して以来、多くのことを学びました。正直なところ、私は最近、通常の連結を使用str.format()str.join()ています。最近受け入れられたPEP 498からの「f-strings」にも目を光らせています。str()パフォーマンスに影響を与える呼び出しに関しては、私はあなたがそれについて正しいと確信しています。当時、関数呼び出しがどれほど高額であるかはわかりませんでした。疑わしいときにはテストをすべきだと私はまだ思っています。
Cj Welborn 2015

で簡単なテストを行った後、パーセンテージよりも遅いjoin_(): return ''.join(["test ", str(1), ", with number ", str(2)])ようjoinです。
17

4

コードの保守またはデバッグを計画している場合、スタイルの決定実際的な決定です:-) Knuthからの有名な引用があります(おそらくHoareを引用していますか)。時期尚早の最適化がすべての悪の根源です。」

O(n)タスクをO(n 2)タスクに(たとえば)変換しないように注意している限り、最もわかりやすい方を使用します。


0

可能な限り代替品を使用しています。たとえばforループで文字列を作成する場合にのみ、連結を使用します。


7
「forループ内の文字列を構築する」 -多くの場合、これはあなたが「」.joinとジェネレータ式を使用することができる場合..です
ジョンFouhy

-1

実際には、この場合(ビルドパス)には、を使用するのが適切ですos.path.join。文字列の連結または補間ではありません


1
これはosパス(ファイルシステムなど)に当てはまりますが、この例のようにURIを構築するときは当てはまりません。URIには常にセパレータとして「/」があります。
Andre Blum
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.