Yield가 있는 함수를 Python에서 생성기라고 들어보셨을 것입니다.
먼저 생성기를 제쳐두고 일반적인 프로그래밍 주제를 사용하여 수율의 개념을 보여드리겠습니다.
피보나치 수열 생성 방법
피보나치 수열은 매우 간단한 재귀 수열로 첫 번째와 두 번째 숫자를 제외하면 어떤 숫자든 처음 두 숫자를 더하면 얻을 수 있습니다. 컴퓨터 프로그램을 사용하여 피보나치 수열의 첫 번째 N개 숫자를 출력하는 것은 매우 간단한 문제입니다. 많은 초보자는 다음 함수를 쉽게 작성할 수 있습니다.
목록 1. 피보나치 수열의 첫 번째 N개 숫자를 간단하게 출력합니다.
deffab(max): n, a, b =0, 0, 1 whilen < max: printb a, b =b, a +b n =n +1
fab(5)를 실행하면 다음과 같은 출력을 얻을 수 있습니다.
>>> fab(5)
결과에는 문제가 없습니다. 그러나 경험이 있는 개발자는 fab 함수에서 숫자를 직접 인쇄하기 위해 print를 사용하면 함수의 재사용성이 떨어진다는 점을 지적할 것입니다. 왜냐하면 fab 함수는 None을 반환하고 다른 함수는 함수에 의해 생성된 시퀀스를 얻을 수 없기 때문입니다.
fab 함수의 재사용성을 높이려면 시퀀스를 직접 인쇄하는 것이 아니라 목록을 반환하는 것이 가장 좋습니다. 다음은 fab 함수의 두 번째 버전을 다시 작성한 것입니다.
목록 2. 피보나치 수열의 처음 N개 숫자 출력 두 번째 버전
deffab(max): n, a, b =0, 0, 1 L =[] whilen < max: L.append(b) a, b =b, a +b n =n +1 returnL
다음 방법을 사용하여 인쇄할 수 있습니다. fab 함수에 의해 반환된 목록:
>>> forn infab(5):
... printn
...
다시 작성된 fab 함수 재사용성 요구 사항은 List를 반환하여 충족할 수 있지만, 경험이 많은 개발자는 매개 변수 max가 증가함에 따라 작업 중에 이 함수가 차지하는 메모리가 증가한다는 점을 지적할 것입니다. 메모리 사용량을 제어하려면 List<를 사용하지 않는 것이 가장 좋습니다. 🎜>
중간 결과를 저장하지만 반복 가능한 객체를 반복합니다. 예를 들어 python2.List에서 fori inxrange(1000): pass 코드는 1000개 요소의 목록을 생성하지 않지만 각 반복에서 다음 값인 memory를 반환합니다. 공간은 거의 차지하지 않습니다. xrange는 List가 아니라 반복 가능한 객체를 반환하기 때문입니다. iterable을 사용하여 iterable을 지원하는 클래스로 fab 함수를 다시 작성할 수 있습니다. 다음은 Fab의 세 번째 버전입니다. 목록 4. 세 번째 버전Fab 클래스는 next()를 통해 시퀀스의 다음 숫자를 계속해서 반환하며, 메모리 사용량은 항상 일정합니다:
>>> forn inFab(5):
... printn
classFab(object): def__init__(self, max): self.max=max self.n, self.a, self.b =0, 0, 1 def__iter__(self): returnself defnext(self): ifself.n < self.max: r =self.b self.a, self.b =self.b, self.a +self.b self.n =self.n +1 returnr raiseStopIteration()
비교 첫 번째 버전인 Fab의 네 번째 버전은 print b를 산출 b로만 변경하여 단순성을 유지하면서 반복 가능한 효과를 얻습니다.
fab의 네 번째 버전을 호출하는 것은 fab의 두 번째 버전과 정확히 동일합니다:
>>> forn infab(5):
deffab(max): n, a, b =0, 0, 1 whilen < max: yieldb # print b a, b =b, a +b n =n +1
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。
如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:
清单 7. 使用 isgeneratorfunction 判断
>>> frominspect importisgeneratorfunction
>>> isgeneratorfunction(fab)
True
要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:
清单 8. 类的定义和类的实例
>>> importtypes
>>> isinstance(fab, types.GeneratorType)
False
>>> isinstance(fab(5), types.GeneratorType)
True
fab 是无法迭代的,而 fab(5) 是可迭代的:
>>> fromcollections importIterable
>>> isinstance(fab, Iterable)
False
>>> isinstance(fab(5), Iterable)
True
每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响:
>>> f1 =fab(3)
>>> f2 =fab(5)
>>> print'f1:', f1.next()
f1: 1
>>> print'f2:', f2.next()
f2: 1
>>> print'f1:', f1.next()
f1: 1
>>> print'f2:', f2.next()
f2: 1
>>> print'f1:', f1.next()
f1: 2
>>> print'f2:', f2.next()
f2: 2
>>> print'f2:', f2.next()
f2: 3
>>> print'f2:', f2.next()
f2: 5
return 的作用
在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
另一个例子
另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:
清单 9. 另一个 yield 的例子
defread_file(fpath): BLOCK_SIZE =1024 with open(fpath, 'rb') as f: whileTrue: block =f.read(BLOCK_SIZE) ifblock: yieldblock else: return
以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。
注:本文的代码均在 Python 2.7 中调试通过