2017-09-18 16 views
3

このnextメソッドが動的にクラスのインスタンスにバインドされ、反復子以外のオブジェクトを返すのはなぜですか?"動的にバインドされた` next`メソッドのiter()が反復子を返しませんでした

from collections import Iterator 
from collections import Iterable 
from types import MethodType 

def next(cls): 
    if cls.start < cls.stop: 
     cls.start += 1 
     return cls.start 
    else: 
     raise StopIteration 


class Foo(object): 
    start, stop = 0, 5 

    def __iter__(self): 
     return self 

if __name__ == "__main__": 
    foo = Foo() 
    setattr(foo, 'next', MethodType(next, foo, Foo)) 
    print hasattr(foo, "next") 
    if isinstance(foo, Iterable): 
     print "iterable" 
    if isinstance(foo, Iterator): 
     print "iterator" 

    for i in foo: 
     print i 

出力:

iterable 
True 
TypeError: iter() returned non-iterator of type 'Foo' 

私はsetattr(Foo, 'next', classmethod(next))をしたときには、適切に働いていました。

+0

このコードは何をする予定ですか? – poke

+0

次のメソッドをイテレータにするために動的にバインドする必要があります。 – QuantumEnergy

+1

通常は '__double_underscored__'という名前の特別なメソッドですが、Python 2では' next'も含まれています(Python 3では '__next__'という名前に変更されています)、インスタンスではなくクラスで定義する必要があります。たとえば、__add__メソッドを動的に追加しても同様の問題が発生します。 – Blckknght

答えて

7
for i in foo: 
    print i 

これは失敗したコードなので、のが、内部で何が起こるかを見てみましょう、いくつかのPython内部のソースに飛び込みます!

for i in fooをコンパイルすると、生成されたバイトコードにfooを反復可能に変換するGET_ITERオペコードが含まれます。 GET_ITERは、イテラブルを提供する実際の実装であるオブジェクトに対してPyObject_GetIterコールを発生させます。それでは、what it doesを見てみましょう:

PyObject * PyObject_GetIter(PyObject *o) 
{ 
    PyTypeObject *t = o->ob_type; 
    getiterfunc f = NULL; 
    if (PyType_HasFeature(t, Py_TPFLAGS_HAVE_ITER)) 
     f = t->tp_iter; 
    if (f == NULL) { 
     if (PySequence_Check(o)) 
      return PySeqIter_New(o); 
     return type_error("'%.200s' object is not iterable", o); 
    } 
    else { 
     PyObject *res = (*f)(o); 
     if (res != NULL && !PyIter_Check(res)) { 
      PyErr_Format(PyExc_TypeError, 
         "iter() returned non-iterator of type '%.100s'", 
         res->ob_type->tp_name); 
      Py_DECREF(res); 
      res = NULL; 
     } 
     return res; 
    } 
} 

(あなたは、少なくともいくつかの基本的なCを理解していれば)あなたが見ることができるように、基本となる型は最初のオブジェクト(o->ob_type)から見上げて、そのITER機能があるさ()を読んでください。

あなたがタイプに__iter__機能を実装しているので、この関数は存在しないので、私たちは、オブジェクトoiter機能を実行し、上記のコードではelse場合、に着きます。結果はnullではありませんが、まだ "反復されていないイテレータ"メッセージが返されるため、PyIter_Check(res)は失敗したようです。それでは、what that doesを見てみましょう:

#define PyIter_Check(obj) \ 
    (PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \ 
    (obj)->ob_type->tp_iternext != NULL && \ 
    (obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented) 

渡されたオブジェクトのタイプob_type)がないことが起こるない非ヌルnext法(tp_iternext)を持っているのであればこの1つは、本質的にチェックします - 次の機能を実装しました。

このチェックの結果は、のタイプにありますが、結果自体ではありません。 fooオブジェクトにはnextという機能がありますが、そのタイプはFooにはありません。

setattr(foo, 'next', MethodType(next, foo, Foo)) 

...以上明示的に...

foo.next = next.__get__(foo, Foo) 

は...唯一のタイプ自体にインスタンスにバインドnext方法を設定しませんが。したがって、上記のCコードはiterableとして消費するのに失敗します。

あなたの代わりにタイプであなたのnext機能を設定した場合、それが正常に動作します:

foo = Foo() 
Foo.next = next 

for i in foo: 
    print i 

これはclassmethodであなたの試みが働いていた理由です:あなたはタイプに機能を設定します具体的なインスタンスの代わりに

関連する問題