소개
이 랩에서는 Python 의 제너레이터를 사용하여 반복을 사용자 정의하는 방법을 배우게 됩니다. 또한 사용자 정의 클래스에서 반복자 기능을 구현하고 데이터 소스를 스트리밍하기 위한 제너레이터를 생성할 것입니다.
실험 과정에서 structure.py 파일이 수정되고, follow.py라는 새로운 파일이 생성될 것입니다.
Python 제너레이터 이해하기
제너레이터는 Python 의 강력한 기능입니다. 제너레이터는 반복자 (iterator) 를 생성하는 간단하고 우아한 방법을 제공합니다. Python 에서 데이터 시퀀스를 다룰 때 반복자는 일련의 값을 하나씩 반복할 수 있으므로 매우 유용합니다. 일반 함수는 일반적으로 단일 값을 반환한 다음 실행을 중지합니다. 그러나 제너레이터는 다릅니다. 제너레이터는 시간이 지남에 따라 일련의 값을 yield 할 수 있으며, 이는 단계별 방식으로 여러 값을 생성할 수 있음을 의미합니다.
제너레이터란 무엇인가?
제너레이터 함수는 일반 함수와 유사한 외관을 가지고 있습니다. 그러나 핵심적인 차이점은 값을 반환하는 방식에 있습니다. 단일 결과를 제공하기 위해 return 문을 사용하는 대신, 제너레이터 함수는 yield 문을 사용합니다. yield 문은 특별합니다. 실행될 때마다 함수의 상태가 일시 중지되고, yield 키워드 뒤에 오는 값이 호출자에게 반환됩니다. 제너레이터 함수가 다시 호출되면 중단된 지점부터 실행이 재개됩니다.
간단한 제너레이터 함수를 생성하는 것으로 시작해 보겠습니다. Python 의 내장 함수인 range()는 소수점 단계를 지원하지 않습니다. 따라서 소수점 단계를 가진 숫자 범위를 생성할 수 있는 제너레이터 함수를 생성합니다.
- 먼저 WebIDE 에서 새 Python 터미널을 열어야 합니다. 이렇게 하려면 "Terminal" 메뉴를 클릭한 다음 "New Terminal"을 선택합니다.
- 터미널이 열리면 터미널에 다음 코드를 입력합니다. 이 코드는 제너레이터 함수를 정의한 다음 테스트합니다.
def frange(start, stop, step):
current = start
while current < stop:
yield current
current += step
## Test the generator with a for loop
for x in frange(0, 2, 0.25):
print(x, end=' ')
이 코드에서 frange 함수는 제너레이터 함수입니다. start 값으로 변수 current를 초기화합니다. 그런 다음 current가 stop 값보다 작은 동안 current 값을 yield 하고 current를 step 값만큼 증가시킵니다. for 루프는 frange 제너레이터 함수에서 생성된 값을 반복하고 이를 출력합니다.
다음과 같은 출력을 볼 수 있습니다.
0 0.25 0.5 0.75 1.0 1.25 1.5 1.75
제너레이터의 일회성 특성
제너레이터의 중요한 특징은 소모성 (exhaustible) 이라는 것입니다. 즉, 제너레이터가 생성한 모든 값을 반복한 후에는 동일한 값 시퀀스를 다시 생성하는 데 사용할 수 없습니다. 다음 코드를 사용하여 이를 시연해 보겠습니다.
## Create a generator object
f = frange(0, 2, 0.25)
## First iteration works fine
print("First iteration:")
for x in f:
print(x, end=' ')
print("\n")
## Second iteration produces nothing
print("Second iteration:")
for x in f:
print(x, end=' ')
print("\n")
이 코드에서 먼저 frange 함수를 사용하여 제너레이터 객체 f를 생성합니다. 첫 번째 for 루프는 제너레이터가 생성한 모든 값을 반복하고 이를 출력합니다. 첫 번째 반복 후 제너레이터는 소모되었으며, 이는 이미 생성할 수 있는 모든 값을 생성했음을 의미합니다. 따라서 두 번째 for 루프에서 다시 반복하려고 하면 새로운 값을 생성하지 않습니다.
출력:
First iteration:
0 0.25 0.5 0.75 1.0 1.25 1.5 1.75
Second iteration:
두 번째 반복은 제너레이터가 이미 소모되었기 때문에 출력을 생성하지 않았습니다.
클래스를 사용하여 재사용 가능한 제너레이터 생성
동일한 값 시퀀스를 여러 번 반복해야 하는 경우 제너레이터를 클래스로 래핑할 수 있습니다. 이렇게 하면 새 반복을 시작할 때마다 새로운 제너레이터가 생성됩니다.
class FRange:
def __init__(self, start, stop, step):
self.start = start
self.stop = stop
self.step = step
def __iter__(self):
n = self.start
while n < self.stop:
yield n
n += self.step
## Create an instance
f = FRange(0, 2, 0.25)
## We can iterate multiple times
print("First iteration:")
for x in f:
print(x, end=' ')
print("\n")
print("Second iteration:")
for x in f:
print(x, end=' ')
print("\n")
이 코드에서 FRange 클래스를 정의합니다. __init__ 메서드는 start, stop, step 값을 초기화합니다. __iter__ 메서드는 Python 클래스의 특수 메서드입니다. 반복자를 생성하는 데 사용됩니다. __iter__ 메서드 내부에는 앞에서 정의한 frange 함수와 유사한 방식으로 값을 생성하는 제너레이터가 있습니다.
FRange 클래스의 인스턴스 f를 생성하고 여러 번 반복하면 각 반복은 __iter__ 메서드를 호출하여 새로운 제너레이터를 생성합니다. 따라서 동일한 값 시퀀스를 여러 번 얻을 수 있습니다.
출력:
First iteration:
0 0.25 0.5 0.75 1.0 1.25 1.5 1.75
Second iteration:
0 0.25 0.5 0.75 1.0 1.25 1.5 1.75
이번에는 __iter__() 메서드가 호출될 때마다 새로운 제너레이터를 생성하므로 여러 번 반복할 수 있습니다.
사용자 정의 클래스에 반복 추가하기
이제 제너레이터의 기본 사항을 이해했으므로, 이를 사용하여 사용자 정의 클래스에 반복 기능을 추가해 보겠습니다. Python 에서 클래스를 반복 가능하게 만들려면 __iter__() 특수 메서드를 구현해야 합니다. 반복 가능한 클래스를 사용하면 목록이나 튜플을 반복할 수 있는 것처럼 해당 요소들을 반복할 수 있습니다. 이는 사용자 정의 클래스를 더 유연하고 사용하기 쉽게 만드는 강력한 기능입니다.
__iter__() 메서드 이해하기
__iter__() 메서드는 클래스를 반복 가능하게 만드는 데 중요한 부분입니다. 반복자 (iterator) 객체를 반환해야 합니다. 반복자는 반복 (루프) 할 수 있는 객체입니다. 이를 달성하는 간단하고 효과적인 방법은 __iter__()를 제너레이터 함수로 정의하는 것입니다. 제너레이터 함수는 yield 키워드를 사용하여 한 번에 하나씩 일련의 값을 생성합니다. yield 문이 나타날 때마다 함수는 일시 중지되고 값을 반환합니다. 다음 번에 반복자가 호출되면 함수는 중단된 지점부터 재개됩니다.
Structure 클래스 수정하기
이 랩의 설정에서 기본 Structure 클래스를 제공했습니다. Stock과 같은 다른 클래스는 이 Structure 클래스에서 상속받을 수 있습니다. 상속은 기존 클래스의 속성과 메서드를 상속하는 새 클래스를 만드는 방법입니다. Structure 클래스에 __iter__() 메서드를 추가하면 모든 하위 클래스를 반복 가능하게 만들 수 있습니다. 즉, Structure에서 상속받는 모든 클래스는 자동으로 반복할 수 있는 기능을 갖게 됩니다.
- WebIDE 에서
structure.py파일을 엽니다.
cd ~/project
이 명령은 현재 작업 디렉토리를 structure.py 파일이 있는 project 디렉토리로 변경합니다. 파일을 액세스하고 수정하려면 올바른 디렉토리에 있어야 합니다.
Structure클래스의 현재 구현을 살펴봅니다.
class Structure(metaclass=StructureMeta):
_fields = []
def __init__(self, *args):
if len(args) != len(self._fields):
raise TypeError(f'Expected {len(self._fields)} arguments')
for name, val in zip(self._fields, args):
setattr(self, '_'+name, val)
Structure 클래스에는 속성 이름을 저장하는 _fields 목록이 있습니다. __init__() 메서드는 클래스의 생성자입니다. 전달된 인수의 수가 필드 수와 같은지 확인하여 객체의 속성을 초기화합니다. 그렇지 않으면 TypeError를 발생시킵니다. 그렇지 않으면 setattr() 함수를 사용하여 속성을 설정합니다.
- 각 속성 값을 순서대로 yield 하는
__iter__()메서드를 추가합니다.
def __iter__(self):
for name in self._fields:
yield getattr(self, name)
이 __iter__() 메서드는 제너레이터 함수입니다. _fields 목록을 반복하고 getattr() 함수를 사용하여 각 속성의 값을 가져옵니다. 그런 다음 yield 키워드는 값을 하나씩 반환합니다.
완전한 structure.py 파일은 이제 다음과 같이 표시됩니다.
class StructureMeta(type):
def __new__(cls, name, bases, clsdict):
fields = clsdict.get('_fields', [])
for name in fields:
clsdict[name] = property(lambda self, name=name: getattr(self, '_'+name))
return super().__new__(cls, name, bases, clsdict)
class Structure(metaclass=StructureMeta):
_fields = []
def __init__(self, *args):
if len(args) != len(self._fields):
raise TypeError(f'Expected {len(self._fields)} arguments')
for name, val in zip(self._fields, args):
setattr(self, '_'+name, val)
def __iter__(self):
for name in self._fields:
yield getattr(self, name)
이 업데이트된 Structure 클래스에는 __iter__() 메서드가 있어 이 클래스와 하위 클래스를 반복 가능하게 만듭니다.
파일을 저장합니다.
structure.py파일을 변경한 후에는 변경 사항을 적용하기 위해 저장해야 합니다.이제
Stock인스턴스를 생성하고 반복하여 반복 기능을 테스트해 보겠습니다.
python3 -c "from stock import Stock; s = Stock('GOOG', 100, 490.1); print('Iterating over Stock:'); [print(val) for val in s]"
이 명령은 Structure 클래스에서 상속받는 Stock 클래스의 인스턴스를 생성합니다. 그런 다음 리스트 컴프리헨션을 사용하여 인스턴스를 반복하고 각 값을 출력합니다.
다음과 같은 출력을 볼 수 있습니다.
Iterating over Stock:
GOOG
100
490.1
이제 Structure에서 상속받는 모든 클래스는 자동으로 반복 가능하며, 반복은 _fields 목록에 정의된 순서대로 속성 값을 yield 합니다. 즉, 반복을 위해 추가 코드를 작성하지 않고도 Structure의 모든 하위 클래스의 속성을 쉽게 반복할 수 있습니다.
반복 기능을 사용하여 클래스 향상시키기
이제 Structure 클래스와 하위 클래스가 반복을 지원하도록 만들었습니다. 반복은 Python 에서 항목 모음을 하나씩 반복할 수 있게 해주는 강력한 개념입니다. 클래스가 반복을 지원하면 더 유연해지고 많은 내장 Python 기능과 함께 작동할 수 있습니다. 이 반복 지원이 Python 에서 많은 강력한 기능을 어떻게 가능하게 하는지 살펴보겠습니다.
시퀀스 변환을 위한 반복 활용
Python 에는 list() 및 tuple()과 같은 내장 함수가 있습니다. 이러한 함수는 모든 반복 가능한 객체를 입력으로 사용할 수 있으므로 매우 유용합니다. 반복 가능한 객체는 목록, 튜플 또는 이제 Structure 클래스 인스턴스와 같이 반복할 수 있는 것입니다. Structure 클래스가 이제 반복을 지원하므로, 이를 쉽게 목록이나 튜플로 변환할 수 있습니다.
Stock인스턴스로 이러한 작업을 시도해 보겠습니다.Stock클래스는Structure의 하위 클래스입니다. 터미널에서 다음 명령을 실행합니다.
python3 -c "from stock import Stock; s = Stock('GOOG', 100, 490.1); print('As list:', list(s)); print('As tuple:', tuple(s))"
이 명령은 먼저 Stock 클래스를 가져오고, 해당 인스턴스를 생성한 다음, list() 및 tuple() 함수를 사용하여 이 인스턴스를 각각 목록과 튜플로 변환합니다. 출력은 인스턴스가 목록과 튜플로 표시되는 것을 보여줍니다.
As list: ['GOOG', 100, 490.1]
As tuple: ('GOOG', 100, 490.1)
언패킹 (Unpacking)
Python 에는 언패킹이라는 매우 유용한 기능이 있습니다. 언패킹을 사용하면 반복 가능한 객체를 가져와 해당 요소를 한 번에 개별 변수에 할당할 수 있습니다. Stock 인스턴스는 반복 가능하므로 이 언패킹 기능을 사용할 수 있습니다.
python3 -c "from stock import Stock; s = Stock('GOOG', 100, 490.1); name, shares, price = s; print(f'Name: {name}, Shares: {shares}, Price: {price}')"
이 코드에서는 Stock 인스턴스를 생성한 다음 해당 요소를 name, shares, price의 세 가지 변수로 언패킹합니다. 그런 다음 이러한 변수를 출력합니다. 출력은 이러한 변수의 값을 보여줍니다.
Name: GOOG, Shares: 100, Price: 490.1
비교 기능 추가
클래스가 반복을 지원하면 비교 연산을 구현하기가 더 쉬워집니다. 비교 연산은 두 객체가 같은지 확인하는 데 사용됩니다. Structure 클래스에 __eq__() 메서드를 추가하여 인스턴스를 비교해 보겠습니다.
structure.py파일을 다시 엽니다.__eq__()메서드는 두 객체를 비교하기 위해==연산자를 사용할 때 호출되는 Python 의 특수 메서드입니다.structure.py파일의Structure클래스에 다음 코드를 추가합니다.
def __eq__(self, other):
return isinstance(other, type(self)) and tuple(self) == tuple(other)
이 메서드는 먼저 isinstance() 함수를 사용하여 other 객체가 self와 동일한 클래스의 인스턴스인지 확인합니다. 그런 다음 self와 other를 모두 튜플로 변환하고 이러한 튜플이 같은지 확인합니다.
완전한 structure.py 파일은 이제 다음과 같이 표시됩니다.
class StructureMeta(type):
def __new__(cls, name, bases, clsdict):
fields = clsdict.get('_fields', [])
for name in fields:
clsdict[name] = property(lambda self, name=name: getattr(self, '_'+name))
return super().__new__(cls, name, bases, clsdict)
class Structure(metaclass=StructureMeta):
_fields = []
def __init__(self, *args):
if len(args) != len(self._fields):
raise TypeError(f'Expected {len(self._fields)} arguments')
for name, val in zip(self._fields, args):
setattr(self, '_'+name, val)
def __iter__(self):
for name in self._fields:
yield getattr(self, name)
def __eq__(self, other):
return isinstance(other, type(self)) and tuple(self) == tuple(other)
__eq__()메서드를 추가한 후structure.py파일을 저장합니다.비교 기능을 테스트해 보겠습니다. 터미널에서 다음 명령을 실행합니다.
python3 -c "from stock import Stock; a = Stock('GOOG', 100, 490.1); b = Stock('GOOG', 100, 490.1); c = Stock('AAPL', 200, 123.4); print(f'a == b: {a == b}'); print(f'a == c: {a == c}')"
이 코드는 세 개의 Stock 인스턴스 a, b, c를 생성합니다. 그런 다음 == 연산자를 사용하여 a를 b와, a를 c와 비교합니다. 출력은 이러한 비교의 결과를 보여줍니다.
a == b: True
a == c: False
- 이제 모든 것이 제대로 작동하는지 확인하기 위해 단위 테스트를 실행해야 합니다. 단위 테스트는 프로그램의 서로 다른 부분이 예상대로 작동하는지 확인하는 코드 집합입니다. 터미널에서 다음 명령을 실행합니다.
python3 teststock.py
모든 것이 제대로 작동하면 테스트가 통과되었음을 나타내는 출력이 표시됩니다.
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
두 개의 간단한 메서드 (__iter__() 및 __eq__()) 만 추가하여 Structure 클래스를 더 Pythonic 하고 사용하기 쉽게 만드는 기능을 크게 향상시켰습니다.
스트리밍 데이터를 위한 제너레이터 생성하기
프로그래밍에서 제너레이터는 특히 스트리밍 데이터 소스를 모니터링하는 것과 같은 실제 문제에 대처할 때 강력한 도구입니다. 이 섹션에서는 제너레이터에 대해 배운 내용을 이러한 실용적인 시나리오에 적용하는 방법을 배우겠습니다. 로그 파일을 주시하고 파일에 추가되는 새 줄을 제공하는 제너레이터를 만들 것입니다.
데이터 소스 설정하기
제너레이터를 만들기 전에 데이터 소스를 설정해야 합니다. 이 경우 주식 시장 데이터를 생성하는 시뮬레이션 프로그램을 사용합니다.
먼저 WebIDE 에서 새 터미널을 열어야 합니다. 여기에서 시뮬레이션을 시작하는 명령을 실행합니다.
터미널을 연 후 주식 시뮬레이션 프로그램을 실행합니다. 입력해야 하는 명령은 다음과 같습니다.
cd ~/project
python3 stocksim.py
첫 번째 명령 cd ~/project는 현재 디렉토리를 홈 디렉토리의 project 디렉토리로 변경합니다. 두 번째 명령 python3 stocksim.py는 주식 시뮬레이션 프로그램을 실행합니다. 이 프로그램은 주식 시장 데이터를 생성하고 현재 디렉토리의 stocklog.csv라는 파일에 씁니다. 모니터링 코드를 작업하는 동안 이 프로그램이 백그라운드에서 실행되도록 합니다.
간단한 파일 모니터 만들기
이제 데이터 소스가 설정되었으므로 stocklog.csv 파일을 모니터링하는 프로그램을 만들어 보겠습니다. 이 프로그램은 음수 가격 변동을 표시합니다.
- 먼저 WebIDE 에서
follow.py라는 새 파일을 만듭니다. 이렇게 하려면 터미널에서 다음 명령을 사용하여 디렉토리를project디렉토리로 변경해야 합니다.
cd ~/project
- 다음으로 다음 코드를
follow.py파일에 추가합니다. 이 코드는stocklog.csv파일을 열고 파일 포인터를 파일 끝으로 이동한 다음 새 줄을 지속적으로 확인합니다. 새 줄이 발견되고 음수 가격 변동을 나타내는 경우 주식 이름, 가격 및 변동을 출력합니다.
## 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('%10s %10.2f %10.2f' % (name, price, change))
- 코드를 추가한 후 파일을 저장합니다. 그런 다음 터미널에서 다음 명령을 사용하여 프로그램을 실행합니다.
python3 follow.py
음수 가격 변동이 있는 주식을 보여주는 출력이 표시됩니다. 다음과 같이 표시될 수 있습니다.
AAPL 148.24 -1.76
GOOG 2498.45 -1.55
프로그램을 중지하려면 터미널에서 Ctrl+C를 누릅니다.
제너레이터 함수로 변환하기
이전 코드가 작동하지만 제너레이터 함수로 변환하여 더 재사용 가능하고 모듈식으로 만들 수 있습니다. 제너레이터 함수는 일시 중지 및 재개할 수 있으며 한 번에 하나의 값을 yield 하는 특수한 유형의 함수입니다.
follow.py파일을 다시 열고 제너레이터 함수를 사용하도록 수정합니다. 업데이트된 코드는 다음과 같습니다.
## follow.py
import os
import time
def follow(filename):
"""
Generator function that yields new lines in a file as they are added.
Similar to the 'tail -f' Unix command.
"""
f = open(filename)
f.seek(0, os.SEEK_END) ## Move to the end of the file
while True:
line = f.readline()
if line == '':
time.sleep(0.1) ## Sleep briefly and retry
continue
yield line
## Example usage - monitor stocks with negative price changes
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('%10s %10.2f %10.2f' % (name, price, change))
follow 함수는 이제 제너레이터 함수입니다. 파일을 열고 끝으로 이동한 다음 새 줄을 지속적으로 확인합니다. 새 줄이 발견되면 해당 줄을 yield 합니다.
- 파일을 저장하고 다음 명령을 사용하여 다시 실행합니다.
python3 follow.py
출력은 이전과 동일해야 합니다. 그러나 이제 파일 모니터링 로직이 follow 제너레이터 함수에 깔끔하게 캡슐화됩니다. 즉, 이 함수를 파일을 모니터링해야 하는 다른 프로그램에서 재사용할 수 있습니다.
제너레이터의 강력함 이해하기
파일 읽기 코드를 제너레이터 함수로 변환하여 훨씬 더 유연하고 재사용 가능하게 만들었습니다. follow() 함수는 주식 데이터뿐만 아니라 파일을 모니터링해야 하는 모든 프로그램에서 사용할 수 있습니다.
예를 들어, 서버 로그, 애플리케이션 로그 또는 시간이 지남에 따라 업데이트되는 다른 파일을 모니터링하는 데 사용할 수 있습니다. 이는 제너레이터가 스트리밍 데이터 소스를 깨끗하고 모듈식 방식으로 처리하는 훌륭한 방법임을 보여줍니다.
요약
이 랩에서는 제너레이터를 사용하여 Python 에서 반복을 사용자 정의하는 방법을 배웠습니다. yield 문을 사용하여 값 시퀀스를 생성하는 간단한 제너레이터를 만들고, __iter__() 메서드를 구현하여 사용자 정의 클래스에 반복 지원을 추가하고, 시퀀스 변환, 언패킹 (unpacking) 및 비교를 위해 반복을 활용하고, 스트리밍 데이터 소스를 모니터링하기 위한 실용적인 제너레이터를 구축했습니다.
제너레이터는 최소한의 코드로 반복자를 생성할 수 있게 해주는 강력한 Python 기능입니다. 특히 대규모 데이터 세트를 처리하고, 스트리밍 데이터를 사용하고, 데이터 파이프라인을 만들고, 사용자 정의 반복 패턴을 구현하는 데 유용합니다. 제너레이터를 사용하면 의도를 명확하게 전달하는 더 깨끗하고 메모리 효율적인 코드를 작성할 수 있습니다.