기본 파이썬 반복기 빌드


569

파이썬에서 반복 함수 (또는 반복자 객체)를 어떻게 만들 수 있습니까?

답변:


650

반복자는 파이썬의 객체는 기본적으로 그들은 두 가지 방법을 제공하는 것을 의미 반복자 프로토콜을 준수 : __iter__()__next__().

  • __iter__반복자 객체를 반환하며 루프 시작시 암시 적으로 호출됩니다.

  • __next__()메소드는 다음 값을 리턴하며 각 루프 증분마다 내재적으로 호출됩니다. 이 메소드는 리턴 할 값이 더 이상 없을 때 StopIteration 예외를 발생시킵니다. 이는 반복을 중지하기 위해 루프 구문을 통해 암시 적으로 캡처됩니다.

다음은 간단한 카운터 예입니다.

class Counter:
    def __init__(self, low, high):
        self.current = low - 1
        self.high = high

    def __iter__(self):
        return self

    def __next__(self): # Python 2: def next(self)
        self.current += 1
        if self.current < self.high:
            return self.current
        raise StopIteration


for c in Counter(3, 9):
    print(c)

인쇄됩니다 :

3
4
5
6
7
8

이전 답변에서 다룬 것처럼 생성기를 사용하여 작성하는 것이 더 쉽습니다.

def counter(low, high):
    current = low
    while current < high:
        yield current
        current += 1

for c in counter(3, 9):
    print(c)

인쇄 된 출력물이 동일합니다. 후드에서 생성기 객체는 반복기 프로토콜을 지원하고 클래스 클래스와 거의 비슷한 작업을 수행합니다.

David Mertz의 기사 인 Iterators와 Simple Generators 는 꽤 좋은 소개입니다.


4
이것은 대부분 좋은 대답이지만, 자기를 돌려주는 사실은 조금 차선책입니다. 예를 들어 이중 중첩 for 루프에서 동일한 카운터 개체를 사용한 경우 의도 한 동작을 얻지 못할 수 있습니다.
Casey Rodarmor 2013

22
아니요, 반복자는 스스로 반환해야합니다. 이터 러블은 이터레이터를 반환하지만 이터 러블은 구현하지 않아야합니다 __next__. counter반복자이지만 시퀀스가 ​​아닙니다. 값을 저장하지 않습니다. 예를 들어 이중 중첩 for 루프에서 카운터를 사용해서는 안됩니다.
leewz

4
카운터 예제에서 self.current는 __iter__에 추가 해야합니다 __init__. 그렇지 않으면 객체는 한 번만 반복 될 수 있습니다. 예를 들어,라고 ctr = Counters(3, 8)하면 for c in ctr두 번 이상 사용할 수 없습니다 .
Curt

7
@ 커트 : 물론 아닙니다. Counter반복자이며 반복자는 한 번만 반복해야합니다. self.current에서 재설정 __iter__하면 중첩 루프 Counter가 완전히 끊어지고 반복자의 모든 가정 된 동작 (이를 호출 iter하는 것이 dem 등원)에 위배됩니다. 반복을 ctr두 번 이상 반복 하려면 반복자가 아닌 반복 가능해야하며 __iter__호출 할 때마다 새로운 반복자를 반환합니다 . 믹스 앤 매치 ( __iter__호출 시 암시 적으로 재설정되는 반복자)를 시도 하면 프로토콜을 위반합니다.
ShadowRanger

2
예를 들어, Counter반복자가 아닌 반복자라면 __next__/ 의 정의를 제거 하고이 대답의 끝에 설명 된 생성기와 동일한 형식의 생성기 함수로 next재정 __iter__의 할 수 있습니다 (범위 대신 제외) 인수에서 오는하기 __iter__에, 그들은 인수 것 __init__에 저장 self에서와 액세스 self에서 __iter__).
ShadowRanger

427

반복 함수를 작성하는 네 가지 방법이 있습니다.

예 :

# generator
def uc_gen(text):
    for char in text.upper():
        yield char

# generator expression
def uc_genexp(text):
    return (char for char in text.upper())

