Python yield 사용 에 대한 분석

Python yield 사용 에 대한 분석
다음으로 전송:http://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/index.html?ca=drs-
파 이 썬 을 처음 배 운 개발 자 들 은 많은 파 이 썬 함수 에서 yield 키 워드 를 사용 한 것 을 자주 발견 합 니 다. 그러나 yield 를 가 진 함수 의 실행 절 차 는 일반 함수 와 다 릅 니 다. yield 는 도대체 무엇 을 하 는 것 입 니까? 왜 yield 를 디자인 해 야 합 니까?본 고 는 yield 의 개념 과 용법 을 간단명료 하 게 설명 하여 독자 들 이 Python 에서 yield 의 간단 하고 강력 한 기능 을 체험 하도록 도와 줄 것 이다.
yield 를 가 진 함수 가 Python 에서 generator (생 성기) 라 고 부 르 는데 generator 가 무엇 입 니까?
우 리 는 먼저 generator 를 버 리 고 흔히 볼 수 있 는 프로 그래 밍 문제 로 yield 의 개념 을 보 여 줍 니 다.
피 보 나치 수열 피 보 나치 수열 을 어떻게 생 성 하 는 지 는 매우 간단 한 귀속 수열 로 첫 번 째 와 두 번 째 수 를 제외 하고 임의의 수 는 앞의 두 수 에서 더 할 수 있다.컴퓨터 프로그램 으로 피 보 나치 수열 의 앞 N 개 수 를 출력 하 는 것 은 매우 간단 한 문제 로 많은 초보 자 들 이 다음 과 같은 함 수 를 쉽게 쓸 수 있다.
def fab(max):
	n, a, b = 0, 0, 1
	while n < max:
		print b
		a, b = b, a+b
		n += 1
fab (5) 를 실행 하면 다음 과 같은 출력 을 얻 을 수 있 습 니 다.
 >>> fab(5) 
 1 
 1 
 2 
 3 
 5
결 과 는 문제 가 없 지만 경험 이 있 는 개발 자 들 은 fab 함수 에서 직접 print 로 숫자 를 인쇄 하면 이 함수 의 재 활용 성 이 떨 어 집 니 다. fab 함수 가 None 으로 돌아 가기 때문에 다른 함수 들 은 이 함수 가 생 성 한 수열 을 얻 을 수 없습니다.
fab 함수 의 재 활용 성 을 높이 려 면 수열 을 직접 인쇄 하지 않 고 List 로 돌아 가 는 것 이 좋 습 니 다.다음은 fab 함수 개작 후의 두 번 째 버 전 입 니 다.
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 함수 가 되 돌아 오 는 List 를 다음 과 같이 출력 할 수 있 습 니 다.
 >>> for n in fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5
개 작 된 fab 함 수 는 List 를 되 돌려 서 재 활용 요 구 를 만족 시 킬 수 있 지만 경험 이 있 는 개발 자 는 이 함수 가 실행 중 사용 하 는 메모리 가 매개 변수 max 의 증가 에 따라 커진다 고 지적 할 것 입 니 다. 메모리 사용량 을 제어 하려 면 List 를 사용 하지 않 는 것 이 좋 습 니 다.
중간 결 과 를 저장 하 는 것 이 아니 라 iterable 대상 을 통 해 교체 합 니 다.예 를 들 어 Python 2. x 에서 코드:
for i in range(1000): pass
1000 개의 요 소 를 생 성 하 는 List 를 가 져 올 수 있 습 니 다. 코드:
for i in xrange(1000): pass
1000 개의 요소 의 List 를 만 들 지 않 고 매번 교체 할 때마다 다음 수 치 를 되 돌려 주 며 메모리 공간 이 작 습 니 다.xrange 는 List 를 되 돌려 주지 않 고 iterable 대상 을 되 돌려 주기 때 문 입 니 다.
iterable 을 이용 하여 우 리 는 fab 함 수 를 iterable 을 지원 하 는 class 로 바 꿀 수 있 습 니 다. 다음은 세 번 째 버 전의 Fab 입 니 다.
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 >>> for n in Fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5
그러나 클 라 스 가 고 친 이 버 전 을 사용 하면 코드 는 1 판 fab 함수 보다 훨씬 간결 하지 않 습 니 다.만약 에 우리 가 1 판 fab 함수 의 간결 성 을 유지 하 는 동시에 iterable 의 효 과 를 얻 으 려 면 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 는 1 판 에 비해 print b 를 yield b 로 바 꾸 는 것 만으로 간결 성 을 유지 하 는 동시에 iterable 효 과 를 얻 었 다.
4 판 fab 와 2 판 fab 가 완전히 일치 하도록 호출 합 니 다.
 >>> for n in fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5
