4

次のことを行う 2 つの関数を考えてみましょう: 単語を指定すると、単語のすべての位置がアルファベットのすべての文字に置き換えられたリストが生成されます。

alphabet = 'abcdefghijklmnopqrstuvwxyz'

バージョン 1 (Pythonic):

def replace1_word( word ):
    return [word[:w]+alphabet[i]+word[w+1:] 
            for w in range(len(word)) 
            for i in range(len(alphabet))]

バージョン 2 (非 Python):

def replace1_word2( word ):
    res=[]
    for w in range(len(word)):
        for i in range(len(alphabet)):
            res.append( word[:w] + alphabet[i] + word[w+1:] )
    return res

モジュールを使用timeitして 1000 回実行し、タイミングを測定したところ、平均実行時間の差は0.028ミリ秒から0.040ミリ秒の間になりました。

私の質問は、コードのどの部分/行が 2 番目のバージョンでコストがかかるのか、そしてその理由は? どちらも同じように機能し、同じ結果をリスト形式で返します。

4

6 に答える 6

6

私の質問は、コードのどの部分/行が 2 番目のバージョンでコストがかかるのか、そしてその理由は? どちらも同じように機能し、同じ結果をリスト形式で返します。

いいえ、そうではありません。疑わしい場合は、常にプロファイルを作成してください。各操作のコストの全体像が示されます。以下の O/P を見るだけで、2 番目の関数で何がコストがかかるかがわかりますか?

>>> cProfile.run("replace1_word2('foo bar baz')")
         313 function calls in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <pyshell#216>:1(replace1_word2)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
       12    0.000    0.000    0.000    0.000 {len}
      286    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
       12    0.000    0.000    0.000    0.000 {range}


>>> cProfile.run("replace1_word('foo bar baz')")
         27 function calls in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <pyshell#220>:1(replace1_word)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
       12    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
       12    0.000    0.000    0.000    0.000 {range}

append を置き換えるもの (またはバージョン 1 がどのようにリストを生成するか) を説明するように注意してください

Python では、関数呼び出しに余分なオーバーヘッドがあります。前者の場合、list.appendは複数回呼び出されますが、リスト内包表記の場合と同様に、リストは単一の式として生成されます。したがって、ある意味では、ループ構造を持つリスト内包表記には同等の表記法はありません。リスト内包表記は、ループの装飾された構文ではなく、Python の強力なツールです。

エピローグ

この問題を解決する関数を書くように言われたら、私は次のようになります

>>> from itertools import product
>>> from string import ascii_lowercase
>>> def replace1_word4(word):
         words = ('{}'.join([word[:w], word[w+1:]]) for w in range(len(word)))
         return [word.format(replace)
                 for  word, replace in product(words, ascii_lowercase)]
于 2013-10-04T16:39:10.027 に答える
2

おそらく、リスト内包表記が評価される方法list.appendと、コレクター変数に対する for ループに関係している可能性があります。2 番目のスニペットを使用するように変更し、yieldその結果を でラップするとlist()、パフォーマンスは最初のバージョンに近づきます。

def replace1_word3(word):
    for w in range(len(word)):
        for i in range(len(alphabet)):
            yield word[:w] + alphabet[i] + word[w+1:]

ベンチマーク:

In [18]: timeit replace1_word('foo bar baz ' * 100)
10 loops, best of 3: 38.7 ms per loop

In [19]: timeit replace1_word2('foo bar baz ' * 100)
10 loops, best of 3: 42.1 ms per loop

In [20]: timeit list(replace1_word3('foo bar baz ' * 100))
10 loops, best of 3: 39.7 ms per loop

残りの違いは、実際のリストがリスト内包表記で内部的に構築される方法とyield=> generator=>のパフォーマンスに起因する可能性がありますlist()

PS Abhijit の答えは、おそらくより技術的な用語でなぜreplace1_word速いのかを説明できます。いずれにせよ、list.append私が推測したように、それが犯人のようです。

于 2013-10-04T16:32:00.233 に答える
0

ループインpythonは遅くなる可能性があります。ネストされたループ バージョンは、for ループのインタープリター オーバーヘッドが余分にかかるため、2 つのうち遅い方です。 https://wiki.python.org/moin/PythonSpeed/PerformanceTips

于 2013-10-04T16:32:19.757 に答える
0

これらのどちらも特に Pythonic ではないと思います。ずっといい:

def replace1_word2( word ):
    res=[]
    for w, letter in enumerate(word):
        for alpha in alphabet:
            res.append(word[:w] + alpha + word[w+1:])
    return res
于 2013-10-04T16:38:49.167 に答える