# iterator protocol
class uc_iter():
    def __init__(self, text):
        self.text = text.upper()
        self.index = 0
    def __iter__(self):
        return self
    def __next__(self):
        try:
            result = self.text[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return result

# getitem method
class uc_getitem():
    def __init__(self, text):
        self.text = text.upper()
    def __getitem__(self, index):
        return self.text[index]

네 가지 방법을 모두 보려면 :

for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
    for ch in iterator('abcde'):
        print(ch, end=' ')
    print()

결과 :

A B C D E
A B C D E
A B C D E
A B C D E

참고 :

두 발전기 유형 ( uc_genuc_genexp)은 reversed(); 평범한 반복자 ( uc_iter)는 __reversed__마술 방법 이 필요합니다 ( docs에 따르면 새로운 반복자를 반환하지만 self작업을 반환해야 합니다 (적어도 CPython에서)). 그리고 getitem iteratable ( uc_getitem)에는 __len__마술 방법 이 있어야합니다 .

    # for uc_iter we add __reversed__ and update __next__
    def __reversed__(self):
        self.index = -1
        return self
    def __next__(self):
        try:
            result = self.text[self.index]
        except IndexError:
            raise StopIteration
        self.index += -1 if self.index < 0 else +1
        return result

    # for uc_getitem
    def __len__(self)
        return len(self.text)

무한 게으르게 평가 된 반복자에 대한 Panic 대령의 두 번째 질문에 대답하기 위해 위의 네 가지 방법 각각을 사용하는 예제가 있습니다.

# generator
def even_gen():
    result = 0
    while True:
        yield result
        result += 2


# generator expression
def even_genexp():
    return (num for num in even_gen())  # or even_iter or even_getitem
                                        # not much value under these circumstances

# iterator protocol
class even_iter():
    def __init__(self):
        self.value = 0
    def __iter__(self):
        return self
    def __next__(self):
        next_value = self.value
        self.value += 2
        return next_value

# getitem method
class even_getitem():
    def __getitem__(self, index):
        return index * 2

import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
    limit = random.randint(15, 30)
    count = 0
    for even in iterator():
        print even,
        count += 1
        if count >= limit:
            break
    print

결과는 (적어도 내 샘플 실행의 경우) :

0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32

사용할 것을 선택하는 방법? 이것은 대부분 맛의 문제입니다. 내가 가장 자주 보는 두 가지 방법은 생성기 및 반복기 프로토콜뿐만 아니라 하이브리드 ( __iter__생성기 반환)입니다.

생성기 표현식은 목록 이해를 대체하는 데 유용합니다 (게 으르므로 리소스를 절약 할 수 있음).

이전 Python 2.x 버전과의 호환성이 필요한 경우을 사용하십시오 __getitem__.


4
나는이 요약이 완전하기 때문에 좋아한다. 이 세 가지 방법 (수율, 생성기 표현 및 반복기)은 본질적으로 동일하지만 일부는 다른 것보다 더 편리합니다. 수익률 연산자는 상태 (예 : 우리가 사용하는 인덱스)를 포함하는 "계속"을 캡처합니다. 정보는 연속의 "폐쇄"에 저장됩니다. 반복자 방식은 반복자 필드 내에 동일한 정보를 저장하는데, 이는 본질적으로 클로저와 동일합니다. 의 getItem의 내용으로 인덱스가 반복적 인 자연에 있지 않기 때문에 방법은 조금 다르다.
Ian

2
@metaperl : 사실입니다. 위의 네 가지 경우 모두 동일한 코드를 사용하여 반복 할 수 있습니다.
Ethan Furman

1
@ 별표 : 아니요, 인스턴스가 uc_iter완료되면 만료되어야합니다 (그렇지 않으면 무한대로). 다시하고 싶다면 다시 전화하여 새 반복자를 가져와야 uc_iter()합니다.
Ethan Furman

2
당신은 설정할 수 self.index = 0있는 __iter__당신을 통해 여러 번 반복 할 수 있도록. 그렇지 않으면 당신은 할 수 없습니다.
John Strood

1
시간을 절약 할 수 있다면 다른 방법보다 방법을 선택하는 이유에 대한 설명을 부탁드립니다.
aaaaaa

103

우선 itertools 모듈 은 반복자가 유용한 모든 종류의 경우에 매우 유용하지만 파이썬에서 반복자를 작성하는 데 필요한 모든 것입니다.

수율

멋지지 않습니까? 수익률은 함수 의 정규 수익 을 대체하는 데 사용할 수 있습니다 . 객체를 동일하게 반환하지만 상태를 파괴하고 종료하는 대신 다음 반복을 실행할 때의 상태를 저장합니다. itertools 함수 목록 에서 직접 가져온 조치의 예는 다음과 같습니다 .

def count(n=0):
    while True:
        yield n
        n += 1

함수 설명 ( itertools 모듈 의 count () 함수입니다 ...)에 명시된 것처럼 n으로 시작하는 연속 정수를 반환하는 반복자를 생성합니다.

생성기 표현 은 다른 웜 캔 (굉장한 웜)입니다. 그것들은 메모리를 절약하기 위해 List Comprehension 대신에 사용될 수 있습니다 (list comprehension은 변수에 할당되지 않은 경우 사용 후 파괴 된 메모리에 목록을 생성하지만 제너레이터 표현식은 Generator Object를 생성 할 수 있습니다 ... 반복자). 다음은 생성기 표현식 정의의 예입니다.

gen = (n for n in xrange(0,11))

이것은 전체 범위가 0에서 10 사이로 미리 결정된 것을 제외하고는 위의 반복자 정의와 매우 유사합니다.

방금 xrange () (이전에는 보지 못했지만 ...)를 발견하고 위의 예제에 추가했습니다. xrange () 는 리스트를 미리 빌드하지 않는 이점이있는 range () 의 반복 가능한 버전입니다 . 반복 할 거대한 데이터 모음이 있고 메모리가 너무 많으면 매우 유용합니다.


20
파이썬 3.0의 같은 xrange ()와 새로운 범위 () 이전 xrange 같은 동작합니다 ()는 더 이상 없다

6
2_3이 자동으로 변환하기 때문에 2._에서 xrange를 계속 사용해야합니다.
Phob

100

나는 당신이 어떤 일을 볼 수 return self에서 __iter__. 난 그냥 참고로 원 __iter__자체가 (따라서에 대한 필요성을 제거하는 발전기가 될 수 __next__및 양육 StopIteration예외)

class range:
  def __init__(self,a,b):
    self.a = a
    self.b = b
  def __iter__(self):
    i = self.a
    while i < self.b:
      yield i
      i+=1

물론 여기서 직접 생성기를 만들 수도 있지만 더 복잡한 클래스의 경우 유용 할 수 있습니다.


5
큰! 그것은 그래서 그냥 쓰기 지루한 return self에서 __iter__. 내가 yield그것을 사용하려고 할 때 나는 당신의 코드가 내가 원하는 것을 정확하게하고 있음을 발견했다.
Ray

3
그러나이 경우 어떻게 구현 next()할까요? return iter(self).next()?
Lenna

4
@Lenna, iter (self)는 범위 인스턴스가 아닌 반복자를 반환하기 때문에 이미 "구현되었습니다".
Manux

3
이것은 가장 쉬운 방법이며 예를 들어 self.current다른 카운터를 추적하지 않아도됩니다 . 이것은 최고의 투표 답변이어야합니다!
astrofrog

4
분명히,이 접근법은 클래스를 반복 가능 하게 만들지 만 반복자 는 만들 수 없습니다 . 클래스의 인스턴스 를 호출 할 때마다 새로운 반복자가 생성iter 되지만 그 자체가 클래스의 인스턴스가 아닙니다.
ShadowRanger

13

이 질문은 반복자가 아닌 반복 가능한 객체에 관한 것입니다. 파이썬에서 시퀀스는 반복 가능하므로 반복 가능한 클래스를 만드는 한 가지 방법은 시퀀스와 같은 동작, 즉 메소드 __getitem____len__메소드를 제공하는 것 입니다. 파이썬 2와 3에서 이것을 테스트했습니다.

class CustomRange:

    def __init__(self, low, high):
        self.low = low
        self.high = high

    def __getitem__(self, item):
        if item >= len(self):
            raise IndexError("CustomRange index out of range")
        return self.low + item

    def __len__(self):
        return self.high - self.low


cr = CustomRange(0, 10)
for i in cr:
    print(i)

1
메소드가 필요하지 않습니다 __len__(). __getitem__예상되는 행동만으로 충분합니다.
BlackJack

5

이 페이지의 모든 답변은 복잡한 개체에 정말 좋습니다. 그러나 속성으로 반복 가능한 종류의 내장 포함 된 사람들을 위해, 같은 str, list, set또는 dict, 또는의 구현 collections.Iterable, 당신은 당신의 수업 시간에 어떤 일을 생략 할 수 있습니다.

class Test(object):
    def __init__(self, string):
        self.string = string

    def __iter__(self):
        # since your string is already iterable
        return (ch for ch in self.string)
        # or simply
        return self.string.__iter__()
        # also
        return iter(self.string)

다음과 같이 사용할 수 있습니다.

for x in Test("abcde"):
    print(x)

# prints
# a
# b
# c
# d
# e

1
당신이 말했듯이, 문자열은 이미 반복 가능하므로 반복자 (내부 생성기 표현식이 내부적으로 수행)에 대한 문자열을 요청하는 대신 여분의 생성기 표현식이 왜 사이에 있어야합니까 return iter(self.string)?
BlackJack

@BlackJack 당신이 맞아요. 나는 그런 식으로 글을 쓰도록 설득 한 것을 모른다. 아마도 더 많은 반복자 구문의 관점에서 반복자 구문의 작동을 설명하려는 답변에서 혼란을 피하려고 노력했을 것입니다.
John Strood

3

이없는 반복 가능한 함수 yield입니다. 그것은 사용할 수 있도록 iter기능과 변경 가능한 (에서의 상태를 유지하는 폐쇄 list파이썬 2의 둘러싸 범위를).

def count(low, high):
    counter = [0]
    def tmp():
        val = low + counter[0]
        if val < high:
            counter[0] += 1
            return val
        return None
    return iter(tmp, None)

Python 3의 경우 클로저 상태는 둘러싸는 범위에서 변경할 수 없으며 nonlocal로컬 변수에서 상태 변수를 업데이트하는 데 사용됩니다.

def count(low, high):
    counter = 0
    def tmp():
        nonlocal counter
        val = low + counter
        if val < high:
            counter += 1
            return val
        return None
    return iter(tmp, None)  

테스트;

for i in count(1,10):
    print(i)
1
2
3
4
5
6
7
8
9

나는 항상 two-arg을 영리하게 사용하는 것에 감사 iter하지만 명확하게 말하면 : 이것은 yield기반 생성기 함수를 사용하는 것보다 더 복잡하고 덜 효율적 이다. 파이썬은 yield여기에서 이용할 수없는 기반 생성기 함수에 대한 수많은 인터프리터 지원을 통해이 코드를 상당히 느리게 만듭니다. 그럼에도 불구하고 찬성.
ShadowRanger

2

짧고 간단한 것을 찾고 있다면 아마도 충분할 것입니다.

class A(object):
    def __init__(self, l):
        self.data = l

    def __iter__(self):
        return iter(self.data)

사용 예 :

In [3]: a = A([2,3,4])

In [4]: [i for i in a]
Out[4]: [2, 3, 4]

-1

Matt Gregory의 대답에서 영감을 얻은 것은 a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz를 반환하는 좀 더 복잡한 반복자입니다.

    class AlphaCounter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def __next__(self): # Python 3: def __next__(self)
        alpha = ' abcdefghijklmnopqrstuvwxyz'
        n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
        n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
        if n_current > n_high:
            raise StopIteration
        else:
            increment = True
            ret = ''
            for x in self.current[::-1]:
                if 'z' == x:
                    if increment:
                        ret += 'a'
                    else:
                        ret += 'z'
                else:
                    if increment:
                        ret += alpha[alpha.find(x)+1]
                        increment = False
                    else:
                        ret += x
            if increment:
                ret += 'a'
            tmp = self.current
            self.current = ret[::-1]
            return tmp

for c in AlphaCounter('a', 'zzz'):
    print(c)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.