[Python 기초]
[Python] Iterator 와 반복 가능한 객체, Generator 정리
dyk98
2024. 11. 27. 19:33
1. 반복 가능한 객체 (Iterable)
- 반복 가능한 객체(Iterable): for 루프에서 반복할 수 있는 객체.
- 예: 리스트, 튜플, 문자열, 딕셔너리, 세트.
- 내부적으로 __iter__() 메서드를 제공하여 이터레이터를 반환.
코드 예시
numbers = [1, 2, 3, 4]
for num in numbers:
print(num)
2. 이터레이터 (Iterator)
- 이터레이터(Iterator): 반복 가능한 객체의 요소를 하나씩 꺼내오는 객체.
- __iter__(): 이터레이터 객체 자신을 반환.
- __next__(): 다음 요소를 반환하며, 더 이상 요소가 없으면 StopIteration 예외 발생.
코드 예시
numbers = [1, 2, 3]
iterator = iter(numbers) # 리스트에서 이터레이터 생성
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
자연수 생성 이터레이터
class NaturalNumbers:
def __init__(self, limit):
self.current = 1
self.limit = limit
def __iter__(self):
return self
def __next__(self):
if self.current <= self.limit:
result = self.current
self.current += 1
return result
raise StopIteration
# 1부터 10까지의 자연수를 출력
for num in NaturalNumbers(10):
print(num)
3. 제너레이터 (Generator)
- Generator: 이터레이터를 생성하는 함수.
- yield 키워드를 사용하여 값을 반환하며, 함수의 실행 상태를 유지.
- 메모리를 효율적으로 사용하며, 필요할 때 값을 생성(lazy evaluation).
특징
- Generator 함수는 호출될 때 객체를 반환.
- yield가 호출될 때까지 실행되고, 다음 호출 시 이전 상태에서 다시 시작.
코드 예시
def even_numbers(limit):
num = 2
while num <= limit:
yield num
num += 2
# 2부터 20까지의 짝수를 출력
for even in even_numbers(20):
print(even)
무한 피보나치 수열 Generator
def infinite_fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 피보나치 수열의 처음 10개 항 출력
fib_gen = infinite_fibonacci()
for _ in range(10):
print(next(fib_gen))
4. Generator표현식
- :List comprehension과 유사하지만 [] 대신 ()를 사용하여 생성.
- 메모리를 효율적으로 사용하며, 값을 필요할 때 계산.
코드 예시
gen_exp = (x ** 2 for x in range(5))
for num in gen_exp:
print(num) # 0, 1, 4, 9, 16
새로운 제너레이터 표현식 예시
제곱수 생성
squared_numbers = (x ** 2 for x in range(1, 11))
for square in squared_numbers:
print(square)
문자열 길이 구하기
strings = ["apple", "banana", "cherry", "date"]
string_lengths = (len(s) for s in strings)
for length in string_lengths:
print(length)
Generator 와 Iterator 비교 예시
List 대신 Iterator와 Generator를 활용한 계산
# 리스트로 구현 (메모리 사용 높음)
squares_list = [x ** 2 for x in range(1, 1001)]
print(squares_list[:10]) # 처음 10개 출력
# 제너레이터로 구현 (메모리 사용 적음)
squares_gen = (x ** 2 for x in range(1, 1001))
for _ in range(10):
print(next(squares_gen))
실용적인 Generator 예시
대용량 로그 파일 처리
def read_large_file(file_path):
with open(file_path, "r") as file:
for line in file:
yield line.strip()
# 사용 예시: 대용량 로그 파일에서 'ERROR'가 포함된 줄 출력
for log in read_large_file("large_log_file.txt"):
if "ERROR" in log:
print(log)
시간 단위로 데이터 생성
import time
def live_data_stream():
while True:
yield time.ctime() # 현재 시간을 반환
time.sleep(1) # 1초 대기
# 5초 동안 실시간 시간 데이터 출력
for _ in range(5):
print(next(live_data_stream()))
5. Generator 와 Iterator 의 장점
- 메모리 효율성
- 데이터를 미리 생성하지 않고 필요할 때만 생성.
- 큰 데이터를 처리하거나 무한 반복에 유용.
- 느린 계산(계산 지연)
- 값을 즉시 계산하지 않고 호출 시 계산.
- 코드 간결화
- 복잡한 반복 작업을 간단하게 작성 가능.
Iterable | 반복 가능한 객체. 예: 리스트, 문자열, 딕셔너리. 내부적으로 iter() 메서드를 가짐. |
Iterator | 반복 가능한 객체에서 요소를 하나씩 꺼내는 객체. iter(), next() 메서드를 가짐. |
Generator | yield 키워드를 사용하여 값을 하나씩 반환하는 함수. 메모리 효율이 높음. |
Generator Expression | 리스트 컴프리헨션과 유사한 구문으로 제너레이터를 생성. () 사용. |
Decorator 와 with 구문
데코레이터 (Decorator)
- 정의: 함수나 메서드에 추가 기능을 부여하는 함수.
- 사용 방법: @decorator_name으로 적용.
- 장점:
- 재사용성: 여러 함수에 동일한 기능 적용.
- 가독성: 코드가 더 간결하고 읽기 쉬움.
- 중복 제거: 반복되는 코드 패턴을 제거.
- 활용 예시:
- 함수 실행 시간 측정
- 사용 권한 확인
- 로그 기록 추가
함수 실행 시간을 측정하는 Decorator
import time
def time_logger(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Function '{func.__name__}' executed in {end_time - start_time:.4f} seconds")
return result
return wrapper
@time_logger
def calculate_sum(n):
return sum(range(1, n+1))
# 함수 호출
print(calculate_sum(1000000))
사용 권한을 확인하는 Decorator
def authorize(func):
def wrapper(user, *args, **kwargs):
if user != "admin":
print("Access denied. Only 'admin' can execute this function.")
return
return func(user, *args, **kwargs)
return wrapper
@authorize
def secure_function(user):
print(f"Welcome, {user}. You have access!")
# 함수 호출
secure_function("admin") # Access granted
secure_function("guest") # Access denied
반환값을 변경하는 Decorator
def double_return(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
return wrapper
@double_return
def get_number():
return 5
print(get_number()) # 10