38

私はこの質問、特に @David Robinson と @mgilson からの最初の回答へのコメントを参照しています: Sum the second value of each tuple in a list

元の質問は、各チューブの 2 番目の値を合計することでした。

structure = [('a', 1), ('b', 3), ('c', 2)]

最初の答え:

sum(n for _, n in structure)

2番目の答え:

sum(x[1] for x in structure)

議論によると、最初の答えは 50% 高速です。

最初の答えが何をするのかを理解したら (Perl から来て、python で特別な _ variable の意味をグーグルで検索しました)、純粋なサブセット タスク (各タプルの 2 番目の要素のみを取得する vs.変数へのバインドと両方の要素) は実際には遅いですか? Python でインデックス アクセスを最適化する機会が失われているのでしょうか? 時間がかかる2番目の答えが欠けていますか?

4

2 に答える 2

37

Pythonのバイトコードを見ると、解凍が速い理由がすぐにわかります。

>>> import dis
>>> def unpack_or_index(t=(0, 1)):
...     _, x = t
...     x = t[1]
... 
>>> dis.dis(unpack_or_index)
  2           0 LOAD_FAST                0 (t)
              3 UNPACK_SEQUENCE          2
              6 STORE_FAST               1 (_)
              9 STORE_FAST               2 (x)

  3          12 LOAD_FAST                0 (t)
             15 LOAD_CONST               1 (1)
             18 BINARY_SUBSCR       
             19 STORE_FAST               2 (x)
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE        

タプルのアンパック操作は単純なバイトコード(UNPACK_SEQUENCE)ですが、インデックス作成操作はタプル()のメソッドを呼び出す必要がありBINARY_SUBSCRます。アンパック操作は、Python評価ループでインラインで実行できますが、サブスクリプション呼び出しでは、を使用して値を取得するためにタプルオブジェクトの関数を検索する必要がありますPyObject_GetItem

UNPACK_SEQUENCEオペコードのソースコードは、シーケンスの長さが引数の長さと正確に一致するPythonタプルまたはリストアンパックを特殊なケースに使用します。

        if (PyTuple_CheckExact(v) &&
            PyTuple_GET_SIZE(v) == oparg) {
            PyObject **items = \
                ((PyTupleObject *)v)->ob_item;
            while (oparg--) {
                w = items[oparg];
                Py_INCREF(w);
                PUSH(w);
            }
            Py_DECREF(v);
            continue;
        } // followed by an "else if" statement for a list with similar code

上記のコードはタプルのネイティブ構造に到達し、値を直接取得します。PyObject_GetItemオブジェクトがカスタムPythonクラスである可能性があることを考慮に入れる必要があるような重い呼び出しを使用する必要はありません。

BINARY_SUBSCRオペコードはPythonリスト用にのみ最適化されています; ネイティブのPythonリストではないものはすべて、PyObject_GetItem呼び出しが必要です。

于 2012-10-23T06:19:28.010 に答える
18

インデックス作成は__getitem__特別な方法で行われるため、各項目に対して関数の検索と実行を行う必要があります。つまり、nアイテムのリストの場合、n検索/呼び出しを行うことになります。

ネイティブのリスト/タプルを操作する場合、アンパックはそれに対処する必要はありません。__iter__単一の呼び出しであるだけを通過し、結果のシーケンスを C でアンパックします。

于 2012-10-23T06:11:52.103 に答える