문제
파이썬에서 반복 함수(또는 반복자 객체)를 어떻게 생성합니까?
해결책
Python의 Iterator 객체는 반복자 프로토콜을 준수합니다. 이는 기본적으로 두 가지 메서드를 제공한다는 의미입니다. __iter__()
그리고 next()
.그만큼 __iter__
반복자 객체를 반환하고 루프 시작 시 암시적으로 호출됩니다.그만큼 next()
메서드는 다음 값을 반환하고 각 루프 증분에서 암시적으로 호출됩니다. next()
반환할 값이 더 이상 없을 때 StopIteration 예외를 발생시킵니다. 이는 반복을 중지하기 위해 구문을 반복하여 암시적으로 캡처됩니다.
다음은 카운터의 간단한 예입니다.
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def next(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
for c in Counter(3, 8):
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, 8):
print c
인쇄된 출력은 동일합니다.내부적으로 생성기 개체는 반복자 프로토콜을 지원하고 Counter 클래스와 대략 유사한 작업을 수행합니다.
데이비드 머츠(David Mertz)의 기사, 반복자와 단순 생성기, 꽤 좋은 소개입니다.
다른 팁
반복 함수를 작성하는 방법에는 네 가지가 있습니다.
- 생성기를 생성합니다(사용 수익률 키워드)
- 생성기 표현식을 사용하십시오(유전자표현)
- 반복자를 생성합니다(정의
__iter__
그리고__next__
(또는next
파이썬 2.x에서)) - Python이 자체적으로 반복할 수 있는 클래스를 만듭니다(정의하다
__getitem__
)
예:
# generator
def uc_gen(text):
for char in text:
yield char.upper()
# generator expression
def uc_genexp(text):
return (char.upper() for char in text)
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index].upper()
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text
def __getitem__(self, index):
result = self.text[index].upper()
return result
네 가지 방법이 모두 실행되는 모습을 보려면 다음을 수행하세요.
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print ch,
print
결과는 다음과 같습니다.
A B C D E
A B C D E
A B C D E
A B C D E
메모:
두 가지 생성기 유형(uc_gen
그리고 uc_genexp
) 수 없습니다 reversed()
;일반 반복자(uc_iter
)가 필요할 것이다 __reversed__
매직 메서드(뒤로 이동하는 새 반복자를 반환해야 함)그리고 반복 가능한 getitem(uc_getitem
)가 있어야합니다 __len__
마법의 방법:
# for uc_iter
def __reversed__(self):
return reversed(self.text)
# for uc_getitem
def __len__(self)
return len(self.text)
무한하게 지연 평가되는 반복기에 대한 Colonel 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
우선, itertools 모듈 이터레이터가 유용한 모든 종류의 경우에 매우 유용하지만, 파이썬에서 이터레이터를 만드는 데 필요한 모든 것은 다음과 같습니다.
생산하다
멋지지 않나요?수율은 일반을 대체하는 데 사용할 수 있습니다. 반품 함수에서.객체를 동일하게 반환하지만 상태를 파괴하고 종료하는 대신 다음 반복을 실행하려고 할 때를 위해 상태를 저장합니다.다음은 itertools 함수 목록:
def count(n=0):
while True:
yield n
n += 1
기능 설명에 명시된 대로(이것은 세다() itertools 모듈의 함수...)를 사용하면 n으로 시작하는 연속 정수를 반환하는 반복자를 생성합니다.
생성기 표현식 완전히 다른 벌레 캔입니다(굉장한 벌레입니다!).그들은 대신에 사용될 수 있습니다 목록 이해 메모리를 절약하기 위해(목록 이해는 변수에 할당되지 않은 경우 사용 후 파괴되는 메모리에 목록을 생성하지만 생성기 표현식은 생성기 개체를 생성할 수 있습니다...이는 Iterator를 표현하는 멋진 방법입니다).다음은 생성기 표현식 정의의 예입니다.
gen = (n for n in xrange(0,11))
이는 전체 범위가 0에서 10 사이로 미리 결정된다는 점을 제외하면 위의 반복자 정의와 매우 유사합니다.
방금 찾았어요 xrange() (이전에 본 적이 없어서 놀랐습니다...) 위의 예에 추가했습니다. xrange() 의 반복 가능한 버전입니다. 범위() 목록을 미리 작성하지 않는다는 장점이 있습니다.반복해야 할 거대한 데이터 모음이 있고 이를 수행할 메모리가 너무 많다면 매우 유용할 것입니다.
너희 중 몇몇이 하는 걸 봤어 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
물론 여기에서는 생성기를 직접 만들 수도 있지만 더 복잡한 클래스의 경우 유용할 수 있습니다.
이 질문은 반복자가 아니라 반복 가능한 객체에 관한 것입니다.Python에서는 시퀀스도 반복 가능하므로 반복 가능한 클래스를 만드는 한 가지 방법은 시퀀스처럼 동작하도록 만드는 것입니다.그것을 제공 __getitem__
그리고 __len__
행동 양식.저는 이것을 Python 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)
이것은 반복 가능한 함수입니다. yield
.그것은 iter
함수와 상태를 변경 가능한 상태로 유지하는 클로저(list
) Python 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
이 페이지의 모든 답변은 복잡한 개체에 매우 적합합니다.하지만 내장된 반복 가능 유형을 속성으로 포함하는 경우에는 다음과 같습니다. 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 string)
다음과 같이 사용할 수 있습니다.
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
짧고 간단한 것을 찾고 계시다면 아마도 이것으로 충분할 것입니다:
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]
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)