12

モジュール内の多くのイテレータ「関数」は、__builtin__実際には型として実装されていますが、ドキュメントでは「関数」であると説明されています。たとえば、enumerate。ドキュメントには、次と同等であると記載されています。

def enumerate(sequence, start=0):
    n = start
    for elem in sequence:
        yield n, elem
        n += 1

もちろん、これは私が実装したものとまったく同じです。ただし、前の定義で次のテストを実行したところ、次の結果が得られました。

>>> x = enumerate(range(10))
>>> x
<generator object enumerate at 0x01ED9F08>

これは私が期待するものです。ただし、__builtin__バージョンを使用すると、次のようになります。

>>> x = enumerate(range(10))
>>> x
<enumerate object at 0x01EE9EE0>

これから、私はそれが次のように定義されていると推測します

class enumerate:
    def __init__(self, sequence, start=0):
        # ....

    def __iter__(self):
        # ...

ドキュメントが示す標準形式ではなく。これで、これがどのように機能するか、および標準フォームとどのように同等であるかが理解できました。知りたいのは、このようにする理由は何ですか。この方法はより効率的ですか?これらの関数が C で実装されていることと何か関係がありますか (関係があるかどうかはわかりませんが、関係があるのではないかと思います)。

違いが重要な場合に備えて、Python 2.7.2 を使用しています。

前もって感謝します。

4

3 に答える 3

9

はい、それはビルトインが一般的にCで実装されているという事実と関係があります。実際、Cコードは、の場合のように、単純な関数ではなく新しい型を導入することがよくありenumerateます。それらをCで記述すると、それらをより細かく制御でき、多くの場合、パフォーマンスが向上します。実際の欠点はないため、当然の選択です。

以下に相当するものを書くためにそれを考慮に入れてください:

def enumerate(sequence, start=0):
    n = start
    for elem in sequence:
        yield n, elem
        n += 1

C、つまりジェネレータの新しいインスタンスでは、実際のバイトコードを含むコードオブジェクトを作成する必要があります。これは不可能ではありませんが、Python C-APIを実装__iter__して__next__呼び出すだけの新しい型を作成するよりも簡単ではありません。さらに、別の型を持つことのその他の利点もあります。

したがって、の場合、enumerateそれreversedは単にそれがより良いパフォーマンスを提供し、より保守しやすいからです。

その他の利点は次のとおりです。

  • type(eg)にメソッドを追加できますchain.from_iterable。これは関数でも実行できますが、最初に関数を定義してから手動で属性を設定する必要があり、あまりきれいに見えません。
  • isinstanceiterablesで私たちをすることができます。これにより、いくつかの最適化が可能になる可能性があります(たとえば、isinstance(iterable, itertools.repeat)それがわかっている場合は、どの値が生成されるかがわかっているので、コードを最適化できる可能性があります。

編集:私が何を意味するのかを明確にするためだけに:

C、つまりジェネレータの新しいインスタンスでは、実際のバイトコードを含むコードオブジェクトを作成する必要があります。

インスタンスObjects/genobject.cを作成する唯一の関数を見ると、そのシグネチャは次のとおりです。PyGen_TypePyGen_New

PyObject *
PyGen_New(PyFrameObject *f)

ここで、をObjects/frameobject.c見ると、を作成するには、次の署名を持つを呼び出す必要PyFrameObjectがあることがわかります。PyFrame_New

PyFrameObject *
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
            PyObject *locals)

ご覧のとおり、インスタンスが必要です。sは、Pythonインタープリターが内部でバイトコードを表す方法です(たとえば、aは関数のバイトコードを表すことができます)。したがって、Cからインスタンスを作成するには、手動でバイトコードを作成する必要があります。この署名があるため、 sを作成するのはそれほど簡単ではありません。:PyCodeObjectPyCodeObjectPyCodeObjectPyGen_TypePyCodeObjectPyCode_New

PyCodeObject *
PyCode_New(int argcount, int kwonlyargcount,
           int nlocals, int stacksize, int flags,
           PyObject *code, PyObject *consts, PyObject *names,
           PyObject *varnames, PyObject *freevars, PyObject *cellvars,
           PyObject *filename, PyObject *name, int firstlineno,
           PyObject *lnotab)