쉽게 말 하면 yield 의 역할 은 하나의 함 수 를 generator 로 바 꾸 는 것 입 니 다. yield 를 가 진 함 수 는 더 이상 일반 함수 가 아 닙 니 다. Python 해석 기 는 이 를 generator 로 보고 fab (5) 를 호출 하면 fab 함 수 를 실행 하지 않 고 iterable 대상 으로 돌아 갑 니 다!for 순환 이 실 행 될 때 매번 순환 할 때마다 fab 함수 내부 의 코드 를 실행 합 니 다. yield b 로 실 행 될 때 fab 함 수 는 교체 값 을 되 돌려 줍 니 다. 다음 교체 시 코드 는 yield b 의 다음 문구 에서 계속 실 행 됩 니 다. 함수 의 로 컬 변 수 는 지난번 실행 중단 전과 똑 같 아서 함수 가 다시 yield 를 만 날 때 까지 계속 실 행 됩 니 다.
fab (5) 의 next () 방법 (fab (5) 은 generator 대상 이기 때문에 이 대상 은 next () 방법 을 가지 고 있 습 니 다. 그러면 우 리 는 fab 의 실행 절 차 를 더욱 분명하게 볼 수 있 습 니 다.
 >>> 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
함수 실행 이 끝 났 을 때 generator 는 자동 으로 StopIteration 이상 을 던 져 교체 가 완료 되 었 음 을 나타 낸다.for 순환 에 서 는 StopIteration 이상 을 처리 하지 않 아 도 순환 이 정상적으로 끝 납 니 다.
우 리 는 다음 과 같은 결론 을 얻 을 수 있다.
yield 가 있 는 함 수 는 generator 입 니 다. 일반 함수 와 달리 generator 를 만 드 는 것 은 함수 호출 처럼 보이 지만 함수 코드 를 실행 하지 않 습 니 다. next () 를 호출 할 때 까지 (for 순환 에서 next () 를 자동 으로 호출 합 니 다.실행 절 차 는 함수 의 절차 에 따라 실행 되 지만 하나의 yield 문 구 를 실행 할 때마다 중단 되 고 교체 값 을 되 돌려 줍 니 다. 다음 에 실행 할 때 yield 의 다음 문 구 를 계속 실행 합 니 다.한 함수 가 정상적으로 실행 되 는 과정 에서 yield 에 의 해 수 차례 중단 되 었 고 중단 할 때마다 yield 를 통 해 현재 의 교체 값 을 되 돌려 주 는 것 처럼 보 입 니 다.
yield 의 장점 은 분명 하 다. 한 함 수 를 하나의 generator 로 바 꾸 면 교체 능력 을 얻 을 수 있다. 클래스 의 인 스 턴 스 저장 상태 로 다음 next () 의 값 을 계산 하 는 것 보다 코드 가 간결 할 뿐만 아니 라 실행 절차 도 매우 뚜렷 하 다.
어떻게 함수 가 특수 한 generator 함수 인지 판단 합 니까?isgenerator function 을 이용 하여 판단 할 수 있 습 니 다.
 >>> from inspect import isgeneratorfunction 
 >>> isgeneratorfunction(fab) 
 True
fab 와 fab (5) 를 구분 해 야 합 니 다. fab 는 generator function 이 고 fab (5) 는 fab 를 호출 하여 돌아 오 는 generator 입 니 다. 예 를 들 어 클래스 의 정의 와 클래스 의 인 스 턴 스 의 차이 와 같 습 니 다.
 >>> 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 를 통 해 우 리 는 더 이상 파일 을 읽 는 교체 클래스 를 작성 하지 않 아 도 파일 을 쉽게 읽 을 수 있 습 니 다.
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 에서 디 버 깅 을 통 과 했 습 니 다.

좋은 웹페이지 즐겨찾기