본문으로 건너뛰기

고급 함수

람다 함수 심화

map() 함수

# 기본 사용
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # [1, 4, 9, 16, 25]

# 일반 함수와 비교
def square(x):
return x ** 2

squared2 = list(map(square, numbers))
print(squared2) # [1, 4, 9, 16, 25]

# 여러 인자
def add(x, y):
return x + y

a = [1, 2, 3]
b = [10, 20, 30]
result = list(map(add, a, b))
print(result) # [11, 22, 33]

# 람다로
result2 = list(map(lambda x, y: x + y, a, b))
print(result2) # [11, 22, 33]

# 실용 예제
prices = [100, 200, 300, 400]
with_tax = list(map(lambda x: x * 1.1, prices))
print(with_tax) # [110.0, 220.0, 330.0, 440.0]

# 문자열 처리
names = ["alice", "bob", "charlie"]
upper_names = list(map(str.upper, names))
print(upper_names) # ['ALICE', 'BOB', 'CHARLIE']

# 복잡한 변환
users = [
{"name": "홍길동", "age": 25},
{"name": "김철수", "age": 30}
]
names_only = list(map(lambda u: u["name"], users))
print(names_only) # ['홍길동', '김철수']

filter() 함수

# 짝수만 필터링
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8, 10]

# 양수만
values = [-5, -2, 0, 3, 7, -1, 10]
positives = list(filter(lambda x: x > 0, values))
print(positives) # [3, 7, 10]

# 리스트 컴프리헨션과 비교
# filter 사용
filtered1 = list(filter(lambda x: x % 2 == 0, numbers))

# 리스트 컴프리헨션
filtered2 = [x for x in numbers if x % 2 == 0]

print(filtered1 == filtered2) # True

# 실용 예제
users = [
{"name": "홍길동", "age": 25, "active": True},
{"name": "김철수", "age": 30, "active": False},
{"name": "이영희", "age": 28, "active": True}
]

# 활성 사용자만
active_users = list(filter(lambda u: u["active"], users))
print([u["name"] for u in active_users]) # ['홍길동', '이영희']

# 30세 미만
young_users = list(filter(lambda u: u["age"] < 30, users))
print([u["name"] for u in young_users]) # ['홍길동', '이영희']

# 빈 문자열 제거
words = ["apple", "", "banana", "", "cherry"]
non_empty = list(filter(None, words)) # None은 falsy 값 제거
print(non_empty) # ['apple', 'banana', 'cherry']

# 또는
non_empty2 = list(filter(lambda x: x, words))
print(non_empty2) # ['apple', 'banana', 'cherry']

reduce() 함수

from functools import reduce

# 모든 수 곱하기
numbers = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, numbers)
print(product) # 120 (1 × 2 × 3 × 4 × 5)

# 합계 (sum()과 같음)
total = reduce(lambda x, y: x + y, numbers)
print(total) # 15

# 초기값 지정
total_with_initial = reduce(lambda x, y: x + y, numbers, 10)
print(total_with_initial) # 25 (10 + 1 + 2 + 3 + 4 + 5)

# 최댓값 찾기 (max()와 같음)
maximum = reduce(lambda x, y: x if x > y else y, numbers)
print(maximum) # 5

# 실용 예제: 중첩 딕셔너리 접근
data = {
"user": {
"profile": {
"name": "홍길동"
}
}
}

keys = ["user", "profile", "name"]
result = reduce(lambda d, key: d[key], keys, data)
print(result) # 홍길동

# 여러 리스트 합치기
lists = [[1, 2], [3, 4], [5, 6]]
flattened = reduce(lambda x, y: x + y, lists)
print(flattened) # [1, 2, 3, 4, 5, 6]

# 문자열 연결
words = ["Python", "is", "awesome"]
sentence = reduce(lambda x, y: f"{x} {y}", words)
print(sentence) # Python is awesome

map, filter, reduce 조합

# 데이터 파이프라인
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 짝수만 → 제곱 → 합계
result = reduce(
lambda x, y: x + y,
map(
lambda x: x ** 2,
filter(lambda x: x % 2 == 0, numbers)
)
)
print(result) # 220 (4 + 16 + 36 + 64 + 100)

# 더 읽기 쉬운 방식
evens = filter(lambda x: x % 2 == 0, numbers)
squared = map(lambda x: x ** 2, evens)
total = reduce(lambda x, y: x + y, squared)
print(total) # 220

