Python에서는 Yield가 포함된 함수를 생성기라고 들어보셨을 것입니다.
먼저 생성기를 제쳐두고 일반적인 프로그래밍 문제를 사용하여 수율의 개념을 보여드리겠습니다.
피보나치 수열 생성 방법
피보나치 수열은 첫 번째와 두 번째 숫자를 제외하고 처음 두 숫자를 더하면 어떤 숫자도 얻을 수 있는 매우 간단한 재귀 수열입니다. 컴퓨터 프로그램을 사용하여 피보나치 수열의 처음 N개 숫자를 출력하는 것은 매우 간단한 문제입니다. 많은 초보자는 다음 함수를 쉽게 작성할 수 있습니다.
목록 1. 피보나치 수열의 처음 N개 숫자의 간단한 출력
def fab(max): n, a, b = 0, 0, 1 while n < max: print b a, b = b, a + b n = n + 1
fab(5)를 실행하면 다음과 같은 출력을 얻을 수 있습니다:
>>> fab(5) 1 1 2 3 5
결과에는 문제가 없지만, 숙련된 개발자는 fab 함수에서 직접 숫자를 인쇄하기 위해 인쇄를 사용하면 함수의 재사용성이 좋지 않다는 점을 지적할 것입니다. 왜냐하면 fab 함수는 None을 반환하고 다른 함수에서는 생성된 시퀀스를 얻을 수 없기 때문입니다. 기능.
fab 함수의 재사용성을 높이려면 시퀀스를 직접 인쇄하는 것이 아니라 목록을 반환하는 것이 가장 좋습니다. 다음은 fab 함수의 두 번째 버전을 다시 작성한 것입니다.
목록 2. 피보나치 수열 두 번째 버전의 처음 N개 숫자 출력
def fab(max): n, a, b = 0, 0, 1 L = [] while n < max: L.append(b) a, b = b, a + b n = n + 1 return L
다음 방법을 사용하여 fab 함수에서 반환된 목록을 인쇄할 수 있습니다.
>>> for n in fab(5): ... print n ... 1 1 2 3 5
재작성된 fab 함수는 List를 반환하여 재사용성 요구 사항을 충족할 수 있지만, 경험이 많은 개발자는 매개 변수 max가 증가함에 따라 작업 중에 이 함수가 차지하는 메모리가 증가한다는 점을 지적할 것입니다. 메모리 점유를 제어하려면 이것이 가장 좋습니다. 목록을 사용하지 마세요
중간 결과를 저장하려면 반복 가능한 객체를 반복합니다. 예를 들어 Python2.x에서 코드는 다음과 같습니다.
목록 3. 반복 가능한 객체를 통한 반복
for i in range(1000): pass
1000개의 요소 목록이 생성되고 코드는
for i in xrange(1000): pass
입니다. 1000개 요소의 목록을 생성하지 않지만 각 반복에서 다음 값을 반환하므로 메모리 공간을 거의 차지하지 않습니다. xrange는 List가 아니라 반복 가능한 객체를 반환하기 때문입니다.
iterable을 사용하면 fab 함수를 iterable을 지원하는 클래스로 다시 작성할 수 있습니다. 다음은 Fab의 세 번째 버전입니다.
목록 4. 세 번째 버전
class Fab(object): def __init__(self, max): self.max = max self.n, self.a, self.b = 0, 0, 1 def __iter__(self): return self def next(self): if self.n < self.max: r = self.b self.a, self.b = self.b, self.a + self.b self.n = self.n + 1 return r raise StopIteration()
Fab 클래스는 next()를 통해 시퀀스의 다음 숫자를 지속적으로 반환하며 메모리 사용량은 항상 일정합니다.
>>> for n in fab(5): ... print n ... 1 1 2 3 5
그러나 클래스를 사용하여 다시 작성된 이 버전의 코드는 fab 함수의 첫 번째 버전보다 훨씬 덜 간결합니다. iterable의 효과를 얻으면서 fab 함수의 첫 번째 버전의 단순성을 유지하려면 Yield가 유용합니다.
목록 5. Yield를 사용한 네 번째 버전
def fab(max): n, a, b = 0, 0, 1 while n < max: yield b # print b a, b = b, a + b n = n + 1 '''
첫 번째 버전과 비교하면, 네 번째 버전의 Fab에서는 print b만 Yield b로 변경되어 단순성을 유지하면서 반복 가능 효과를 달성했습니다.
네 번째 버전의 fab을 호출하는 것은 두 번째 버전의 fab과 정확히 같습니다:
>>> for n in fab(5): ... print n ... 1 1 2 3 5
간단히 말해서, Yield의 기능은 함수를 생성기로 바꾸는 것입니다. Python 인터프리터는 이를 생성기로 처리하지 않고 대신 fab 함수를 실행합니다. , 반복 가능한 객체를 반환합니다! for 루프가 실행되면 각 루프는 fab 함수 내부의 코드를 실행합니다. Yield b에 도달하면 fab 함수는 반복 값을 반환하며, 코드는 다음 Yield b 문부터 계속 실행됩니다. 그리고 함수 지역 변수는 마지막 중단 이전과 정확히 동일해 보이므로 함수는 항복이 다시 발생할 때까지 계속 실행됩니다.
fab(5)의 next() 메서드를 수동으로 호출할 수도 있습니다(fab(5)는 next() 메서드가 있는 생성기 개체이기 때문). 그러면 fab의 실행 프로세스를 더 명확하게 볼 수 있습니다.
목록 6. 실행과정
>>> f = fab(5) >>> f.next() 1 >>> f.next() 1 >>> f.next() 2 >>> f.next() 3 >>> f.next() 5 >>> f.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
함수 실행이 끝나면 생성기는 자동으로 StopIteration 예외를 발생시켜 반복이 완료되었음을 나타냅니다. for 루프에서는 StopIteration 예외를 처리할 필요가 없으며 루프가 정상적으로 종료됩니다.
우리는 다음과 같은 결론을 내릴 수 있습니다:
생성기를 생성하는 것은 함수 호출처럼 보이지만 next()가 호출될 때까지 함수 코드를 실행하지 않습니다(next(는 for에서 자동으로 호출됩니다). 루프) )) 실행을 시작하기 전에. 실행 흐름은 여전히 함수의 흐름에 따라 실행되지만, Yield 문이 실행될 때마다 중단되고 반복 값이 반환됩니다. 다음 실행은 다음 Yield 문에서 계속됩니다. 정상적인 실행 중에 함수가 Yield에 의해 여러 번 중단되고 각 중단이 Yield를 통해 현재 반복 값을 반환하는 것처럼 보입니다.
Yield의 이점은 명백합니다. 함수를 생성기로 다시 작성하면 클래스의 인스턴스를 사용하여 next() 값을 계산하는 것과 비교할 때 코드가 간결할 뿐만 아니라 실행도 간편해집니다. 프로세스는 매우 명확합니다.
如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:
清单 7. 使用 isgeneratorfunction 判断
>>> from inspect import isgeneratorfunction >>> isgeneratorfunction(fab) True
要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:
清单 8. 类的定义和类的实例
>>> import types >>> isinstance(fab, types.GeneratorType) False >>> isinstance(fab(5), types.GeneratorType) True
fab 是无法迭代的,而 fab(5) 是可迭代的:
>>> from collections import Iterable >>> 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 的例子
def read_file(fpath): BLOCK_SIZE = 1024 with open(fpath, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return
以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。
注:本文的代码均在 Python 2.7 中调试通过
위 내용은 Python 수율 사용량 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!