9

私はPythonとプログラミングの初心者です。ジェネレーターは少し複雑すぎて、新しいプログラマーには理解できません。Python のジェネレーター関数に関する私の理論は次のとおりです。

  1. ステートメントを含むすべての関数はyield、ジェネレーター オブジェクトを返します

  2. ジェネレーターオブジェクトは、状態を含むスタックです

  3. メソッドを呼び出すたびに.next、Python は関数の状態を抽出し、別の yield ステートメントを見つけると、状態を再度バインドして前の状態を削除します。

例:

 [ 
  [state1] # Stack contains states and states contain info about the function
  [state2] # State1 will be deleted when python finds the other yield? 
 ] 

もちろん、これは地球上で最もばかげた理論のようなものかもしれませんが、私はコーディング ワードの初心者であることを許してください。

私の質問:

  1. 状態を保存するためにPythonが内部的に作成するものは何ですか?

  2. yieldスタックが存在する場合、ステートメントはスタックに状態を追加しますか?

  3. 内部的にどのような利回りが作成されますか? yield がジェネレーターオブジェクトを作成することは理解していますが、それらを機能させるジェネレーターオブジェクトには何が含まれているのでしょうか? それらは単なる状態のスタック/リスト.nextですか?メソッドを使用して各状態を抽出すると、Python はインデックス付きの状態で関数を自動的に呼び出しますか?

4

1 に答える 1

16

yield ステートメントを含むすべての関数は、ジェネレーター オブジェクトを返します。

正解です。a を含む関数の戻り値はyieldジェネレータ オブジェクトです。ジェネレーター オブジェクトは反復子であり、各反復はyield、ジェネレーターをサポートするコードから編集された値を返します。

ジェネレーターオブジェクトは、状態を含むスタックです

ジェネレーター オブジェクトには、現在の実行フレームへのポインターと、ジェネレーターの状態を維持するために使用されるその他のものがすべて含まれています。実行フレームは、ジェネレータ内のコードのコール スタックを含むものです。

.next メソッドを呼び出すたびに、Python は関数の状態を抽出し、別の yield ステートメントを見つけると、状態を再度バインドして前の状態を削除します

並べ替え。を呼び出すとnext(gen_object)、Pythonは現在の実行フレームを評価します。

gen_send_ex(PyGenObject *gen, PyObject *arg, int exc) {  // This is called when you call next(gen_object)
    PyFrameObject *f = gen->gi_frame;
    ...
    gen->gi_running = 1;
    result = PyEval_EvalFrameEx(f, exc);  // This evaluates the current frame
    gen->gi_running = 0; 

PyEval_EvalFramePython バイトコードを解釈するために使用される最高レベルの関数です。

PyObject* PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)

これは、Python 解釈の主な未加工の関数です。文字通り2000行の長さです。実行フレーム f に関連付けられたコード オブジェクトが実行され、必要に応じてバイトコードが解釈され、呼び出しが実行されます。追加の throwflag パラメータはほとんど無視できます。true の場合、すぐに例外がスローされます。これは、ジェネレータ オブジェクトの throw() メソッドに使用されます。

yieldバイトコードの評価中にa にヒットすると、呼び出し元に渡された値を返す必要があることを認識しています。

    TARGET(YIELD_VALUE) {
        retval = POP();
        f->f_stacktop = stack_pointer;
        why = WHY_YIELD;
        goto fast_yield;
    }

譲ると、フレームの値スタックの現在の値が ( 経由で) 維持されるため、が再び呼び出されたf->f_stacktop = stack_pointerときに中断したところから再開できます。nextすべての非ジェネレーター関数は、評価が完了した後に に設定されますf_stacktop。そのため、ジェネレーター オブジェクトを再度NULL呼び出すと、以前と同じフレーム ポインターを使用して、 が再度呼び出されます。ポインターの状態は、前に降伏したときとまったく同じになるため、その時点から実行が続行されます。基本的に、フレームの現在の状態は「凍結」です。これは、ジェネレーターを導入した PEP で説明されています。nextPyEval_ExvalFrameEx

yield ステートメントが検出された場合、関数の状態は凍結され、値 [yielded] が .next() の呼び出し元に返されます。「凍結」とは、ローカル変数の現在のバインディング、命令ポインター、および内部評価スタックを含む、すべてのローカル状態が保持されることを意味します。十分な情報が保存されるため、次に .next() が呼び出されたときに、関数は次のことができます。 yield ステートメントが単なる別の外部呼び出しであるかのように処理します。

ジェネレーター オブジェクトが保持する状態のほとんどを次に示します (ヘッダー ファイルから直接取得)。

typedef struct {
    PyObject_HEAD
    /* The gi_ prefix is intended to remind of generator-iterator. */

    /* Note: gi_frame can be NULL if the generator is "finished" */
    struct _frame *gi_frame;

    /* True if generator is being executed. */
    char gi_running;

    /* The code object backing the generator */
    PyObject *gi_code;

    /* List of weak reference. */
    PyObject *gi_weakreflist;

    /* Name of the generator. */
    PyObject *gi_name;

    /* Qualified name of the generator. */
    PyObject *gi_qualname;
} PyGenObject;

gi_frame現在の実行フレームへのポインタです。

これはすべて CPython の実装固有であることに注意してください。PyPy/Jython/など。まったく異なる方法でジェネレーターを実装している可能性が非常に高いです。ジェネレーター オブジェクトのソースを読んで、 CPython の実装についてさらに学ぶことをお勧めします。

于 2014-08-10T21:14:45.003 に答える