모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

19강 / 전체 32강

반환값과 함수 설계 원칙 — 순수 함수와 부수 효과

10분 읽기 조회 6

return의 정확한 동작과 순수 함수·부수 효과 개념으로 견고한 함수를 설계하는 방법을 배운다.

🎯 이 강의에서 배우는 것

함수 설계의 핵심은 무엇을 반환하고 무엇을 하지 않느냐입니다. 순수 함수(pure function)와 부수 효과(side effect)의 개념은 버그가 적고 테스트하기 쉬운 코드를 작성하는 데 결정적인 기준이 됩니다. 이 강의에서 return의 정확한 동작과 함수 설계의 원칙을 배웁니다.

↩️ return 문의 정확한 동작

# return은 두 가지 일을 함
# 1. 값을 호출자에게 전달
# 2. 함수를 즉시 종료 (이후 코드 실행 안 됨)

def early_return(x):
    if x < 0:
        return "음수"    # 여기서 함수 종료
    if x == 0:
        return "영"     # 여기서 함수 종료
    return "양수"        # 위 조건 모두 해당 안 될 때

print(early_return(-5))   # 음수
print(early_return(0))    # 영
print(early_return(7))    # 양수

# return 없으면 None 반환
def no_return():
    x = 42
    # return 없음 → 암묵적으로 return None

result = no_return()
print(result)           # None
print(result is None)   # True

# 명시적 return None (의도를 명확히)
def find_first_negative(lst):
    for x in lst:
        if x < 0:
            return x   # 찾으면 반환
    return None        # 없으면 None 명시적 반환 (의도 명확)

print(find_first_negative([1, 2, -3, 4]))   # -3
print(find_first_negative([1, 2, 3]))       # None

📦 여러 값 반환 — 파이썬의 튜플 반환

# 파이썬의 return a, b 는 사실 return (a, b) — 튜플
def min_max(lst):
    return min(lst), max(lst)   # 튜플 반환

result = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print(type(result))   # <class 'tuple'>
print(result)         # (1, 9)

# 언패킹으로 받기 (파이썬다운 방법)
minimum, maximum = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print(f"최솟값: {minimum}, 최댓값: {maximum}")

# 여러 값 반환 vs 딕셔너리 반환 비교
def get_stats_tuple(data):
    return min(data), max(data), sum(data) / len(data)

def get_stats_dict(data):
    return {
        "min": min(data),
        "max": max(data),
        "avg": sum(data) / len(data),
    }

# 딕셔너리: 반환 필드가 많거나 이름이 중요할 때
stats = get_stats_dict([10, 20, 30, 40, 50])
print(stats["avg"])   # 30.0

# Python 3.7+ namedtuple / dataclass로 이름 있는 튜플 반환
from collections import namedtuple
Stats = namedtuple("Stats", ["min", "max", "avg"])

def get_stats_named(data):
    return Stats(min(data), max(data), sum(data) / len(data))

s = get_stats_named([10, 20, 30, 40, 50])
print(s.avg)     # 30.0
print(s[0])      # 10 (인덱스 접근도 가능)
min_val, max_val, avg = s   # 언패킹도 가능

✨ 순수 함수 — 예측 가능한 코드의 토대

# 순수 함수의 2가지 조건:
# 1. 동일한 입력 → 항상 동일한 출력 (결정적)
# 2. 외부 상태를 변경하지 않음 (no side effects)

# ✅ 순수 함수 예시
def add(a, b):
    return a + b   # 항상 a+b, 외부 변경 없음

def celsius_to_fahrenheit(c):
    return c * 9/5 + 32   # 항상 같은 결과

def filter_positive(nums):
    return [x for x in nums if x > 0]   # 새 리스트 반환, 원본 불변

# ❌ 순수하지 않은 함수 (부수 효과 있음)
total = 0

def add_to_total(x):
    global total   # 전역 변수 수정 → 부수 효과!
    total += x

import random
def random_greeting():
    greetings = ["안녕", "반가워", "잘 지내?"]
    return random.choice(greetings)   # 같은 입력인데 다른 출력 가능 → 비결정적

# 순수 함수의 장점
# 1. 테스트 쉬움: 입력/출력만 확인하면 됨
# 2. 추론 쉬움: 외부 상태 모르고도 이해 가능
# 3. 캐싱 가능: functools.lru_cache와 완벽 호환

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(50))   # 즉각 계산 (캐시 덕분)

⚡ 부수 효과 — 언제 필요하고 어떻게 관리할까

# 부수 효과의 종류:
# - 전역/외부 변수 변경
# - 파일/DB/네트워크 I/O
# - 화면 출력 (print)
# - 인자로 받은 가변 객체 수정

# 부수 효과는 피할 수 없지만, 분리하고 명확히 해야 함

