2017-09-18 3 views
3

next이 클래스의 인스턴스에 동적으로 바인딩 된 메서드가 실패하고 반복자가 아닌 객체를 반환하는 이유는 무엇입니까?동적으로 바인드 된 next 메소드에 대해 iter()가 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 

출력 : 나는 setattr(Foo, 'next', classmethod(next))을했을 때

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

그것은 제대로했다.

+0

이 코드는 무엇을하기로되어 있습니까? – poke

+0

다음 메소드를 iterator로 만들기 위해 동적으로 바인딩하고 싶습니다. – QuantumEnergy

+1

일반적으로'__double_underscored__' 이름이지만 Python 2에서'next' (파이썬 3에서는'__next__ '로 이름이 바뀐)라는 특수 메소드는 인스턴스가 아니라 클래스에 정의되어야합니다. 예를 들어'__add__' 메서드를 동적으로 추가하면 비슷한 문제가 발생합니다. – Blckknght

답변

7
for i in foo: 
    print i 

이 코드는 실패한 코드이므로, 내부적으로 어떤 일이 발생하는지 살펴보고 일부 파이썬 내부 용 다이빙을 살펴 보겠습니다.

for i in foo이 컴파일되면 생성 된 바이트 코드는 foo을 반복 가능한 것으로 변환하는 GET_ITER opcode를 포함하게됩니다. GET_ITER은 iterable을 제공하는 실제 구현 인 객체에서 PyObject_GetIter 호출을 발생시킵니다. (당신이 적어도 몇 가지 기본적인 C를 이해한다면) 당신이 볼 수 있듯이, 기본 유형이 먼저 객체 (o->ob_type)에서 조회됩니다

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; 
    } 
} 

, 다음의 ITER 기능은 다음과 같습니다 그럼 what it does에서 살펴 보자 읽으십시오().

당신이 유형에 __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)을하지 될 일이없는 null 이외의 next 방법 (tp_iternext)가있는 경우 확인 - 다음 기능을 구현했습니다.

확인 결과는 유형이 아니고 결과 자체가 아닙니다. foo 개체에는 next 함수가 있지만 해당 형식은 Foo입니다.

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

... 이상 명시 적으로 ...

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

는 ... 단지 유형 자체에 인스턴스에 바인딩 next 방법 을 설정하지만,하지 않습니다. 따라서 위의 C 코드는 iterable로 소비하지 못합니다.대신 유형next 기능을 설정한다면

, 그것을 잘 작동합니다 : classmethod와 시도가 일을 왜

foo = Foo() 
Foo.next = next 

for i in foo: 
    print i 

이는 이유입니다 : 당신은 유형의 기능을 설정 구체적인 인스턴스 대신

관련 문제