firstlineno、などの引数がどのように含まれているかに注意してください。これらの引数filenameは、他のCコードからではなく、Pythonソースによって取得されることを意図しています。もちろん、Cで作成することもできますが、単純な新しいタイプを作成するよりも必要な文字数が少なくなるかどうかはまったくわかりません。

于 2013-02-13T19:39:00.323 に答える
2

はい、Cで実装されています。イテレータにはC API(PEP 234tp_iternext )を使用します。このAPIでは、スロットを持つ新しいタイプを作成することでイテレータが定義されます。

ジェネレーター関数の構文(yield)によって作成される関数は、特別なジェネレーターオブジェクトを返す「魔法の」関数です。これらはのインスタンスでありtypes.GeneratorType、手動で作成することはできません。C APIを使用する別のライブラリが独自のイテレータタイプを定義している場合、それはのインスタンスにはなりませんが、GeneratorTypeCAPIイテレータプロトコルを実装します。

したがって、enumerateタイプはとは異なる別個のタイプでありGeneratorType、他のタイプと同じように、などを使用して使用できますisinstance(ただし、使用しないでください)。


Bakuriuの答えとは異なりenumerate、ジェネレーターではないため、バイトコード/フレームはありません。

$ grep -i 'frame\|gen' Objects/enumobject.c
    PyObject_GenericGetAttr,        /* tp_getattro */
    PyType_GenericAlloc,            /* tp_alloc */
    PyObject_GenericGetAttr,        /* tp_getattro */
    PyType_GenericAlloc,            /* tp_alloc */

代わりに、新しいenumobjectを作成する方法はenum_new、署名がフレームを使用しない関数を使用することです。

static PyObject *
enum_new(PyTypeObject *type, PyObject *args, PyObject *kwds)

この関数は、 (タイプの)構造体のtp_newスロット内に配置されます。ここでは、スロットが関数によって占有されていることもわかります。この関数には、列挙しているイテレーターの次の項目を取得し、PyObject(タプル)を返す単純なCコードが含まれています。PyEnum_TypePyTypeObjecttp_iternextenum_next

次に進むと、名前が付いPyEnum_Typeた組み込みモジュール()に配置され、一般にアクセスできるようになります。Python/bltinmodule.cenumerate

バイトコードは必要ありません。generatortype純粋なC。純粋なPythonや実装よりもはるかに効率的です。

于 2013-02-13T20:02:39.207 に答える
1

呼び出しはenumerate反復子を返す必要があります。イテレータは、特定の API を持つオブジェクトです。特定の API を使用してクラスを実装する最も簡単な方法は、通常、それをクラスとして実装することです。

Python 2.2 より前の Python の残りの部分は型とクラスの両方を持っていたため、組み込みクラスは Python 2 では「型」と呼ばれていたため、「クラス」ではなく「型」と表示されている理由は Python 2 固有のものです。Python 2.3 では、クラスと型が統合されました。したがって、Python 3 ではクラスと呼ばれます。

>>> enumerate
<class 'enumerate'>

これにより、「関数の代わりに組み込み型があるのはなぜですか」という質問が、C で実装されていることとはほとんど関係がないことが明確になります。それらは、機能を実装する最良の方法であるため、型/クラスです。それはとても簡単です。

代わりに、あなたの質問を「なぜenumerateジェネレーターではなく型/クラスなのか」 (これは非常に異なる質問です) と解釈すると、答えも当然異なります。その答えは、ジェネレーターは Python 関数からイテレーターを作成するための Python ショートカットであるということです。それらは C からの使用を意図したものではありません。クラス メソッドからイテレータ オブジェクトを作成する場合は、オブジェクト コンテキストも渡す必要があるため、クラス メソッドからジェネレータを作成する場合よりも、関数からジェネレータを作成する場合の方が有用ではありません。しかし、関数を使用すると、これは必要ありません。したがって、ほとんどの場合、「足場」コードが少ないという利点があります。

于 2013-09-28T20:44:57.027 に答える