Iterator
반복자는 반복 프로토콜을 준수하는 객체입니다. 즉, 기본적으로 호출 시 Next 프로젝트 시퀀스를 반환하는 next 메소드가 있음을 의미합니다. 반환할 항목이 없으면 StopIteration 예외를 발생시킵니다.
반복 개체는 하나의 루프를 허용합니다. 단일 반복의 상태(위치)를 유지하거나 다른 관점에서 보면 시퀀스가 반복될 때마다 반복 개체가 필요합니다. 이는 동일한 시퀀스를 두 번 이상 반복할 수 있음을 의미합니다. 반복 로직을 시퀀스에서 분리하면 더 많은 반복 방법이 제공됩니다.
반복자 객체를 생성하기 위해 컨테이너의 __iter__ 메서드를 호출하는 것은 반복자를 마스터하는 가장 직접적인 방법입니다. iter 기능을 사용하면 일부 키 입력을 절약할 수 있습니다.
>>> nums = [1,2,3] # note that ... varies: these are different objects >>> iter(nums) <listiterator object at ...> >>> nums.__iter__() <listiterator object at ...> >>> nums.__reversed__() <listreverseiterator object at ...> >>> it = iter(nums) >>> next(it) # next(obj) simply calls obj.next() 1 >>> it.next() 2 >>> next(it) 3 >>> next(it) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
루프 내에서 사용하면 StopIteration이 허용되고 루프가 중지됩니다. 그러나 명시적 호출을 사용하면 반복기 요소가 모두 소진되면 이에 액세스하면 예외가 발생한다는 것을 알 수 있습니다.
for...in 루프를 사용하고 __iter__ 메소드도 사용하세요. 이를 통해 시퀀스에 대한 반복을 투명하게 시작할 수 있습니다. 그러나 이미 반복자가 있는 경우 for 루프에서 유사하게 사용할 수 있기를 원합니다. 이를 달성하기 위해 반복자에는 next 외에도 반복자 자체(self)를 반환하는 __iter__ 메서드도 있습니다.
Python의 반복자 지원은 어디에나 있습니다. 표준 라이브러리의 모든 시퀀스와 순서가 지정되지 않은 컨테이너가 지원됩니다. 이 개념은 다른 것에도 확장되었습니다. 예를 들어 파일 객체는 줄 반복을 지원합니다.
>>> f = open('/etc/fstab') >>> f is f.__iter__() True
파일 자체는 반복자이며 해당 __iter__ 메서드는 별도의 개체를 생성하지 않습니다. 단일 스레드 순차 읽기만 허용됩니다.
표현식 생성
반복 가능한 객체를 생성하는 두 번째 방법은 목록 이해의 기초인 생성기 표현식을 사용하는 것입니다. 명확성을 높이기 위해 생성된 표현식은 항상 괄호나 표현식으로 묶입니다. 괄호를 사용하면 생성기 반복자가 생성됩니다. 대괄호의 경우 이 프로세스는 '단락'되어 목록을 얻습니다.
>>> (i for i in nums) <generator object <genexpr> at 0x...> >>> [i for i in nums] [1, 2, 3] >>> list(i for i in nums) [1, 2, 3]
Python 2.7 및 3.x에서는 목록 표현식 구문이 사전 및 집합 표현식으로 확장되었습니다. 생성된 표현식을 중괄호로 묶으면 세트가 생성됩니다. 표현식에 key:value:
>>> {i for i in range(3)} set([0, 1, 2]) >>> {i:i**2 for i in range(3)} {0: 0, 1: 1, 2: 4}
형식의 키-값 쌍이 포함된 경우 사전 사전 사전이 생성됩니다. Python 버전에서는 구문이 약간 나쁩니다.
>>> set(i for i in 'abc') set(['a', 'c', 'b']) >>> dict((i, ord(i)) for i in 'abc') {'a': 97, 'c': 99, 'b': 98}
식을 생성하는 것은 매우 간단하며 말할 필요도 없습니다. 언급할 가치가 있는 단 한 가지 문제가 있습니다: 인덱스 변수 (i)는 Python 3 미만 버전에서 누출됩니다.
생성기
생성기는 단일 값이 아닌 결과 목록을 생성하는 함수입니다.
반복 가능한 객체를 생성하는 세 번째 방법은 생성기 함수를 호출하는 것입니다. 제너레이터는 Yield 키워드를 포함하는 함수입니다. 이 키워드가 존재한다는 것만으로도 함수의 성격이 완전히 바뀌는 점은 주목할 가치가 있습니다. 즉, Yield 문을 호출할 필요도 없고 액세스할 필요도 없습니다. 하지만 함수가 생성자가 되도록 하세요. 함수가 호출되면 그 안에 있는 명령이 실행됩니다. 그리고 생성기가 호출되면 실행은 해당 생성기의 첫 번째 명령 이전에 중지됩니다. 생성기를 호출하면 반복 프로토콜에 연결된 생성기 개체가 생성됩니다. 일반 함수와 마찬가지로 동시 및 재귀 호출이 허용됩니다.
next가 호출되면 함수는 첫 번째 산출물까지 실행됩니다. Yield 문이 발생할 때마다 next로 반환되는 값을 가져옵니다. Yield 문이 실행된 후 함수 실행이 중지됩니다.
>>> def f(): ... yield 1 ... yield 2 >>> f() <generator object f at 0x...> >>> gen = f() >>> gen.next() 1 >>> gen.next() 2 >>> gen.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
단일 생성기 함수 호출의 전체 프로세스를 살펴보겠습니다.
>>> def f(): ... print("-- start --") ... yield 3 ... print("-- middle --") ... yield 4 ... print("-- finished --") >>> gen = f() >>> next(gen) -- start -- 3 >>> next(gen) -- middle -- 4 >>> next(gen) -- finished -- Traceback (most recent call last): ... StopIteration
일반 함수에서 f()를 실행하여 즉시 print를 실행하는 것과 비교하면 함수 본문에 어떤 문장도 실행하지 않고 gen을 할당합니다. 다음에 gen.next()가 호출될 때만 첫 번째 Yield 부분까지의 명령문이 실행됩니다. 두 번째 문은 -- middle --을 인쇄하고 두 번째 항복이 발생하면 실행을 중지합니다. 세 번째 다음 인쇄가 완료되고 함수가 끝날 때까지 결과가 없으므로 예외가 발생합니다.
함수가 반환된 후 제어권이 호출자에게 반환되면 어떻게 되나요? 각 생성기의 상태는 생성기 개체에 저장됩니다. 이 시점에서 생성기 함수는 별도의 스레드에서 실행되는 것처럼 보이지만 이것은 단지 환상일 뿐입니다. 실행은 엄격하게 단일 스레드이지만 인터프리터는 다음 값 요청 사이에 상태를 유지하고 저장합니다.
생성기가 왜 유용한가요? 반복자 섹션에서 강조했듯이 생성기 함수는 반복 가능한 객체를 생성하는 또 다른 방법일 뿐입니다. Yield 문으로 완료할 수 있는 모든 작업은 다음 메서드로도 완료할 수 있습니다. 그러나 인터프리터가 마술처럼 반복자를 생성할 수 있도록 함수를 사용하면 이점이 있습니다. 함수는 next 및 __iter__ 메서드가 필요한 클래스 정의보다 훨씬 짧을 수 있습니다. 더 중요한 것은 생성기 작성자가 연속적인 next 호출 사이에 반복자 객체의 인스턴스 속성을 전달하는 것보다 지역 변수에 지역화된 명령문을 더 쉽게 이해할 수 있다는 것입니다.
还有问题是为何迭代器有用?当一个迭代器用来驱动循环,循环变得简单。迭代器代码初始化状态,决定是否循环结束,并且找到下一个被提取到不同地方的值。这凸显了循环体——最值得关注的部分。除此之外,可以在其它地方重用迭代器代码。
双向通信
每个yield语句将一个值传递给调用者。这就是为何PEP 255引入生成器(在Python2.2中实现)。但是相反方向的通信也很有用。一个明显的方式是一些外部(extern)语句,或者全局变量或共享可变对象。通过将先前无聊的yield语句变成表达式,直接通信因PEP 342成为现实(在2.5中实现)。当生成器在yield语句之后恢复执行时,调用者可以对生成器对象调用一个方法,或者传递一个值 给 生成器,然后通过yield语句返回,或者通过一个不同的方法向生成器注入异常。
第一个新方法是send(value),类似于next(),但是将value传递进作为yield表达式值的生成器中。事实上,g.next()和g.send(None)是等效的。
第二个新方法是throw(type, value=None, traceback=None),等效于在yield语句处
raise type, value, traceback
不像raise(从执行点立即引发异常),throw()首先恢复生成器,然后仅仅引发异常。选用单次throw就是因为它意味着把异常放到其它位置,并且在其它语言中与异常有关。
当生成器中的异常被引发时发生什么?它可以或者显式引发,当执行某些语句时可以通过throw()方法注入到yield语句中。任一情况中,异常都以标准方式传播:它可以被except和finally捕获,或者造成生成器的中止并传递给调用者。
因完整性缘故,值得提及生成器迭代器也有close()方法,该方法被用来让本可以提供更多值的生成器立即中止。它用生成器的__del__方法销毁保留生成器状态的对象。
让我们定义一个只打印出通过send和throw方法所传递东西的生成器。
>>> import itertools >>> def g(): ... print '--start--' ... for i in itertools.count(): ... print '--yielding %i--' % i ... try: ... ans = yield i ... except GeneratorExit: ... print '--closing--' ... raise ... except Exception as e: ... print '--yield raised %r--' % e ... else: ... print '--yield returned %s--' % ans >>> it = g() >>> next(it) --start-- --yielding 0-- 0 >>> it.send(11) --yield returned 11-- --yielding 1-- 1 >>> it.throw(IndexError) --yield raised IndexError()-- --yielding 2-- 2 >>> it.close() --closing--
注意: next还是__next__?
在Python 2.x中,接受下一个值的迭代器方法是next,它通过全局函数next显式调用,意即它应该调用__next__。就像全局函数iter调用__iter__。这种不一致在Python 3.x中被修复,it.next变成了it.__next__。对于其它生成器方法——send和throw情况更加复杂,因为它们不被解释器隐式调用。然而,有建议语法扩展让continue带一个将被传递给循环迭代器中send的参数。如果这个扩展被接受,可能gen.send会变成gen.__send__。最后一个生成器方法close显然被不正确的命名了,因为它已经被隐式调用。
链式生成器
注意: 这是PEP 380的预览(还未被实现,但已经被Python3.3接受)
比如说我们正写一个生成器,我们想要yield一个第二个生成器——一个子生成器(subgenerator)——生成的数。如果仅考虑产生(yield)的值,通过循环可以不费力的完成:
subgen = some_other_generator() for v in subgen: yield v
然而,如果子生成器需要调用send()、throw()和close()和调用者适当交互的情况下,事情就复杂了。yield语句不得不通过类似于前一章节部分定义的try...except...finally结构来保证“调试”生成器函数。这种代码在PEP 380中提供,现在足够拿出将在Python 3.3中引入的新语法了:
yield from some_other_generator()
像上面的显式循环调用一样,重复从some_other_generator中产生值直到没有值可以产生,但是仍然向子生成器转发send、throw和close。
更多Python中的迭代器与生成器高级用法相关文章请关注PHP中文网!