소개
이 섹션에서는 제너레이터 함수를 사용하여 반복을 사용자 정의하는 방법을 살펴봅니다.
문제
자신만의 사용자 정의 반복 패턴을 만들고 싶다고 가정해 봅시다.
예를 들어, 카운트다운 (countdown) 과 같은 경우입니다.
>>> for x in countdown(10):
... print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>
이것을 쉽게 할 수 있는 방법이 있습니다.
제너레이터 (Generators)
제너레이터는 반복을 정의하는 함수입니다.
def countdown(n):
while n > 0:
yield n
n -= 1
예를 들어:
>>> for x in countdown(10):
... print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>
제너레이터는 yield 문을 사용하는 모든 함수입니다.
제너레이터의 동작은 일반 함수와 다릅니다. 제너레이터 함수를 호출하면 제너레이터 객체가 생성됩니다. 함수를 즉시 실행하지 않습니다.
def countdown(n):
## Added a print statement
print('Counting down from', n)
while n > 0:
yield n
n -= 1
>>> x = countdown(10)
## There is NO PRINT STATEMENT
>>> x
## x is a generator object
<generator object at 0x58490>
>>>
함수는 __next__() 호출 시에만 실행됩니다.
>>> x = countdown(10)
>>> x
<generator object at 0x58490>
>>> x.__next__()
Counting down from 10
10
>>>
yield는 값을 생성하지만 함수 실행을 일시 중단합니다. 함수는 __next__()에 대한 다음 호출에서 재개됩니다.
>>> x.__next__()
9
>>> x.__next__()
8
제너레이터가 최종적으로 반환되면 반복은 오류를 발생시킵니다.
>>> x.__next__()
1
>>> x.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in ? StopIteration
>>>
관찰: 제너레이터 함수는 for 문이 리스트, 튜플, 딕셔너리, 파일 등에서 사용하는 것과 동일한 하위 수준 프로토콜을 구현합니다.
연습 문제 6.4: 간단한 제너레이터 (Generator)
반복을 사용자 정의하고 싶을 때마다 항상 제너레이터 함수를 생각해야 합니다. 작성하기 쉽습니다. 원하는 반복 로직을 수행하는 함수를 만들고 yield를 사용하여 값을 내보냅니다.
예를 들어, 파일에서 일치하는 부분 문자열을 포함하는 줄을 검색하는 이 제너레이터를 사용해 보세요.
>>> def filematch(filename, substr):
with open(filename, 'r') as f:
for line in f:
if substr in line:
yield line
>>> for line in open('portfolio.csv'):
print(line, end='')
name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44
>>> for line in filematch('portfolio.csv', 'IBM'):
print(line, end='')
"IBM",50,91.10
"IBM",100,70.44
>>>
이것은 다소 흥미롭습니다. 사용자 정의 처리를 함수에 숨기고 이를 사용하여 for 루프에 공급할 수 있다는 아이디어입니다. 다음 예제는 더 특이한 경우를 살펴봅니다.
연습 문제 6.5: 스트리밍 데이터 소스 모니터링
제너레이터는 로그 파일이나 주식 시장 피드와 같은 실시간 데이터 소스를 모니터링하는 흥미로운 방법이 될 수 있습니다. 이 부분에서는 이 아이디어를 탐구해 보겠습니다. 시작하려면 다음 지침을 주의 깊게 따르십시오.
프로그램 stocksim.py는 주식 시장 데이터를 시뮬레이션하는 프로그램입니다. 출력으로, 이 프로그램은 실시간 데이터를 파일 stocklog.csv에 지속적으로 씁니다. 별도의 명령 창에서 `` 디렉토리로 이동하여 이 프로그램을 실행합니다.
$ python3 stocksim.py
Windows 를 사용 중인 경우 stocksim.py 프로그램을 찾아 두 번 클릭하여 실행하십시오. 이제 이 프로그램에 대해 잊어버리십시오 (그냥 실행되도록 두십시오). 다른 창을 사용하여 시뮬레이터가 작성하는 파일 stocklog.csv를 살펴보십시오. 몇 초마다 파일에 새로운 텍스트 줄이 추가되는 것을 볼 수 있습니다. 다시 말하지만, 이 프로그램을 백그라운드에서 실행하도록 두십시오. 몇 시간 동안 실행됩니다 (걱정할 필요가 없습니다).
위의 프로그램이 실행되면 파일을 열고, 끝으로 이동하여 새로운 출력을 감시하는 작은 프로그램을 작성해 보겠습니다. follow.py 파일을 만들고 이 코드를 넣으십시오.
## follow.py
import os
import time
f = open('stocklog.csv')
f.seek(0, os.SEEK_END) ## Move file pointer 0 bytes from end of file
while True:
line = f.readline()
if line == '':
time.sleep(0.1) ## Sleep briefly and retry
continue
fields = line.split(',')
name = fields[0].strip('"')
price = float(fields[1])
change = float(fields[4])
if change < 0:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')
프로그램을 실행하면 실시간 주식 시세를 볼 수 있습니다. 내부적으로 이 코드는 로그 파일을 감시하는 데 사용되는 Unix tail -f 명령과 유사합니다.
참고: 이 예제에서 readline() 메서드를 사용하는 것은 파일에서 줄을 읽는 일반적인 방법이 아니라는 점에서 다소 특이합니다 (일반적으로 for 루프를 사용합니다). 그러나 이 경우, 더 많은 데이터가 추가되었는지 확인하기 위해 파일의 끝을 반복적으로 조사하는 데 사용됩니다 (readline()은 새 데이터를 반환하거나 빈 문자열을 반환합니다).
연습 문제 6.6: 제너레이터를 사용하여 데이터 생성
연습 문제 6.5 의 코드를 살펴보면, 코드의 첫 번째 부분은 데이터 줄을 생성하고, while 루프의 끝에 있는 문은 데이터를 소비합니다. 제너레이터 함수의 주요 특징은 모든 데이터 생성 코드를 재사용 가능한 함수로 이동할 수 있다는 것입니다.
파일 읽기가 제너레이터 함수 follow(filename)에 의해 수행되도록 연습 문제 6.5 의 코드를 수정하십시오. 다음 코드가 작동하도록 만드십시오.
>>> for line in follow('stocklog.csv'):
print(line, end='')
... 여기에 출력 줄이 생성되어야 합니다 ...
주식 시세 코드를 다음과 같이 수정하십시오.
if __name__ == '__main__':
for line in follow('stocklog.csv'):
fields = line.split(',')
name = fields[0].strip('"')
price = float(fields[1])
change = float(fields[4])
if change < 0:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')
연습 문제 6.7: 포트폴리오 감시
follow.py 프로그램을 수정하여 주식 데이터 스트림을 감시하고 포트폴리오에 있는 주식에 대한 정보만 표시하는 티커를 인쇄하도록 하십시오. 예를 들어:
if __name__ == '__main__':
import report
portfolio = report.read_portfolio('portfolio.csv')
for line in follow('stocklog.csv'):
fields = line.split(',')
name = fields[0].strip('"')
price = float(fields[1])
change = float(fields[4])
if name in portfolio:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')
참고: 이것이 작동하려면 Portfolio 클래스가 in 연산자를 지원해야 합니다. 연습 문제 6.3 을 참조하고 __contains__() 연산자를 구현했는지 확인하십시오.
토론
여기에서 매우 강력한 일이 일어났습니다. 흥미로운 반복 패턴 (파일 끝에서 줄 읽기) 을 자체 작은 함수로 이동했습니다. 이제 follow() 함수는 모든 프로그램에서 사용할 수 있는 완전히 일반적인 유틸리티입니다. 예를 들어, 서버 로그, 디버깅 로그 및 기타 유사한 데이터 소스를 감시하는 데 사용할 수 있습니다. 꽤 멋지죠.
요약
축하합니다! 사용자 지정 반복 (Customizing Iteration) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 기술을 향상시킬 수 있습니다.