
20. 데코레이터와 제너레이터 - 중급으로 가는 관문
함수를 감싸는 함수, 값을 하나씩 내보내는 함수. 파이썬의 데코레이터와 제너레이터는 처음엔 낯설지만, 한번 익히면 코드의 품격이 달라진다. 중급 파이썬으로 가는 관문을 열어보자.
일급 객체 (First-Class Object)
파이썬에서 함수는 일급 객체다. 즉, 함수를 변수에 담고, 인자로 전달하고, 반환값으로 쓸 수 있다.
# 1. 변수에 담기
def greet(name):
return f"안녕, {name}!"
hello = greet # 함수를 변수에 대입
print(hello("철수")) # 안녕, 철수!
# 2. 함수를 인자로 전달
def apply(func, value):
return func(value)
print(apply(len, "파이썬")) # 3
print(apply(str.upper, "hello")) # HELLO
# 3. 함수를 반환
def make_greeter(greeting):
def greeter(name):
return f"{greeting}, {name}!"
return greeter
hi = make_greeter("안녕")
bye = make_greeter("잘가")
print(hi("철수")) # 안녕, 철수!
print(bye("영희")) # 잘가, 영희!
클로저 (Closure)
클로저는 내부 함수가 외부 함수의 변수를 기억하는 것이다.
def make_counter():
count = 0
def counter():
nonlocal count # 외부 함수의 변수 수정
count += 1
return count
return counter
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
💡
make_counter()실행이 끝났는데도count가 살아있다! 내부 함수counter가count를 기억하고 있기 때문이다.
def make_multiplier(n):
def multiply(x):
return x * n # n을 기억!
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
데코레이터 (Decorator)
데코레이터는 함수를 감싸서 기능을 추가하는 함수다.
데코레이터 없이 — 수동 방식
def say_hello():
print("Hello!")
def add_greeting(func):
def wrapper():
print("===== 인사 시작 =====")
func()
print("===== 인사 끝 =====")
return wrapper
# 수동으로 감싸기
say_hello = add_greeting(say_hello)
say_hello()
# ===== 인사 시작 =====
# Hello!
# ===== 인사 끝 =====
@ 문법 — 데코레이터 적용
def add_greeting(func):
def wrapper():
print("===== 인사 시작 =====")
func()
print("===== 인사 끝 =====")
return wrapper
@add_greeting # say_hello = add_greeting(say_hello) 와 같음
def say_hello():
print("Hello!")
say_hello()
# ===== 인사 시작 =====
# Hello!
# ===== 인사 끝 =====

인자가 있는 함수에 데코레이터 적용
def log_call(func):
def wrapper(*args, **kwargs):
print(f"[LOG] {func.__name__} 호출 - args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} 반환 → {result}")
return result
return wrapper
@log_call
def add(a, b):
return a + b
@log_call
def greet(name, greeting="안녕"):
return f"{greeting}, {name}!"
print(add(3, 5))
# [LOG] add 호출 - args: (3, 5), kwargs: {}
# [LOG] add 반환 → 8
# 8
print(greet("철수", greeting="하이"))
# [LOG] greet 호출 - args: ('철수',), kwargs: {'greeting': '하이'}
# [LOG] greet 반환 → 하이, 철수!
# 하이, 철수!
실전 데코레이터: 실행 시간 측정
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"[{func.__name__}] 실행 시간: {end - start:.4f}초")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "완료!"
@timer
def calculate_sum(n):
return sum(range(n + 1))
print(slow_function())
# [slow_function] 실행 시간: 1.00xx초
# 완료!
print(calculate_sum(1000000))
# [calculate_sum] 실행 시간: 0.0xxx초
# 500000500000
실전 데코레이터: 호출 횟수 카운터
def count_calls(func):
def wrapper(*args, **kwargs):
wrapper.calls += 1
print(f"[{func.__name__}] {wrapper.calls}번째 호출")
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
@count_calls
def say_hi():
print("Hi!")
say_hi() # [say_hi] 1번째 호출 → Hi!
say_hi() # [say_hi] 2번째 호출 → Hi!
say_hi() # [say_hi] 3번째 호출 → Hi!
제너레이터 (Generator)
제너레이터는 yield 키워드를 사용해 값을 하나씩 생성하는 함수다. 일반 함수와 달리, 호출할 때마다 멈췄던 위치에서 다시 실행된다.
return vs yield
# 일반 함수 — 한 번에 모든 결과 반환
def get_numbers_list(n):
result = []
for i in range(n):
result.append(i)
return result
# 제너레이터 — 하나씩 생성
def get_numbers_gen(n):
for i in range(n):
yield i # 값을 하나 내보내고 멈춤
# 사용
numbers_list = get_numbers_list(5) # [0, 1, 2, 3, 4] 한 번에 메모리에
numbers_gen = get_numbers_gen(5) # 제너레이터 객체 (아직 값 없음)
print(type(numbers_gen)) # <class 'generator'>
next()로 값 하나씩 꺼내기
gen = get_numbers_gen(3)
print(next(gen)) # 0 (첫 번째 yield에서 멈춤)
print(next(gen)) # 1 (두 번째 yield에서 멈춤)
print(next(gen)) # 2 (세 번째 yield에서 멈춤)
print(next(gen)) # StopIteration 에러!
for문으로 순회
def countdown(n):
while n > 0:
yield n
n -= 1
for num in countdown(5):
print(num, end=" ")
# 5 4 3 2 1

