[2026] Python 함수 | 매개변수, 반환값, 람다, 데코레이터 완벽 정리
이 글의 핵심
Python 함수: 매개변수, 반환값, 람다, 데코레이터 함수 기본·매개변수 (Parameters).
들어가며
”함수는 코드의 기본 단위”
함수는 재사용 가능한 코드 블록입니다. Python에서 함수는 일급 객체(First-class Object)로, 변수에 할당하고 인자로 전달할 수 있습니다.
실무 활용 사례: 데이터 분석, 웹 개발, 자동화 프로젝트에서 실제로 사용한 패턴과 코드를 바탕으로 정리했습니다. 초보자가 흔히 겪는 오류와 해결법을 포함합니다.
실무에서 느낀 Python의 매력
처음 Python을 배울 때는 “이게 정말 프로그래밍 언어인가?” 싶을 정도로 간결했습니다. C++에서 10줄로 작성하던 코드가 Python에서는 2~3줄로 끝나는 경우가 많았죠. 특히 데이터 분석 프로젝트를 진행하면서 Pandas와 NumPy의 강력함을 체감했습니다. 엑셀로 몇 시간 걸리던 작업이 Python 스크립트로는 몇 초 만에 끝나는 걸 보고 동료들이 놀라워했던 기억이 납니다. 하지만 처음부터 순탄하지만은 않았습니다. 들여쓰기 하나 잘못해서 몇 시간을 헤맨 적도 있고, 가상환경 설정이 꼬여서 프로젝트 전체를 다시 시작한 적도 있습니다. 이런 시행착오를 겪으며 깨달은 건, 환경 설정을 처음부터 제대로 하는 것이 얼마나 중요한지였습니다. 이 글에서는 제가 겪은 실수들을 바탕으로, 여러분이 같은 시행착오를 겪지 않도록 실전 팁을 담았습니다.
1. 함수 기본
함수란?
함수(Function)는 특정 작업을 수행하는 재사용 가능한 코드 블록입니다. 함수를 사용하면 코드 중복을 줄이고, 가독성을 높이며, 유지보수가 쉬워집니다. 함수의 장점:
- 재사용성: 같은 코드를 여러 곳에서 호출 가능
- 모듈화: 복잡한 프로그램을 작은 단위로 분리
- 테스트 용이: 함수 단위로 테스트 가능
- 가독성: 코드의 의도를 명확하게 표현
함수 정의와 호출
Python에서 함수는 def 키워드로 정의합니다. 재료(인자)를 넣으면 정해진 순서로 요리(본문)를 하고, 필요하면 결과를 return으로 돌려주는 레시피 카드처럼 쓰면 팀원이 읽기에도 좋습니다.
# 기본 함수 정의
def greet(name):
"""
사용자에게 인사하는 함수
Args:
name (str): 인사할 사람의 이름
Returns:
str: 인사 메시지
"""
return f"안녕하세요, {name}님!"
# 함수 호출
message = greet("철수")
print(message) # 출력: 안녕하세요, 철수님!
# 직접 출력
print(greet("영희")) # 출력: 안녕하세요, 영희님!
함수 구조 설명:
- def: 함수 정의 키워드
- greet: 함수 이름 (소문자와 언더스코어 사용 권장)
- (name): 매개변수 (파라미터)
- """…""": 독스트링 (함수 설명, 선택사항)
- return: 반환값 (없으면 None 반환)
여러 값 반환하기
Python 함수는 튜플을 이용해 여러 값을 동시에 반환할 수 있습니다. 이는 다른 언어에서는 구조체나 객체를 반환해야 하는 상황을 간단하게 처리합니다.
# 여러 반환값 (튜플 언패킹)
def get_user_info():
"""사용자 정보를 반환합니다."""
name = "철수"
age = 25
city = "서울"
return name, age, city # 튜플로 반환: ("철수", 25, "서울")
# 반환값을 각각의 변수에 할당
name, age, city = get_user_info()
print(f"이름: {name}, 나이: {age}, 도시: {city}")
# 출력: 이름: 철수, 나이: 25, 도시: 서울
# 튜플로 받기
user_info = get_user_info()
print(user_info) # 출력: ('철수', 25, '서울')
print(user_info[0]) # 출력: 철수
# 일부만 받기 (나머지는 _로 무시)
name, _, city = get_user_info()
print(f"{name}님은 {city}에 삽니다.")
실전 활용 예제: 다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def calculate_statistics(numbers):
"""숫자 리스트의 통계를 계산합니다."""
if not numbers:
return 0, 0, 0, 0
total = sum(numbers)
count = len(numbers)
average = total / count
maximum = max(numbers)
minimum = min(numbers)
return total, average, maximum, minimum
# 사용
scores = [85, 90, 78, 92, 88]
total, avg, max_score, min_score = calculate_statistics(scores)
print(f"총점: {total}")
print(f"평균: {avg:.2f}")
print(f"최고점: {max_score}")
print(f"최저점: {min_score}")
반환값이 없는 함수
return 문이 없거나 값 없이 return만 쓰면 None을 반환합니다.
# 반환값 없음 (None 반환)
def print_hello():
"""화면에 인사만 출력하고 값을 반환하지 않습니다."""
print("Hello")
# return 문 없음 → 암묵적으로 None 반환
result = print_hello()
# 출력: Hello
print(result) # 출력: None
print(type(result)) # 출력: <class 'NoneType'>
# 조건부 반환
def check_positive(num):
"""양수인지 확인하고, 음수면 조기 반환합니다."""
if num < 0:
print("음수입니다.")
return # 값 없이 반환 → None
print(f"{num}은 양수입니다.")
return True
result1 = check_positive(10) # 출력: 10은 양수입니다.
print(result1) # 출력: True
result2 = check_positive(-5) # 출력: 음수입니다.
print(result2) # 출력: None
None 활용 패턴: 아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def find_user(user_id):
"""사용자를 찾아 반환합니다. 없으면 None."""
users = {1: "철수", 2: "영희", 3: "민수"}
return users.get(user_id) # 없으면 None 반환
# None 체크
user = find_user(5)
if user is None:
print("사용자를 찾을 수 없습니다.")
else:
print(f"사용자: {user}")
2. 매개변수 (Parameters)
위치 인자 (Positional Arguments)
위치 인자는 함수 호출 시 순서대로 매개변수에 전달됩니다. 가장 기본적인 인자 전달 방식입니다.
def add(a, b):
"""두 숫자를 더합니다."""
return a + b
# 위치 인자로 호출
result = add(3, 5) # a=3, b=5
print(result) # 출력: 8
# 순서가 중요!
def subtract(a, b):
"""a에서 b를 뺍니다."""
return a - b
print(subtract(10, 3)) # 7 (10 - 3)
print(subtract(3, 10)) # -7 (3 - 10) ← 순서가 바뀌면 결과도 바뀜
실전 예제:
def calculate_bmi(weight, height):
"""
BMI(체질량지수)를 계산합니다.
Args:
weight (float): 체중 (kg)
height (float): 키 (m)
Returns:
float: BMI 값
"""
bmi = weight / (height ** 2)
return round(bmi, 2)
# 사용
my_bmi = calculate_bmi(70, 1.75) # 70kg, 1.75m
print(f"BMI: {my_bmi}") # 출력: BMI: 22.86
키워드 인자 (Keyword Arguments)
키워드 인자는 매개변수 이름을 명시하여 전달합니다. 순서에 상관없이 호출할 수 있어 가독성이 높습니다.
def introduce(name, age, city):
"""자기소개를 출력합니다."""
print(f"{name}({age}세) - {city}")
# 위치 인자로 호출 (순서 중요)
introduce("철수", 25, "서울")
# 출력: 철수(25세) - 서울
# 키워드 인자로 호출 (순서 무관)
introduce(age=25, name="철수", city="서울")
# 출력: 철수(25세) - 서울
introduce(city="부산", age=30, name="영희")
# 출력: 영희(30세) - 부산
# 위치 + 키워드 혼합 (위치 인자가 먼저)
introduce("민수", age=28, city="대전")
# 출력: 민수(28세) - 대전
키워드 인자의 장점: 아래 코드는 python를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ❌ 위치 인자만 사용 - 의미 불명확
send_email("user@example.com", "제목", "내용", True, False, "high")
# ✅ 키워드 인자 사용 - 의미 명확
send_email(
to="user@example.com",
subject="제목",
body="내용",
html=True,
cc=False,
priority="high"
)
기본값 (Default Arguments)
기본값을 설정하면 인자를 생략할 수 있습니다. 선택적 매개변수를 구현할 때 유용합니다.
def greet(name, greeting="안녕하세요"):
"""
인사 메시지를 생성합니다.
Args:
name (str): 이름
greeting (str): 인사말 (기본값: "안녕하세요")
"""
return f"{greeting}, {name}님!"
# 기본값 사용
print(greet("철수")) # 출력: 안녕하세요, 철수님!
# 기본값 오버라이드
print(greet("영희", "반갑습니다")) # 출력: 반갑습니다, 영희님!
print(greet("민수", greeting="좋은 아침입니다")) # 출력: 좋은 아침입니다, 민수님!
실전 예제 - API 요청 함수: 다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def fetch_data(url, timeout=30, retry=3, verify_ssl=True):
"""
API에서 데이터를 가져옵니다.
Args:
url (str): API 엔드포인트 URL
timeout (int): 타임아웃 시간 (초, 기본값: 30)
retry (int): 재시도 횟수 (기본값: 3)
verify_ssl (bool): SSL 인증서 검증 여부 (기본값: True)
"""
print(f"URL: {url}")
print(f"Timeout: {timeout}초")
print(f"Retry: {retry}회")
print(f"SSL 검증: {verify_ssl}")
# 기본값으로 호출
fetch_data("https://api.example.com/users")
# 일부 기본값 오버라이드
fetch_data("https://api.example.com/posts", timeout=60)
# 모든 인자 명시
fetch_data(
url="https://api.example.com/data",
timeout=10,
retry=5,
verify_ssl=False
)
기본값 사용 시 주의사항: 다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ❌ 위험: 가변 객체를 기본값으로 사용
def add_item(item, items=[]): # 리스트는 함수 정의 시 한 번만 생성됨
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] ← 이전 호출의 리스트가 유지됨!
print(add_item(3)) # [1, 2, 3]
# ✅ 올바른 방법: None을 기본값으로
def add_item_safe(item, items=None):
if items is None:
items = [] # 매 호출마다 새 리스트 생성
items.append(item)
return items
print(add_item_safe(1)) # [1]
print(add_item_safe(2)) # [2] ← 독립적인 리스트
print(add_item_safe(3)) # [3]
기본값 규칙:
- 기본값이 있는 매개변수는 기본값이 없는 매개변수 뒤에 와야 합니다.
- 가변 객체(리스트, 딕셔너리)를 기본값으로 사용하지 마세요.
- 기본값은 함수 정의 시점에 한 번만 평가됩니다. 아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# ❌ 문법 오류
def wrong_order(a=10, b): # SyntaxError: non-default argument follows default argument
pass
# ✅ 올바른 순서
def correct_order(b, a=10):
pass
가변 인자 (*args, kwargs)
Python 함수는 가변 개수의 인자를 받을 수 있습니다. 이는 유연한 API 설계에 매우 유용합니다.
*args: 가변 위치 인자
*args는 임의 개수의 위치 인자를 튜플로 받습니다. 함수 내부에서는 일반 튜플처럼 사용합니다.
# *args 기본 사용
def sum_all(*numbers):
"""
모든 숫자의 합을 계산합니다.
Args:
*numbers: 임의 개수의 숫자
Returns:
int/float: 합계
"""
print(f"받은 인자: {numbers}") # 튜플로 출력
print(f"인자 개수: {len(numbers)}")
return sum(numbers)
print(sum_all(1, 2, 3)) # 출력: 6
print(sum_all(1, 2, 3, 4, 5)) # 출력: 15
print(sum_all(10)) # 출력: 10
print(sum_all()) # 출력: 0 (빈 튜플)
실전 예제 - 로깅 함수:
def log(level, *messages):
"""
여러 메시지를 로그로 출력합니다.
Args:
level (str): 로그 레벨 (INFO, WARNING, ERROR)
*messages: 출력할 메시지들
"""
timestamp = "2026-03-29 10:30:00"
combined_message = " ".join(str(msg) for msg in messages)
print(f"[{timestamp}] [{level}] {combined_message}")
# 사용
log("INFO", "서버 시작됨")
log("WARNING", "연결 지연:", 5, "초")
log("ERROR", "파일을 찾을 수 없음:", "config.json")
# 출력:
# [2026-03-29 10:30:00] [INFO] 서버 시작됨
# [2026-03-29 10:30:00] [WARNING] 연결 지연: 5 초
# [2026-03-29 10:30:00] [ERROR] 파일을 찾을 수 없음: config.json
kwargs: 가변 키워드 인자
**kwargs는 임의 개수의 키워드 인자를 딕셔너리로 받습니다.
# **kwargs 기본 사용
def print_info(**kwargs):
"""
모든 키워드 인자를 출력합니다.
Args:
**kwargs: 임의 개수의 키워드 인자
"""
print(f"받은 인자: {kwargs}") # 딕셔너리로 출력
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="철수", age=25, city="서울")
# 출력:
# 받은 인자: {'name': '철수', 'age': 25, 'city': '서울'}
# name: 철수
# age: 25
# city: 서울
print_info(product="노트북", price=1500000, brand="Apple")
# 출력:
# 받은 인자: {'product': '노트북', 'price': 1500000, 'brand': 'Apple'}
# product: 노트북
# price: 1500000
# brand: Apple
실전 예제 - 데이터베이스 쿼리 빌더: 다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def build_query(table, **conditions):
"""
SQL WHERE 조건을 생성합니다.
Args:
table (str): 테이블 이름
**conditions: 검색 조건 (컬럼=값)
Returns:
str: SQL 쿼리
"""
query = f"SELECT * FROM {table}"
if conditions:
where_clauses = [f"{key}='{value}'" for key, value in conditions.items()]
query += " WHERE " + " AND ".join(where_clauses)
return query
# 사용
print(build_query("users"))
# SELECT * FROM users
print(build_query("users", name="철수"))
# SELECT * FROM users WHERE name='철수'
print(build_query("users", age=25, city="서울"))
# SELECT * FROM users WHERE age='25' AND city='서울'
print(build_query("products", category="전자제품", price=1000000, stock=10))
# SELECT * FROM products WHERE category='전자제품' AND price='1000000' AND stock='10'
혼합 사용: 위치 + *args + kwargs
모든 인자 타입을 함께 사용할 수 있습니다. 순서가 중요합니다.
def complex_func(a, b, *args, **kwargs):
"""
모든 타입의 인자를 받는 함수
Args:
a: 필수 위치 인자 1
b: 필수 위치 인자 2
*args: 추가 위치 인자들
**kwargs: 키워드 인자들
"""
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"kwargs={kwargs}")
# 호출 예제
complex_func(1, 2, 3, 4, x=5, y=6)
# 출력:
# a=1, b=2
# args=(3, 4)
# kwargs={'x': 5, 'y': 6}
complex_func(10, 20)
# 출력:
# a=10, b=20
# args=()
# kwargs={}
complex_func(1, 2, 3, 4, 5, name="철수", age=25)
# 출력:
# a=1, b=2
# args=(3, 4, 5)
# kwargs={'name': '철수', 'age': 25}
매개변수 순서 규칙: 아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ✅ 올바른 순서
def func(pos1, pos2, *args, default=10, **kwargs):
pass
# 1. 일반 위치 인자
# 2. *args (가변 위치 인자)
# 3. 기본값이 있는 인자
# 4. **kwargs (가변 키워드 인자)
# ❌ 잘못된 순서
def wrong_func(**kwargs, *args): # SyntaxError
pass
실전 활용 - 래퍼 함수: 다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def retry_on_error(func, *args, max_retries=3, **kwargs):
"""
함수 실행 실패 시 재시도하는 래퍼
Args:
func: 실행할 함수
*args: func에 전달할 위치 인자
max_retries: 최대 재시도 횟수
**kwargs: func에 전달할 키워드 인자
"""
for attempt in range(max_retries):
try:
result = func(*args, **kwargs)
print(f"성공 (시도 {attempt + 1})")
return result
except Exception as e:
print(f"실패 (시도 {attempt + 1}): {e}")
if attempt == max_retries - 1:
raise
# 사용 예제
def unstable_api_call(endpoint, timeout=10):
"""불안정한 API 호출 시뮬레이션"""
import random
if random.random() < 0.7: # 70% 확률로 실패
raise ConnectionError("네트워크 오류")
return f"데이터: {endpoint}"
# retry_on_error로 감싸서 재시도
result = retry_on_error(
unstable_api_call,
"/users",
timeout=30,
max_retries=5
)
3. 람다 함수 (Lambda Functions)
람다 함수란?
람다 함수는 이름 없는 익명 함수로, 간단한 연산을 한 줄로 표현할 때 사용합니다. JavaScript의 화살표 함수(=>)와 유사합니다.
문법: lambda 매개변수: 표현식
일반 함수 vs 람다 함수
# 일반 함수 정의
def square(x):
"""숫자를 제곱합니다."""
return x ** 2
# 람다 함수 (동일한 기능)
square_lambda = lambda x: x ** 2
# 사용
print(square(5)) # 출력: 25
print(square_lambda(5)) # 출력: 25
# 람다는 변수에 할당하지 않고 바로 사용 가능
print((lambda x: x ** 2)(5)) # 출력: 25
람다 함수의 특징:
- 한 줄 표현식만 가능: 여러 줄 코드는 불가능
- return 키워드 없음: 표현식의 결과가 자동으로 반환
- 익명 함수: 이름이 없어 일회성 사용에 적합
- 간결함: 간단한 연산을 짧게 표현 언제 람다를 사용할까?:
- ✅
sorted(),map(),filter()등의 key 함수로 사용 - ✅ 간단한 콜백 함수
- ❌ 복잡한 로직 (일반 함수 사용)
- ❌ 재사용이 많은 함수 (일반 함수로 정의)
람다 활용 예제
sorted()와 함께 사용
sorted()는 key 매개변수로 정렬 기준을 지정할 수 있습니다. 람다 함수로 간단하게 표현합니다.
# 예제 1: 튜플 리스트 정렬
students = [("철수", 85), ("영희", 90), ("민수", 80)]
# 점수 기준 내림차순 정렬
sorted_students = sorted(students, key=lambda x: x[1], reverse=True)
print(sorted_students)
# 출력: [('영희', 90), ('철수', 85), ('민수', 80)]
# lambda x: x[1]의 의미:
# - x: 각 튜플 (예: ("철수", 85))
# - x[1]: 튜플의 두 번째 요소 (점수)
# - sorted는 이 값을 기준으로 정렬
# 예제 2: 딕셔너리 리스트 정렬
products = [
{"name": "노트북", "price": 1500000},
{"name": "마우스", "price": 30000},
{"name": "키보드", "price": 80000}
]
# 가격 기준 오름차순 정렬
sorted_by_price = sorted(products, key=lambda x: x[price])
print(sorted_by_price)
# 출력: [{'name': '마우스', 'price': 30000}, ...]
# 이름 길이 기준 정렬
sorted_by_name_length = sorted(products, key=lambda x: len(x[name]))
print(sorted_by_name_length)
map()과 함께 사용
map()은 리스트의 각 요소에 함수를 적용합니다.
# 예제: 숫자 리스트를 제곱
numbers = [1, 2, 3, 4, 5]
# 람다로 제곱 연산
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # 출력: [1, 4, 9, 16, 25]
# 여러 리스트 동시 처리
list1 = [1, 2, 3]
list2 = [10, 20, 30]
result = list(map(lambda x, y: x + y, list1, list2))
print(result) # 출력: [11, 22, 33]
# 실전 예제: 문자열 리스트 처리
names = [" 철수 ", "영희", " 민수"]
cleaned = list(map(lambda x: x.strip().upper(), names))
print(cleaned) # 출력: ['철수', '영희', '민수']
map() 설명: map(함수, 리스트)는 리스트의 각 요소에 함수를 적용하고, 결과를 반환합니다. list()로 감싸야 실제 리스트로 변환됩니다.
filter()와 함께 사용
filter()는 조건을 만족하는 요소만 걸러냅니다.
# 예제: 짝수만 필터링
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # 출력: [2, 4, 6, 8, 10]
# lambda x: x % 2 == 0의 의미:
# - x % 2 == 0: 짝수 판별 (True/False 반환)
# - filter는 True인 요소만 통과
# 실전 예제: 조건 필터링
products = [
{"name": "노트북", "price": 1500000, "stock": 5},
{"name": "마우스", "price": 30000, "stock": 0},
{"name": "키보드", "price": 80000, "stock": 10}
]
# 재고가 있는 상품만 필터링
in_stock = list(filter(lambda x: x[stock] > 0, products))
print(in_stock)
# 출력: [{'name': '노트북', ...}, {'name': '키보드', ...}]
# 가격이 50만원 이상인 상품
expensive = list(filter(lambda x: x[price] >= 500000, products))
print(expensive)
filter() 설명: filter(조건함수, 리스트)는 조건함수가 True를 반환하는 요소만 남깁니다.
람다 vs 리스트 컴프리헨션
많은 경우 리스트 컴프리헨션이 람다보다 읽기 쉽습니다. 다음은 python를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
numbers = [1, 2, 3, 4, 5]
# map + lambda
squared_map = list(map(lambda x: x ** 2, numbers))
# 리스트 컴프리헨션 (더 읽기 쉬움)
squared_comp = [x ** 2 for x in numbers]
print(squared_map) # [1, 4, 9, 16, 25]
print(squared_comp) # [1, 4, 9, 16, 25]
# filter + lambda
even_filter = list(filter(lambda x: x % 2 == 0, numbers))
# 리스트 컴프리헨션 (더 읽기 쉬움)
even_comp = [x for x in numbers if x % 2 == 0]
print(even_filter) # [2, 4]
print(even_comp) # [2, 4]
선택 가이드:
- 람다 사용:
sorted(),max(),min()의 key 함수 - 리스트 컴프리헨션 사용:
map(),filter()대체
4. 클로저 (Closure)
클로저란?
클로저(Closure)는 외부 함수의 변수를 기억하는 내부 함수입니다. JavaScript의 클로저, C++의 람다 캡처와 유사한 개념입니다. 클로저의 핵심:
- 내부 함수가 외부 함수의 지역 변수를 기억합니다.
- 외부 함수가 종료되어도 내부 함수는 그 변수에 접근 가능합니다.
- 상태를 유지하는 함수를 만들 수 있습니다.
기본 클로저 예제
def make_multiplier(n):
"""
n배를 계산하는 함수를 생성합니다.
Args:
n (int): 곱할 숫자
Returns:
function: x를 n배 하는 함수
"""
def multiply(x):
return x * n # n을 기억! (클로저)
return multiply
# 3배 함수 생성
times_3 = make_multiplier(3)
# 이 시점에서 make_multiplier는 종료되었지만,
# times_3은 여전히 n=3을 기억합니다.
# 5배 함수 생성
times_5 = make_multiplier(5)
# 사용
print(times_3(10)) # 출력: 30 (10 * 3)
print(times_3(7)) # 출력: 21 (7 * 3)
print(times_5(10)) # 출력: 50 (10 * 5)
print(times_5(4)) # 출력: 20 (4 * 5)
클로저 동작 원리:
def outer(x):
"""외부 함수"""
print(f"외부 함수: x={x}")
def inner(y):
"""내부 함수 (클로저)"""
print(f"내부 함수: x={x}, y={y}")
return x + y
return inner
# outer 호출 → inner 함수 반환
func = outer(10)
# 출력: 외부 함수: x=10
# outer는 종료되었지만, func은 x=10을 기억
result = func(5)
# 출력: 내부 함수: x=10, y=5
# 반환: 15
nonlocal 키워드
nonlocal은 내부 함수에서 외부 함수의 변수를 수정할 때 사용합니다.
def make_counter():
"""카운터 함수를 생성합니다."""
count = 0 # 외부 함수의 지역 변수
def increment():
nonlocal count # count를 외부 함수의 변수로 지정
count += 1
return count
return increment
# 독립적인 카운터 생성
counter1 = make_counter()
counter2 = make_counter()
# counter1 사용
print(counter1()) # 출력: 1
print(counter1()) # 출력: 2
print(counter1()) # 출력: 3
# counter2는 독립적 (별도의 count 변수)
print(counter2()) # 출력: 1
print(counter2()) # 출력: 2
# counter1은 여전히 자신의 상태 유지
print(counter1()) # 출력: 4
nonlocal 없이 시도하면?: 아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def make_counter_wrong():
count = 0
def increment():
count += 1 # ❌ UnboundLocalError: local variable 'count' referenced before assignment
return count
return increment
# Python은 count += 1을 보고 count를 지역 변수로 간주하지만,
# 할당 전에 읽으려고 하므로 에러 발생
실전 클로저 예제
예제 1: 설정 값을 기억하는 함수 생성기
def create_logger(prefix):
"""
특정 접두사를 가진 로거 함수를 생성합니다.
Args:
prefix (str): 로그 메시지 앞에 붙을 접두사
Returns:
function: 로그 출력 함수
"""
def log(message):
print(f"[{prefix}] {message}")
return log
# 각 모듈별 로거 생성
db_logger = create_logger("DATABASE")
api_logger = create_logger("API")
auth_logger = create_logger("AUTH")
# 사용
db_logger("연결 성공") # 출력: [DATABASE] 연결 성공
api_logger("요청 수신") # 출력: [API] 요청 수신
auth_logger("로그인 성공") # 출력: [AUTH] 로그인 성공
예제 2: 누적 계산기
def make_accumulator(initial=0):
"""
값을 누적하는 함수를 생성합니다.
Args:
initial (int): 초기값
Returns:
function: 값을 누적하는 함수
"""
total = initial
def add(value):
nonlocal total
total += value
return total
return add
# 독립적인 누적기 생성
account1 = make_accumulator(1000) # 초기 잔액 1000원
account2 = make_accumulator(5000) # 초기 잔액 5000원
# account1 사용
print(account1(500)) # 출력: 1500 (1000 + 500)
print(account1(200)) # 출력: 1700 (1500 + 200)
print(account1(-300)) # 출력: 1400 (1700 - 300)
# account2는 독립적
print(account2(1000)) # 출력: 6000 (5000 + 1000)
print(account2(500)) # 출력: 6500 (6000 + 500)
예제 3: 함수 팩토리 (다양한 연산)
def make_operation(operation):
"""
연산 타입에 따라 다른 함수를 반환합니다.
Args:
operation (str): 'add', 'multiply', 'power'
Returns:
function: 해당 연산을 수행하는 함수
"""
if operation == "add":
return lambda x, y: x + y
elif operation == "multiply":
return lambda x, y: x * y
elif operation == "power":
return lambda x, y: x ** y
else:
return lambda x, y: None
# 연산 함수 생성
add_func = make_operation("add")
multiply_func = make_operation("multiply")
power_func = make_operation("power")
# 사용
print(add_func(5, 3)) # 출력: 8
print(multiply_func(5, 3)) # 출력: 15
print(power_func(5, 3)) # 출력: 125 (5^3)
클로저 vs 클래스
클로저는 간단한 상태 유지에 적합하고, 복잡한 상태는 클래스가 더 적합합니다. 다음은 python를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 클로저 방식
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
# 클래스 방식 (더 명확)
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
# 사용
closure_counter = make_counter()
class_counter = Counter()
print(closure_counter()) # 1
print(class_counter.increment()) # 1
선택 가이드:
- 클로저: 간단한 상태 유지, 함수형 프로그래밍 스타일
- 클래스: 여러 메서드와 속성, 복잡한 상태 관리
5. 데코레이터 기본 (Decorators)
데코레이터란?
데코레이터(Decorator)는 함수를 수정하지 않고 기능을 추가하는 패턴입니다. 함수를 인자로 받아 새로운 함수를 반환합니다. 데코레이터의 용도:
- 로깅 (함수 호출 기록)
- 실행 시간 측정
- 권한 검사
- 캐싱 (결과 저장)
- 입력 검증 데코레이터 없이 구현:
def say_hello():
print("Hello!")
def add_logging(func):
"""함수 실행 전후에 로그를 추가합니다."""
def wrapper():
print("함수 실행 전")
func()
print("함수 실행 후")
return wrapper
# 수동으로 감싸기
say_hello = add_logging(say_hello)
say_hello()
# 출력:
# 함수 실행 전
# Hello!
# 함수 실행 후
데코레이터 문법으로 간단하게:
def my_decorator(func):
"""함수를 꾸며주는 데코레이터"""
def wrapper():
print("함수 실행 전")
func()
print("함수 실행 후")
return wrapper
# @ 문법으로 데코레이터 적용
@my_decorator
def say_hello():
print("Hello!")
# 호출
say_hello()
# 출력:
# 함수 실행 전
# Hello!
# 함수 실행 후
@my_decorator는 say_hello = my_decorator(say_hello)와 동일합니다. 더 읽기 쉽고 간결합니다.
인자가 있는 함수에 데코레이터 적용
*args, **kwargs를 사용하면 모든 인자를 받을 수 있습니다.
def my_decorator(func):
"""모든 함수에 적용 가능한 범용 데코레이터"""
def wrapper(*args, **kwargs):
print(f"함수 호출: {func.__name__}")
print(f"인자: args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"결과: {result}")
return result
return wrapper
@my_decorator
def add(a, b):
"""두 숫자를 더합니다."""
return a + b
@my_decorator
def greet(name, greeting="안녕하세요"):
"""인사 메시지를 생성합니다."""
return f"{greeting}, {name}님!"
# 사용
result1 = add(3, 5)
# 출력:
# 함수 호출: add
# 인자: args=(3, 5), kwargs={}
# 결과: 8
result2 = greet("철수", greeting="반갑습니다")
# 출력:
# 함수 호출: greet
# 인자: args=('철수',), kwargs={'greeting': '반갑습니다'}
# 결과: 반갑습니다, 철수님!
실전 데코레이터 예제
예제 1: 실행 시간 측정
import time
def measure_time(func):
"""함수 실행 시간을 측정합니다."""
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
elapsed = end_time - start_time
print(f"{func.__name__} 실행 시간: {elapsed:.4f}초")
return result
return wrapper
@measure_time
def slow_function():
"""시간이 오래 걸리는 함수 시뮬레이션"""
time.sleep(2)
return "완료"
result = slow_function()
# 출력: slow_function 실행 시간: 2.0023초
예제 2: 권한 검사
def require_auth(func):
"""로그인 여부를 확인하는 데코레이터"""
def wrapper(user, *args, **kwargs):
if not user.get("is_logged_in"):
print("오류: 로그인이 필요합니다.")
return None
return func(user, *args, **kwargs)
return wrapper
@require_auth
def view_profile(user):
"""사용자 프로필을 조회합니다."""
return f"{user['name']}의 프로필"
@require_auth
def delete_account(user):
"""계정을 삭제합니다."""
return f"{user['name']}의 계정이 삭제되었습니다."
# 테스트
logged_in_user = {"name": "철수", "is_logged_in": True}
guest_user = {"name": "손님", "is_logged_in": False}
print(view_profile(logged_in_user)) # 출력: 철수의 프로필
print(view_profile(guest_user)) # 출력: 오류: 로그인이 필요합니다.
예제 3: 결과 캐싱 (메모이제이션)
def cache(func):
"""함수 결과를 캐싱하여 중복 계산을 방지합니다."""
cached_results = {}
def wrapper(*args):
if args in cached_results:
print(f"캐시에서 반환: {args}")
return cached_results[args]
print(f"계산 중: {args}")
result = func(*args)
cached_results[args] = result
return result
return wrapper
@cache
def fibonacci(n):
"""피보나치 수를 계산합니다 (재귀)."""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 사용
print(fibonacci(5))
# 출력:
# 계산 중: (5,)
# 계산 중: (4,)
# 계산 중: (3,)
# 계산 중: (2,)
# 계산 중: (1,)
# 계산 중: (0,)
# 캐시에서 반환: (1,)
# 캐시에서 반환: (2,)
# 캐시에서 반환: (3,)
# 5
print(fibonacci(5)) # 두 번째 호출
# 출력: 캐시에서 반환: (5,)
# 5
Python 내장 캐싱: functools.lru_cache 사용 권장
아래 코드는 python를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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(100)) # 매우 빠름
예제 4: 재시도 로직
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def retry(max_attempts=3):
"""실패 시 재시도하는 데코레이터"""
def decorator(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 wrapper
return decorator
@retry(max_attempts=3)
def unstable_api_call():
"""불안정한 API 호출 시뮬레이션"""
import random
if random.random() < 0.7:
raise ConnectionError("네트워크 오류")
return "성공"
# 사용 (최대 3번 재시도)
result = unstable_api_call()
고급 주제
재귀 함수 (Recursive Functions)
재귀 함수는 자기 자신을 호출하는 함수입니다.
def factorial(n):
"""
팩토리얼을 재귀로 계산합니다.
n! = n × (n-1) × (n-2) × ....× 1
"""
# 기저 조건 (재귀 종료)
if n <= 1:
return 1
# 재귀 호출
return n * factorial(n - 1)
print(factorial(5)) # 120 (5 × 4 × 3 × 2 × 1)
# 실행 과정:
# factorial(5) = 5 × factorial(4)
# factorial(4) = 4 × factorial(3)
# factorial(3) = 3 × factorial(2)
# factorial(2) = 2 × factorial(1)
# factorial(1) = 1
# 결과: 5 × 4 × 3 × 2 × 1 = 120
재귀 vs 반복: 다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 재귀 방식
def sum_recursive(n):
if n <= 0:
return 0
return n + sum_recursive(n - 1)
# 반복 방식 (더 효율적)
def sum_iterative(n):
total = 0
for i in range(1, n + 1):
total += i
return total
print(sum_recursive(100)) # 5050
print(sum_iterative(100)) # 5050
일급 객체로서의 함수
Python에서 함수는 일급 객체(First-class Object)입니다. 변수에 할당하고, 인자로 전달하고, 반환할 수 있습니다.
# 함수를 변수에 할당
def greet(name):
return f"안녕하세요, {name}님!"
say_hello = greet # 함수를 변수에 할당
print(say_hello("철수")) # 출력: 안녕하세요, 철수님!
# 함수를 인자로 전달
def apply_operation(func, value):
"""함수를 인자로 받아 실행합니다."""
return func(value)
def square(x):
return x ** 2
def cube(x):
return x ** 3
print(apply_operation(square, 5)) # 25
print(apply_operation(cube, 5)) # 125
# 함수를 리스트에 저장
operations = [square, cube, lambda x: x * 2]
for op in operations:
print(op(3))
# 출력: 9, 27, 6
고차 함수 (Higher-order Functions)
고차 함수는 함수를 인자로 받거나 함수를 반환하는 함수입니다. 다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def create_multiplier(factor):
"""특정 배수를 계산하는 함수를 반환합니다."""
return lambda x: x * factor
def create_adder(n):
"""특정 값을 더하는 함수를 반환합니다."""
return lambda x: x + n
# 함수 생성
double = create_multiplier(2)
triple = create_multiplier(3)
add_10 = create_adder(10)
# 사용
print(double(5)) # 10
print(triple(5)) # 15
print(add_10(5)) # 15
# 함수 조합
def compose(f, g):
"""두 함수를 조합합니다: f(g(x))"""
return lambda x: f(g(x))
# double(add_10(x)) 함수 생성
double_then_add = compose(double, add_10)
print(double_then_add(5)) # 30 (double(add_10(5)) = double(15) = 30)
함수를 설계할 때 자주 쓰는 패턴
함수는 같은 재료(인자)를 넣으면 같은 규칙으로 결과를 내는 레시피와 비슷합니다. 이름·독스트링·한 가지 일에 집중하는 습관을 두면, 나중에 테스트와 수정이 훨씬 수월합니다. 다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ✅ 좋은 함수 - 명확한 이름, 독스트링, 단일 책임
def calculate_total_price(items, tax_rate=0.1, discount=0):
"""
총 가격을 계산합니다 (세금 포함, 할인 적용).
Args:
items (list): 상품 리스트, 각 항목은 {'name': str, 'price': float}
tax_rate (float): 세율 (기본값: 0.1 = 10%)
discount (float): 할인율 (기본값: 0 = 할인 없음)
Returns:
float: 최종 가격 (세금 포함, 할인 적용)
Examples:
>>> items = [{'name': '노트북', 'price': 1000000}]
>>> calculate_total_price(items, tax_rate=0.1)
1100000.0
"""
if not items:
return 0.0
# 소계 계산
subtotal = sum(item['price'] for item in items)
# 할인 적용
discounted = subtotal * (1 - discount)
# 세금 적용
total = discounted * (1 + tax_rate)
return round(total, 2)
# ❌ 나쁜 함수 - 이름 불명확, 문서화 없음, 의미 불명
def calc(x, y=0.1):
return sum(i['price'] for i in x) * (1 + y)
좋은 함수의 특징:
- 명확한 이름: 함수가 하는 일을 정확히 표현
- 독스트링: 사용법과 예제 포함
- 단일 책임: 한 가지 일만 수행
- 적절한 길이: 20-30줄 이내 (복잡하면 분리)
- 타입 힌트: 매개변수와 반환값 타입 명시 (선택)
타입 힌트 (Type Hints)
Python 3.5+에서는 타입 힌트로 코드 가독성을 높일 수 있습니다. 다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
from typing import List, Dict, Optional
def calculate_average(numbers: List[float]) -> float:
"""
숫자 리스트의 평균을 계산합니다.
Args:
numbers: 숫자 리스트
Returns:
평균값
"""
if not numbers:
return 0.0
return sum(numbers) / len(numbers)
def find_user(user_id: int) -> Optional[Dict[str, any]]:
"""
사용자를 찾습니다.
Args:
user_id: 사용자 ID
Returns:
사용자 정보 딕셔너리 또는 None
"""
users = {1: {"name": "철수", "age": 25}}
return users.get(user_id)
# 타입 힌트는 실행에 영향을 주지 않지만,
# IDE의 자동완성과 타입 체커(mypy)가 활용합니다.
함수 설계 원칙
1. 단일 책임 원칙
다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ❌ 나쁜 예: 여러 책임
def process_user_data(user):
# 검증
if not user.get("email"):
raise ValueError("이메일 필요")
# 데이터베이스 저장
db.save(user)
# 이메일 발송
send_email(user[email], "가입 완료")
# 로그 기록
log(f"사용자 등록: {user['name']}")
# ✅ 좋은 예: 책임 분리
def validate_user(user):
"""사용자 데이터 검증"""
if not user.get("email"):
raise ValueError("이메일 필요")
def save_user(user):
"""데이터베이스에 저장"""
db.save(user)
def send_welcome_email(user):
"""환영 이메일 발송"""
send_email(user[email], "가입 완료")
def log_user_registration(user):
"""등록 로그 기록"""
log(f"사용자 등록: {user['name']}")
# 각 함수는 하나의 책임만 가짐
def register_user(user):
"""사용자 등록 전체 프로세스"""
validate_user(user)
save_user(user)
send_welcome_email(user)
log_user_registration(user)
2. 순수 함수 (Pure Functions)
순수 함수는 같은 입력에 항상 같은 출력을 반환하고, 부작용이 없습니다. 다음은 python를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# ✅ 순수 함수
def add(a, b):
return a + b
# 항상 같은 결과
print(add(3, 5)) # 8
print(add(3, 5)) # 8
# ❌ 순수하지 않은 함수 (외부 상태 의존)
counter = 0
def increment():
global counter
counter += 1
return counter
# 호출할 때마다 다른 결과
print(increment()) # 1
print(increment()) # 2
정리
핵심 요약
- 함수 정의:
def 함수명(매개변수):형태로 정의,return으로 값 반환 - 매개변수 타입: 위치 인자, 키워드 인자, 기본값,
*args,**kwargs - 람다 함수:
lambda x: x ** 2형태의 익명 함수, 간단한 연산에 사용 - 클로저: 외부 함수의 변수를 기억하는 내부 함수,
nonlocal로 수정 - 데코레이터:
@decorator문법으로 함수에 기능 추가, 로깅/캐싱/권한 검사 등
함수 설계 체크리스트
함수 작성 전:
- 함수 이름이 하는 일을 명확히 표현하는가?
- 하나의 책임만 가지는가?
- 매개변수가 5개 이하인가? (많으면 딕셔너리나 클래스 고려) 함수 작성 중:
- 독스트링을 작성했는가?
- 기본값이 있는 매개변수가 뒤에 있는가?
- 가변 객체를 기본값으로 사용하지 않았는가?
- 에러 처리가 적절한가? 함수 작성 후:
- 함수 이름만 보고 기능을 유추할 수 있는가?
- 테스트 케이스를 작성했는가?
- 재사용 가능한가?
다음 단계
함수를 마스터했다면, 객체지향 프로그래밍을 배울 차례입니다.
- Python 클래스와 객체 | 객체지향 프로그래밍 입문
- Python 모듈과 패키지 | import, init.py, 패키지 구조
- Python 데코레이터 심화 | 매개변수, functools.wraps, 클래스 데코레이터
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Python 기본 문법 | 변수, 연산자, 조건문, 반복문
- Python 자료형 | 리스트, 딕셔너리, 튜플, 세트
- Python 클래스와 객체 | 객체지향 프로그래밍
- Python 예외 처리 | try-except, 사용자 정의 예외
이 글에서 다루는 키워드 (관련 검색어)
Python 함수, def 키워드, 매개변수, return, 람다 함수, lambda, 클로저, closure, 데코레이터, decorator, *args, **kwargs, 가변 인자, 함수형 프로그래밍, 고차 함수, 재귀 함수, 타입 힌트 등으로 검색하시면 이 글이 도움이 됩니다.
실전 연습 문제
문제 1: 온도 변환 함수
섭씨를 화씨로, 화씨를 섭씨로 변환하는 함수를 작성하세요. 아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def celsius_to_fahrenheit(celsius):
"""섭씨를 화씨로 변환합니다."""
# 공식: F = C × 9/5 + 32
pass
def fahrenheit_to_celsius(fahrenheit):
"""화씨를 섭씨로 변환합니다."""
# 공식: C = (F - 32) × 5/9
pass
# 테스트
print(celsius_to_fahrenheit(0)) # 32.0
print(celsius_to_fahrenheit(100)) # 212.0
print(fahrenheit_to_celsius(32)) # 0.0
문제 2: 리스트 필터링 함수
조건을 만족하는 요소만 반환하는 범용 필터 함수를 작성하세요. 아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def filter_by_condition(items, condition):
"""
조건을 만족하는 항목만 반환합니다.
Args:
items: 항목 리스트
condition: 조건 함수 (True/False 반환)
"""
pass
# 테스트
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = filter_by_condition(numbers, lambda x: x % 2 == 0)
print(evens) # [2, 4, 6, 8, 10]
문제 3: 데코레이터 만들기
함수 실행 횟수를 세는 데코레이터를 작성하세요. 아래 코드는 python를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
def count_calls(func):
"""함수 호출 횟수를 세는 데코레이터"""
# 여기에 코드 작성
pass
@count_calls
def say_hello():
print("Hello!")
# 테스트
say_hello() # 호출 1회: Hello!
say_hello() # 호출 2회: Hello!
say_hello() # 호출 3회: Hello!
정답은 다음 글에서 확인하세요!
마치며
Python 함수는 프로그래밍의 핵심 빌딩 블록입니다. 함수를 잘 설계하면 코드 재사용성, 가독성, 유지보수성이 모두 향상됩니다. 학습 로드맵:
- 기본 함수: def, return, 매개변수 익히기
- 고급 기능: *args, kwargs, 람다 연습
- 클로저: 상태를 유지하는 함수 만들기
- 데코레이터: 기존 함수에 기능 추가하기
- 실전 적용: 프로젝트에서 함수 분리 연습 함수 단위로 로직을 나누는 데 익숙해지셨다면, Python 클래스와 객체에서 데이터와 함수를 묶는 방법(객체지향)을 이어서 읽어 보세요. 관련 동작을 클래스로 묶으면 큰 프로그램도 설계도에 맞춰 확장하기 쉬워집니다.