# 나쁜 설계: 계산과 I/O를 섞음
def calculate_and_save_bad(data, filepath):
    total = sum(data)
    average = total / len(data)
    result = {"total": total, "average": average}
    # 계산 + 파일 저장을 한 함수에서 — 테스트 어려움!
    with open(filepath, "w") as f:
        import json
        json.dump(result, f)
    return result

# 좋은 설계: 계산(순수)과 I/O를 분리
def calculate_stats(data):   # 순수 함수 — 테스트 쉬움
    total = sum(data)
    return {"total": total, "average": total / len(data)}

def save_stats(stats, filepath):   # I/O 전담 — 부수 효과 명확
    with open(filepath, "w") as f:
        import json
        json.dump(stats, f)

# 사용
stats = calculate_stats([10, 20, 30])
# save_stats(stats, "output.json")

# 인자 변이 — 조심해야 할 부수 효과
def sort_inplace(lst):
    lst.sort()   # ← 원본 수정 (부수 효과!)

def sort_new(lst):
    return sorted(lst)   # ← 새 리스트 반환 (순수)

data = [3, 1, 2]
sort_inplace(data)
print(data)   # [1, 2, 3] ← 원본이 변경됨!

data2 = [3, 1, 2]
result = sort_new(data2)
print(data2)   # [3, 1, 2] ← 원본 보존
print(result)  # [1, 2, 3]

📐 함수 설계 체크리스트

# 좋은 함수의 기준:

# ✅ 이름이 동사로 시작 (무엇을 하는지 명확)
def calculate_total_price(items):   # ✅ calculate
    pass

def user_data():   # ❌ 동사 없음 → get_user_data()
    pass

# ✅ 단일 책임 원칙 (한 가지 일만)
def validate_email(email: str) -> bool:   # ✅ 검증만
    return "@" in email and "." in email.split("@")[-1]

def validate_and_send_email(email: str) -> bool:   # ❌ 두 가지 일
    pass

# ✅ 타입 힌트로 인터페이스 명확화
def parse_date(date_str: str) -> tuple[int, int, int]:
    year, month, day = date_str.split("-")
    return int(year), int(month), int(day)

# ✅ 일관적인 반환 타입 (섞이면 사용하기 어려움)
def get_user_bad(user_id: int):
    if user_id < 0:
        return None          # None 반환
    elif user_id == 0:
        return False         # False 반환 — 타입 불일치!
    return {"id": user_id}  # dict 반환

def get_user_good(user_id: int) -> dict | None:   # 반환 타입이 명확
    if user_id <= 0:
        return None
    return {"id": user_id}

# ✅ 매개변수 5개 이하 (많으면 dataclass/dict로 묶기)
from dataclasses import dataclass

@dataclass
class EmailConfig:
    to: str
    subject: str
    body: str
    cc: list = None
    bcc: list = None

def send_email(config: EmailConfig) -> bool:   # 매개변수 1개로 압축
    pass

⚠️ 자주 하는 실수

  • 반환 타입 불일치: 함수가 때는 int, 때는 None, 때는 False를 반환하면 호출부에서 타입 확인이 복잡해집니다. 일관된 타입을 반환하거나 Optional[type]으로 타입 힌트를 명시하세요.
  • 인자 변이를 부수 효과로 사용: 함수가 받은 리스트를 정렬하거나 수정하면, 호출부에서 원본이 변경됐는지 모릅니다. 함수 이름에 "inplace" 또는 "sort_" 등으로 명시하거나 새 객체를 반환하세요.
  • print()가 있는 함수를 "계산 함수"로 착각: print()는 부수 효과입니다. 로직 함수에 print를 넣으면 테스트 시 출력이 섞여 검증하기 어렵습니다.

📝 정리 및 다음 강의 예고

  • return은 값 반환과 함수 즉시 종료 두 가지 역할을 합니다.
  • return a, b는 튜플 반환 — 언패킹으로 받거나 namedtuple로 이름을 붙이세요.
  • 순수 함수: 같은 입력 → 같은 출력, 외부 변경 없음. 테스트와 추론이 쉽습니다.
  • 부수 효과는 피할 수 없지만 명확히 분리하고, 함수의 책임을 단순하게 유지하세요.

다음 강의: 스코프와 LEGB 규칙 — 변수를 찾는 순서(Local → Enclosing → Global → Built-in)와 global/nonlocal 키워드를 완전히 이해합니다.

관련 주제

  • return 다중값 반환(튜플)
  • 단일 책임 원칙(SRP)
  • 순수 함수(Pure Function)
  • 부수 효과(Side Effect)
  • None vs 예외 반환기준
  • 함수 설계 체크리스트
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 파이썬 기초 40강 — 처음 배우는 프로그래밍
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

파이썬 기초 40강 — 처음 배우는 프로그래밍

이 강의가 속한 시리즈는 총 32강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...