# 리스트 컴프리헨션 + sum
result2 = sum(x ** 2 for x in numbers if x % 2 == 0)
print(result2) # 220

데코레이터 (Decorators)

데코레이터란?

# 함수를 감싸는 함수
def my_decorator(func):
def wrapper():
print("함수 실행 전")
func()
print("함수 실행 후")
return wrapper

def say_hello():
print("Hello!")

# 데코레이터 적용
decorated = my_decorator(say_hello)
decorated()
# 함수 실행 전
# Hello!
# 함수 실행 후

# @ 문법 사용
@my_decorator
def say_hi():
print("Hi!")

say_hi()
# 함수 실행 전
# Hi!
# 함수 실행 후

인자가 있는 함수 데코레이터

def my_decorator(func):
def wrapper(*args, **kwargs):
print(f"함수 {func.__name__} 호출")
print(f"인자: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"결과: {result}")
return result
return wrapper

@my_decorator
def add(a, b):
return a + b

result = add(3, 5)
# 함수 add 호출
# 인자: (3, 5), {}
# 결과: 8

@my_decorator
def greet(name, greeting="안녕하세요"):
return f"{greeting}, {name}님!"

greet("홍길동", greeting="반갑습니다")
# 함수 greet 호출
# 인자: ('홍길동',), {'greeting': '반갑습니다'}
# 결과: 반갑습니다, 홍길동님!

실용적인 데코레이터

import time
import functools

# 1. 실행 시간 측정
def timer(func):
@functools.wraps(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 "완료"

slow_function() # slow_function: 1.0012초

# 2. 로깅
def logger(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[LOG] {func.__name__} 호출")
print(f"[LOG] 인자: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"[LOG] 반환값: {result}")
return result
return wrapper

@logger
def divide(a, b):
return a / b

divide(10, 2)
# [LOG] divide 호출
# [LOG] 인자: (10, 2), {}
# [LOG] 반환값: 5.0

# 3. 캐싱 (메모이제이션)
def memoize(func):
cache = {}

@functools.wraps(func)
def wrapper(*args):
if args in cache:
print(f"캐시에서 반환: {args}")
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper

@memoize
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10)) # 빠름!
print(fibonacci(10)) # 캐시에서 반환

# 4. 타입 검증
def validate_types(*expected_types):
def decorator(func):
@functools.wraps(func)
def wrapper(*args):
for arg, expected_type in zip(args, expected_types):
if not isinstance(arg, expected_type):
raise TypeError(
f"Expected {expected_type.__name__}, "
f"got {type(arg).__name__}"
)
return func(*args)
return wrapper
return decorator

@validate_types(int, int)
def add(a, b):
return a + b

print(add(3, 5)) # 8
# print(add("3", 5)) # TypeError!

# 5. 재시도
def retry(max_attempts=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"시도 {attempt + 1} 실패: {e}")
if attempt == max_attempts - 1:
raise
return None
return wrapper
return decorator

@retry(max_attempts=3)
def unstable_function():
import random
if random.random() < 0.7:
raise ValueError("랜덤 에러")
return "성공"

# unstable_function() # 최대 3번 시도

여러 데코레이터 적용

@timer
@logger
def calculate(x, y):
return x + y

# 실행 순서: timer(logger(calculate))
calculate(3, 5)

# 클래스 데코레이터
def singleton(cls):
"""싱글톤 패턴"""
instances = {}

@functools.wraps(cls)
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper

@singleton
class Database:
def __init__(self):
print("Database 연결")

db1 = Database() # Database 연결
db2 = Database() # 출력 없음 (같은 인스턴스)
print(db1 is db2) # True

제너레이터 (Generators)

제너레이터란?

# 일반 함수 (리스트 반환)
def get_numbers(n):
result = []
for i in range(n):
result.append(i)
return result

numbers = get_numbers(5)
print(numbers) # [0, 1, 2, 3, 4]

# 제너레이터 (yield 사용)
def generate_numbers(n):
for i in range(n):
yield i

gen = generate_numbers(5)
print(gen) # <generator object>

# 하나씩 가져오기
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2

# for 문으로 순회
gen2 = generate_numbers(5)
for num in gen2:
print(num) # 0, 1, 2, 3, 4

제너레이터의 장점

# 메모리 효율적
import sys

# 리스트: 모든 값을 메모리에 저장
def list_range(n):
return [i for i in range(n)]

# 제너레이터: 값을 필요할 때만 생성
def gen_range(n):
for i in range(n):
yield i

n = 1000000

list_result = list_range(n)
gen_result = gen_range(n)

print(f"리스트 크기: {sys.getsizeof(list_result):,} bytes")
print(f"제너레이터 크기: {sys.getsizeof(gen_result)} bytes")
# 리스트 크기: 8,000,056 bytes
# 제너레이터 크기: 112 bytes

# 무한 시퀀스 가능
def infinite_counter():
n = 0
while True:
yield n
n += 1

counter = infinite_counter()
for i in range(5):
print(next(counter)) # 0, 1, 2, 3, 4

제너레이터 표현식

# 리스트 컴프리헨션
squares_list = [x ** 2 for x in range(10)]
print(type(squares_list)) # <class 'list'>

# 제너레이터 표현식 (괄호 사용)
squares_gen = (x ** 2 for x in range(10))
print(type(squares_gen)) # <class 'generator'>

# 하나씩 가져오기
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1

# sum, max 등에 바로 사용 가능
total = sum(x ** 2 for x in range(100))
print(total) # 328350

# 메모리 효율적인 파일 처리
def read_large_file(file_path):
with open(file_path) as f:
for line in f:
yield line.strip()

# for line in read_large_file('huge.txt'):
# process(line) # 한 줄씩 처리

실용적인 제너레이터

# 1. 피보나치 수열
def fibonacci_gen(max_count=None):
a, b = 0, 1
count = 0

while max_count is None or count < max_count:
yield a
a, b = b, a + b
count += 1

# 처음 10개만
for num in fibonacci_gen(10):
print(num, end=" ")
print() # 0 1 1 2 3 5 8 13 21 34

# 2. 배치 처리
def batch_generator(data, batch_size):
"""데이터를 배치로 나누기"""
for i in range(0, len(data), batch_size):
yield data[i:i + batch_size]

data = list(range(1, 21))
for batch in batch_generator(data, 5):
print(batch)
# [1, 2, 3, 4, 5]
# [6, 7, 8, 9, 10]
# [11, 12, 13, 14, 15]
# [16, 17, 18, 19, 20]

# 3. 윈도우 슬라이딩
def sliding_window(data, window_size):
"""슬라이딩 윈도우"""
for i in range(len(data) - window_size + 1):
yield data[i:i + window_size]

numbers = [1, 2, 3, 4, 5]
for window in sliding_window(numbers, 3):
print(window)
# [1, 2, 3]
# [2, 3, 4]
# [3, 4, 5]

# 4. 파일 줄 단위 처리
def process_lines(file_path):
"""파일을 줄 단위로 처리"""
with open(file_path) as f:
for line_num, line in enumerate(f, 1):
# 빈 줄 건너뛰기
if not line.strip():
continue

# 주석 건너뛰기
if line.strip().startswith('#'):
continue

yield line_num, line.strip()

# 5. 순열/조합 제너레이터
def permutations(items, n):
"""간단한 순열 생성"""
if n == 0:
yield []
return

for i in range(len(items)):
for perm in permutations(items[:i] + items[i+1:], n - 1):
yield [items[i]] + perm

for perm in permutations([1, 2, 3], 2):
print(perm)
# [1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]

yield from

# 중첩 제너레이터
def generator1():
yield 1
yield 2

def generator2():
yield 3
yield 4

# ❌ 비효율적
def combined():
for item in generator1():
yield item
for item in generator2():
yield item

# ✅ yield from 사용
def combined_better():
yield from generator1()
yield from generator2()

for num in combined_better():
print(num) # 1, 2, 3, 4

# 재귀적 트리 순회
def traverse_tree(node):
"""트리 순회 제너레이터"""
yield node['value']
for child in node.get('children', []):
yield from traverse_tree(child)

tree = {
'value': 1,
'children': [
{'value': 2, 'children': [{'value': 4}, {'value': 5}]},
{'value': 3}
]
}

print(list(traverse_tree(tree))) # [1, 2, 4, 5, 3]

이터레이터 프로토콜

이터레이터란?

# 이터러블: __iter__() 메서드가 있는 객체
# 이터레이터: __next__() 메서드가 있는 객체

# 리스트는 이터러블
numbers = [1, 2, 3]

# 이터레이터 얻기
iterator = iter(numbers)

# next()로 값 가져오기
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# print(next(iterator)) # StopIteration 에러

# for 문의 내부 동작
numbers = [1, 2, 3]
iterator = iter(numbers)
while True:
try:
item = next(iterator)
print(item)
except StopIteration:
break

커스텀 이터레이터

# 이터레이터 클래스
class CountDown:
def __init__(self, start):
self.current = start

def __iter__(self):
return self

def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1

# 사용
for num in CountDown(5):
print(num) # 5, 4, 3, 2, 1

# 재사용 가능한 이터러블
class CountDownIterable:
def __init__(self, start):
self.start = start

def __iter__(self):
return CountDown(self.start)

cd = CountDownIterable(3)
print(list(cd)) # [3, 2, 1]
print(list(cd)) # [3, 2, 1] (재사용 가능)

# 실용 예제: 범위 이터레이터
class MyRange:
def __init__(self, start, end, step=1):
self.current = start
self.end = end
self.step = step

def __iter__(self):
return self

def __next__(self):
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += self.step
return value

for num in MyRange(0, 10, 2):
print(num, end=" ") # 0 2 4 6 8
print()

# 제너레이터로 더 간단하게
def my_range(start, end, step=1):
current = start
while current < end:
yield current
current += step

for num in my_range(0, 10, 2):
print(num, end=" ") # 0 2 4 6 8

실전 예제

데이터 처리 파이프라인

def read_data():
"""데이터 읽기"""
data = [
{"name": "Alice", "age": 25, "score": 85},
{"name": "Bob", "age": 30, "score": 92},
{"name": "Charlie", "age": 22, "score": 78},
{"name": "David", "age": 35, "score": 95},
{"name": "Eve", "age": 28, "score": 88}
]
for item in data:
yield item

def filter_adults(data_gen):
"""성인만 필터링"""
for item in data_gen:
if item["age"] >= 25:
yield item

def add_grade(data_gen):
"""학점 추가"""
for item in data_gen:
score = item["score"]
if score >= 90:
item["grade"] = "A"
elif score >= 80:
item["grade"] = "B"
else:
item["grade"] = "C"
yield item

def format_output(data_gen):
"""출력 형식"""
for item in data_gen:
yield f"{item['name']} ({item['age']}세): {item['score']}점 ({item['grade']})"

# 파이프라인 실행
pipeline = format_output(add_grade(filter_adults(read_data())))

for result in pipeline:
print(result)
# Alice (25세): 85점 (B)
# Bob (30세): 92점 (A)
# David (35세): 95점 (A)
# Eve (28세): 88점 (B)

캐싱 데코레이터 고급

import time
from collections import OrderedDict

def lru_cache(max_size=128):
"""LRU 캐시 데코레이터"""
def decorator(func):
cache = OrderedDict()

@functools.wraps(func)
def wrapper(*args):
if args in cache:
# 최근 사용으로 이동
cache.move_to_end(args)
return cache[args]

# 캐시 크기 제한
if len(cache) >= max_size:
cache.popitem(last=False)

# 계산 및 캐시
result = func(*args)
cache[args] = result
return result

def cache_info():
return f"캐시 크기: {len(cache)}/{max_size}"

wrapper.cache_info = cache_info
wrapper.cache_clear = lambda: cache.clear()

return wrapper
return decorator

@lru_cache(max_size=3)
def expensive_function(n):
print(f"계산 중: {n}")
time.sleep(0.1)
return n ** 2

print(expensive_function(1)) # 계산 중: 1
print(expensive_function(2)) # 계산 중: 2
print(expensive_function(1)) # 캐시에서 (계산 안 함)
print(expensive_function(3)) # 계산 중: 3
print(expensive_function(4)) # 계산 중: 4 (캐시 초과, 1 제거)
print(expensive_function(1)) # 계산 중: 1 (다시 계산)
print(expensive_function.cache_info()) # 캐시 크기: 3/3

제너레이터 기반 데이터 분석

def analyze_log_file(file_path):
"""로그 파일 분석"""

def read_lines(path):
"""줄 단위로 읽기"""
with open(path) as f:
for line in f:
yield line.strip()

def parse_line(lines):
"""줄 파싱"""
for line in lines:
parts = line.split('|')
if len(parts) == 4:
yield {
'timestamp': parts[0],
'level': parts[1],
'module': parts[2],
'message': parts[3]
}

def filter_errors(entries):
"""에러만 필터링"""
for entry in entries:
if entry['level'] == 'ERROR':
yield entry

def group_by_module(entries):
"""모듈별 그룹화"""
groups = {}
for entry in entries:
module = entry['module']
if module not in groups:
groups[module] = []
groups[module].append(entry)
return groups

# 파이프라인
lines = read_lines(file_path)
entries = parse_line(lines)
errors = filter_errors(entries)
grouped = group_by_module(errors)

return grouped

# 사용 예제 (파일이 있다면)
# errors_by_module = analyze_log_file('app.log')
# for module, errors in errors_by_module.items():
# print(f"{module}: {len(errors)}개 에러")

데코레이터 팩토리

def repeat(times):
"""함수를 여러 번 실행"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
result = func(*args, **kwargs)
results.append(result)
return results
return wrapper
return decorator

@repeat(times=3)
def greet(name):
return f"Hello, {name}!"

print(greet("World"))
# ['Hello, World!', 'Hello, World!', 'Hello, World!']

# 조건부 데코레이터
def debug(enabled=True):
"""디버그 모드"""
def decorator(func):
if not enabled:
return func

@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
print(f"Args: {args}, Kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"Result: {result}")
return result
return wrapper
return decorator

@debug(enabled=True)
def add(a, b):
return a + b

add(3, 5)
# Calling add
# Args: (3, 5), Kwargs: {}
# Result: 8

@debug(enabled=False)
def multiply(a, b):
return a * b

multiply(3, 5) # 디버그 출력 없음

자주 묻는 질문

Q1. 제너레이터와 리스트, 언제 뭘 써야 하나요?

A: 상황에 따라 선택하세요

# 리스트 사용
# - 전체 데이터가 필요할 때
# - 여러 번 순회해야 할 때
# - 인덱싱이 필요할 때
# - 데이터가 작을 때

numbers = [1, 2, 3, 4, 5]
print(numbers[2]) # 인덱싱 가능
print(list(reversed(numbers))) # 역순
print(len(numbers)) # 길이

# 제너레이터 사용
# - 큰 데이터를 처리할 때
# - 한 번만 순회할 때
# - 메모리가 제한적일 때
# - 무한 시퀀스가 필요할 때

def big_data():
for i in range(10000000):
yield i

# 메모리 효율적으로 처리
total = sum(big_data())

Q2. 데코레이터는 언제 사용하나요?

A: 반복되는 패턴을 추상화할 때

# 사용 사례
# 1. 로깅
# 2. 성능 측정
# 3. 캐싱
# 4. 권한 검사
# 5. 입력 검증
# 6. 재시도 로직
# 7. 트랜잭션 관리

# 예: 권한 검사
def require_auth(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if not user.get('authenticated'):
raise PermissionError("로그인이 필요합니다")
return func(user, *args, **kwargs)
return wrapper

@require_auth
def delete_account(user):
return "계정이 삭제되었습니다"

Q3. functools.wraps는 왜 필요한가요?

A: 원본 함수의 메타데이터를 보존하기 위해

# functools.wraps 없이
def decorator1(func):
def wrapper(*args, **kwargs):
"""래퍼 함수"""
return func(*args, **kwargs)
return wrapper

@decorator1
def my_func():
"""내 함수"""
pass

print(my_func.__name__) # wrapper
print(my_func.__doc__) # 래퍼 함수

# functools.wraps 사용
def decorator2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""래퍼 함수"""
return func(*args, **kwargs)
return wrapper

@decorator2
def my_func2():
"""내 함수"""
pass

print(my_func2.__name__) # my_func2
print(my_func2.__doc__) # 내 함수

Q4. yield와 return의 차이는?

A: return은 값을 반환하고 종료, yield는 값을 생성하고 일시 중지

# return
def get_numbers():
return [1, 2, 3]

numbers = get_numbers()
print(numbers) # [1, 2, 3]
print(numbers) # [1, 2, 3] (같은 리스트)

# yield
def generate_numbers():
yield 1
yield 2
yield 3

gen = generate_numbers()
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3

# 재사용 불가
gen2 = generate_numbers()
print(list(gen2)) # [1, 2, 3]
print(list(gen2)) # [] (소진됨)

다음 단계

고급 함수 테크닉을 마스터했습니다!

핵심 정리:
✅ map, filter, reduce로 함수형 프로그래밍
✅ 데코레이터로 함수 기능 확장
✅ 제너레이터로 메모리 효율적 처리
✅ 이터레이터 프로토콜 이해
✅ 실전 예제 (캐싱, 파이프라인 등)

다음 단계: 클래스 기초에서 객체지향 프로그래밍을 시작해보세요!