제너레이터의 장점: 메모리 효율
import sys
# 리스트: 메모리에 모든 값 저장
big_list = [i for i in range(1000000)]
print(f"리스트 크기: {sys.getsizeof(big_list):,} bytes")
# 리스트 크기: 8,448,728 bytes (약 8MB)
# 제너레이터: 값을 하나씩 생성
big_gen = (i for i in range(1000000))
print(f"제너레이터 크기: {sys.getsizeof(big_gen):,} bytes")
# 제너레이터 크기: 200 bytes (고정!)
💡 대용량 데이터를 다룰 때 제너레이터를 사용하면 메모리를 획기적으로 절약할 수 있다!
제너레이터 표현식
리스트 컴프리헨션의 []를 ()로 바꾸면 제너레이터가 된다.
# 리스트 컴프리헨션
squares_list = [x ** 2 for x in range(10)]
# 제너레이터 표현식
squares_gen = (x ** 2 for x in range(10))
print(sum(squares_list)) # 285
print(sum(squares_gen)) # 285 (메모리 효율적)
실전 예시: 파일 읽기 제너레이터
def read_large_file(filepath):
"""대용량 파일을 한 줄씩 읽는 제너레이터"""
with open(filepath, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()
# 사용 (파일이 10GB여도 메모리 걱정 없음!)
# for line in read_large_file("huge_data.txt"):
# process(line)
무한 시퀀스
def infinite_counter(start=0):
"""무한히 숫자를 생성하는 제너레이터"""
n = start
while True:
yield n
n += 1
# 처음 10개만 사용
counter = infinite_counter(1)
for _ in range(10):
print(next(counter), end=" ")
# 1 2 3 4 5 6 7 8 9 10
피보나치 제너레이터
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib), end=" ")
# 0 1 1 2 3 5 8 13 21 34
직접 해보기
문제 1. 함수 실행 전후에 "시작"과 "끝"을 출력하는 border 데코레이터를 만들어보자.
문제 2. 함수의 결과를 2배로 만드는 double_result 데코레이터를 만들어보자.
문제 3. 짝수만 생성하는 제너레이터 even_numbers(n)을 만들어보자. (0부터 n 미만)
문제 4. 제너레이터 표현식으로 1부터 100까지의 3의 배수의 합을 구해보자.
문제 5. 비밀번호를 무한히 생성하는 제너레이터를 만들어보자. (알파벳+숫자 8자리)
정답 보기
# 문제 1
def border(func):
def wrapper(*args, **kwargs):
print("=" * 30)
print("시작")
print("=" * 30)
result = func(*args, **kwargs)
print("=" * 30)
print("끝")
print("=" * 30)
return result
return wrapper
@border
def say_hello(name):
print(f"안녕하세요, {name}님!")
say_hello("철수")
# 문제 2
def double_result(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
return wrapper
@double_result
def add(a, b):
return a + b
print(add(3, 5)) # 16 (8의 2배)
# 문제 3
def even_numbers(n):
for i in range(0, n, 2):
yield i
for num in even_numbers(20):
print(num, end=" ")
# 0 2 4 6 8 10 12 14 16 18
print()
# 문제 4
result = sum(x for x in range(1, 101) if x % 3 == 0)
print(result) # 1683
# 문제 5
import random
import string
def password_generator():
chars = string.ascii_letters + string.digits
while True:
yield ''.join(random.choice(chars) for _ in range(8))
gen = password_generator()
for _ in range(5):
print(next(gen))
오늘의 정리
| 항목 | 내용 |
|---|---|
| 일급 객체 | 함수를 변수에 담고, 인자로 전달하고, 반환할 수 있음 |
| 클로저 | 내부 함수가 외부 함수의 변수를 기억하는 것 |
| 데코레이터 | @데코레이터로 함수를 감싸서 기능 추가 |
| yield | 값을 하나 내보내고 함수 실행을 일시 정지 |
| 제너레이터 | yield를 사용하는 함수. 메모리 효율적 |
| 제너레이터 표현식 | (표현식 for x in 반복) — 소괄호 사용 |
| 실전 활용 | 타이머, 로깅, 호출 카운터, 대용량 파일 읽기 |
다음 편 예고: 모듈과 패키지 - 남이 만든 코드 활용하기
import로 다른 사람이 만든 코드를 가져다 쓰는 방법, 파이썬의 강력한 표준 라이브러리, 그리고 pip install로 외부 패키지를 설치하는 방법을 알아보자.
태그: 파이썬 Python 파이썬독학 데코레이터 decorator 제너레이터 generator yield 파이썬중급 IT교육
'Python' 카테고리의 다른 글
| 22. 에러와 예외처리 - 프로그램이 죽지 않게 (0) | 2026.02.22 |
|---|---|
| 21. 모듈과 패키지 - 남이 만든 코드 활용하기 (0) | 2026.02.22 |
| 19. 재귀 함수 - 자기 자신을 호출하는 함수 (0) | 2026.02.22 |
| 18. 함수 (2) - 스코프와 고급 기능 (0) | 2026.02.22 |
| 17. 함수 (1) - 코드를 재사용하는 방법 (0) | 2026.02.22 |
댓글