>  기사  >  백엔드 개발  >  Python Yield 사용에 대한 간략한 분석

Python Yield 사용에 대한 간략한 분석

高洛峰
高洛峰원래의
2016-10-19 16:04:391095검색

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 함수의 첫 번째 버전보다 훨씬 덜 간결합니다. 반복 가능한 효과를 얻으면서 Fab 함수의 첫 번째 버전의 단순성을 유지하려면 Yield가 유용합니다.

목록 5. Yield를 사용하는 네 번째 버전

비교 첫 번째 버전인 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
... printn

...

간단히 말해서, Yield의 기능은 함수를 생성기로 바꾸는 것입니다. Python 인터프리터는 이를 생성기로 취급하지 않습니다. fab(5)를 호출하면 fab 함수가 실행되지 않지만 반복 가능한 객체가 반환됩니다! for 루프가 실행되면 각 루프는 fab 함수 내부의 코드를 실행합니다. Yield b에 도달하면 fab 함수는 반복 값을 반환하며, 코드는 다음 Yield b 문부터 계속 실행됩니다. 그리고 함수 지역 변수는 마지막 중단 이전과 정확히 동일해 보이므로 함수는 항복이 다시 발생할 때까지 계속 실행됩니다.

 fab(5)는 next() 메소드를 갖는 생성기 객체이기 때문에 fab(5)의 next() 메소드를 수동으로 호출하여 fab을 더 명확하게 볼 수도 있습니다. 실행 프로세스:

목록 6. 실행 프로세스

>>> f =fab(5)

>>> f.next()

> >> f.next()

>>> f.next()

>>> f.next()

>>> f.next()

>>> f.next()

추적(가장 최근 호출 마지막):

File"", line 1, in StopIteration

함수일 때 실행이 끝나면 생성기는 자동으로 StopIteration 예외를 발생시켜 반복이 완료되었음을 나타냅니다. for 루프에서는 StopIteration 예외를 처리할 필요가 없으며 루프는 정상적으로 종료됩니다.

다음과 같은 결론을 내릴 수 있습니다.

생성기를 사용하는 함수는 일반 함수와 다릅니다. 생성기를 생성하는 것은 함수 호출처럼 보이지만 어떤 함수도 실행하지 않습니다. 코드는 next()가 호출될 때까지 실행을 시작하지 않습니다(next()는 for 루프에서 자동으로 호출됨). 실행 흐름은 여전히 ​​함수의 흐름에 따라 실행되지만, Yield 문이 실행될 때마다 중단되고 반복 값이 반환됩니다. 다음 실행은 다음 Yield 문에서 계속됩니다. 정상적인 실행 중에 함수가 Yield에 의해 여러 번 중단되고 각 중단이 Yield를 통해 현재 반복 값을 반환하는 것처럼 보입니다.

  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, &#39;rb&#39;) as f:
       whileTrue:
           block =f.read(BLOCK_SIZE)
           ifblock:
               yieldblock
           else:
               return

  以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。

  注:本文的代码均在 Python 2.7 中调试通过


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.