[Python 100일 챌린지] Day 27 - 가변 인자 (*args, **kwargs)
print()는 왜 1개든 10개든 마음대로 넣을 수 있을까요? 🤔 ──바로*args를 사용하기 때문입니다! 😊지금까지 만든 함수는 매개변수 개수가 고정이었죠?
add(a, b)함수는 딱 2개만 받을 수 있습니다.하지만 실전에서는 “몇 개가 들어올지 모르는” 상황이 많습니다. 여러 숫자의 평균 계산, 다양한 옵션의 설정 함수…
오늘 배울
*args와**kwargs로 진짜 유연한 함수를 만듭니다! 💡(30분 완독 ⭐⭐⭐)
🎯 오늘의 학습 목표
- *args로 가변 위치 인자 받기
- 일반 매개변수와 *args 혼합하기
- **kwargs로 가변 키워드 인자 받기
- 언패킹 연산자 (*와 **) 활용하기
- 복합 매개변수 순서 규칙 이해하기
- *args와 **kwargs 함께 사용하기
📚 사전 지식
- Day 24: 함수 정의하기 (def) - 함수의 기본 개념
- Day 25: 함수 매개변수와 반환값 - 위치/키워드 인수
- Day 26: 기본 매개변수와 키워드 인자 - 기본값 설정
🎁 가변 인자란?
한 줄 설명
가변 인자 = 개수가 정해지지 않은 인자를 받을 수 있는 특별한 매개변수
함수를 정의할 때 몇 개의 인자를 받을지 모를 때 사용합니다.
실생활 비유
1
2
3
4
5
6
7
8
9
10
📦 택배 상자:
일반 함수: 정확히 3개만 넣을 수 있는 상자
box(item1, item2, item3)
*args: 개수 제한 없이 넣을 수 있는 상자
box(item1, item2, ...) # 몇 개든 OK!
**kwargs: 라벨을 붙여서 넣을 수 있는 상자
box(fragile=item1, heavy=item2, ...)
🎯 학습 목표 1: *args로 가변 위치 인자 받기
기본 개념
*args는 여러 개의 위치 인자를 튜플로 받습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def print_args(*args):
"""전달받은 모든 인자를 출력"""
print(f"받은 인자 개수: {len(args)}")
print(f"타입: {type(args)}")
print(f"내용: {args}")
print_args(1, 2, 3)
# 받은 인자 개수: 3
# 타입: <class 'tuple'>
# 내용: (1, 2, 3)
print_args("사과", "바나나", "포도", "딸기")
# 받은 인자 개수: 4
# 타입: <class 'tuple'>
# 내용: ('사과', '바나나', '포도', '딸기')
이름은 관례일 뿐
*args의 args는 단지 관례입니다. 다른 이름도 가능합니다.
1
2
3
4
5
6
7
def sum_numbers(*numbers): # *numbers도 OK
return sum(numbers)
def concat_strings(*strings): # *strings도 OK
return " ".join(strings)
# 하지만 *args가 가장 일반적이고 읽기 쉬움!
실전 활용 예제
예제 1: 합계 계산
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def calculate_sum(*numbers):
"""개수 제한 없이 숫자를 받아 합계 계산"""
if not numbers:
return 0
total = sum(numbers)
print(f"숫자 {len(numbers)}개의 합: {total}")
return total
# 다양한 개수로 호출
calculate_sum(1, 2, 3) # 6
calculate_sum(10, 20, 30, 40, 50) # 150
calculate_sum(1, 2, 3, 4, 5, 6, 7, 8, 9) # 45
calculate_sum() # 0
예제 2: 최댓값/최솟값 찾기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def find_minmax(*numbers):
"""여러 숫자 중 최솟값과 최댓값을 반환"""
if not numbers:
return None, None
minimum = min(numbers)
maximum = max(numbers)
return minimum, maximum
# 사용
min_val, max_val = find_minmax(5, 2, 8, 1, 9, 3)
print(f"최솟값: {min_val}, 최댓값: {max_val}")
# 최솟값: 1, 최댓값: 9
min_val, max_val = find_minmax(100, 50, 75, 200, 125)
print(f"최솟값: {min_val}, 최댓값: {max_val}")
# 최솟값: 50, 최댓값: 200
예제 3: 문자열 연결
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def create_sentence(*words, separator=" "):
"""여러 단어를 받아 문장 생성 (구분자 선택 가능)"""
if not words:
return ""
sentence = separator.join(words)
return sentence
# 사용
print(create_sentence("Python", "is", "awesome"))
# Python is awesome
print(create_sentence("사과", "바나나", "포도", separator=", "))
# 사과, 바나나, 포도
print(create_sentence("HTML", "CSS", "JavaScript", separator=" + "))
# HTML + CSS + JavaScript
🎯 학습 목표 2: 일반 매개변수와 *args 혼합하기
위치 규칙
일반 매개변수는 *args 앞에 와야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def greet(greeting, *names):
"""인사말과 여러 이름을 받아 인사"""
for name in names:
print(f"{greeting}, {name}!")
# 사용
greet("안녕하세요", "홍길동", "김철수", "이영희")
# 안녕하세요, 홍길동!
# 안녕하세요, 김철수!
# 안녕하세요, 이영희!
greet("Hello", "John", "Jane")
# Hello, John!
# Hello, Jane!
필수 + 가변 인자 조합
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def calculate(operation, *numbers):
"""연산 종류와 숫자들을 받아 계산"""
if not numbers:
return 0
if operation == "sum":
return sum(numbers)
elif operation == "avg":
return sum(numbers) / len(numbers)
elif operation == "max":
return max(numbers)
elif operation == "min":
return min(numbers)
else:
return "지원하지 않는 연산"
# 사용
print(calculate("sum", 1, 2, 3, 4, 5)) # 15
print(calculate("avg", 10, 20, 30, 40)) # 25.0
print(calculate("max", 5, 15, 3, 20, 8)) # 20
print(calculate("min", 5, 15, 3, 20, 8)) # 3
🎯 학습 목표 3: **kwargs로 가변 키워드 인자 받기
기본 개념
**kwargs는 여러 개의 키워드 인자를 딕셔너리로 받습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def print_kwargs(**kwargs):
"""전달받은 모든 키워드 인자를 출력"""
print(f"받은 인자 개수: {len(kwargs)}")
print(f"타입: {type(kwargs)}")
print(f"내용: {kwargs}")
print()
print_kwargs(name="홍길동", age=25, city="서울")
# 받은 인자 개수: 3
# 타입: <class 'dict'>
# 내용: {'name': '홍길동', 'age': 25, 'city': '서울'}
print_kwargs(language="Python", version=3.12, year=2024)
# 받은 인자 개수: 3
# 타입: <class 'dict'>
# 내용: {'language': 'Python', 'version': 3.12, 'year': 2024}
kwargs도 관례
**kwargs의 kwargs도 관례입니다. 다른 이름도 가능합니다.
1
2
3
4
5
def print_info(**details): # **details도 OK
for key, value in details.items():
print(f"{key}: {value}")
# 하지만 **kwargs가 가장 일반적!
실전 활용 예제
예제 1: 사용자 프로필 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def create_profile(name, **details):
"""이름과 추가 정보로 프로필 생성"""
profile = {"이름": name}
# kwargs의 모든 항목을 프로필에 추가
for key, value in details.items():
profile[key] = value
return profile
# 사용
profile1 = create_profile("홍길동", 나이=25, 직업="개발자", 도시="서울")
print(profile1)
# {'이름': '홍길동', '나이': 25, '직업': '개발자', '도시': '서울'}
profile2 = create_profile("김철수", 나이=30, 취미="독서")
print(profile2)
# {'이름': '김철수', '나이': 30, '취미': '독서'}
예제 2: 설정 관리자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def configure_app(**settings):
"""앱 설정을 관리하는 함수"""
default_settings = {
"theme": "light",
"language": "ko",
"notifications": True,
"auto_save": False
}
# 전달받은 설정으로 기본값 업데이트
default_settings.update(settings)
print("=== 앱 설정 ===")
for key, value in default_settings.items():
print(f"{key}: {value}")
return default_settings
# 사용
configure_app(theme="dark", auto_save=True)
# === 앱 설정 ===
# theme: dark
# language: ko
# notifications: True
# auto_save: True
configure_app(language="en", notifications=False)
# === 앱 설정 ===
# theme: light
# language: en
# notifications: False
# auto_save: False
예제 3: HTML 태그 생성기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def create_tag(tag_name, content="", **attributes):
"""HTML 태그를 생성하는 함수"""
# 속성 문자열 생성
attrs = []
for key, value in attributes.items():
attrs.append(f'{key}="{value}"')
attrs_str = " " + " ".join(attrs) if attrs else ""
# 태그 생성
if content:
return f"<{tag_name}{attrs_str}>{content}</{tag_name}>"
else:
return f"<{tag_name}{attrs_str} />"
# 사용
print(create_tag("p", "안녕하세요"))
# <p>안녕하세요</p>
print(create_tag("a", "클릭", href="https://example.com", target="_blank"))
# <a href="https://example.com" target="_blank">클릭</a>
print(create_tag("img", src="photo.jpg", alt="사진", width="400"))
# <img src="photo.jpg" alt="사진" width="400" />
print(create_tag("div", "내용", id="main", class_="container"))
# <div id="main" class_="container">내용</div>
🎯 학습 목표 4: 언패킹 연산자 (*와 **) 활용하기
리스트/튜플 언패킹 (*)
1
2
3
4
5
6
7
8
9
10
11
12
def sum_three(a, b, c):
return a + b + c
# 리스트를 언패킹해서 전달
numbers = [1, 2, 3]
result = sum_three(*numbers) # sum_three(1, 2, 3)과 동일
print(result) # 6
# 튜플도 동일
numbers_tuple = (10, 20, 30)
result = sum_three(*numbers_tuple)
print(result) # 60
딕셔너리 언패킹 (**)
1
2
3
4
5
6
7
8
9
10
11
12
def introduce(name, age, city):
print(f"이름: {name}, 나이: {age}, 도시: {city}")
# 딕셔너리를 언패킹해서 전달
person = {
"name": "홍길동",
"age": 25,
"city": "서울"
}
introduce(**person) # introduce(name="홍길동", age=25, city="서울")과 동일
# 이름: 홍길동, 나이: 25, 도시: 서울
함수 호출 시 언패킹 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def print_info(name, *hobbies, **details):
print(f"이름: {name}")
print(f"취미: {', '.join(hobbies)}")
for key, value in details.items():
print(f"{key}: {value}")
# 언패킹을 사용한 호출
name = "홍길동"
hobbies = ["독서", "운동", "영화"]
details = {"age": 25, "city": "서울", "job": "개발자"}
print_info(name, *hobbies, **details)
# 이름: 홍길동
# 취미: 독서, 운동, 영화
# age: 25
# city: 서울
# job: 개발자
🎯 학습 목표 5: 복합 매개변수 순서 규칙 이해하기
매개변수 순서
중요: 매개변수 순서는 반드시 지켜야 합니다!
1
2
3
4
5
# ✅ 올바른 순서
def func(a, b, *args, c=0, d=0, **kwargs):
pass
# 순서: 일반 매개변수 → *args → 기본값 매개변수 → **kwargs
실전 예제 1: 만능 로거
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def log(level, message, *tags, timestamp=True, **metadata):
"""다양한 정보를 포함하는 로그 함수"""
from datetime import datetime
# 로그 헤더
if timestamp:
time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{time_str}] [{level}]", end=" ")
else:
print(f"[{level}]", end=" ")
# 메시지
print(message, end="")
# 태그
if tags:
print(f" | Tags: {', '.join(tags)}", end="")
# 메타데이터
if metadata:
print(" | Metadata: {", end="")
meta_items = [f"{k}={v}" for k, v in metadata.items()]
print(", ".join(meta_items), end="")
print("}", end="")
print() # 줄바꿈
# 사용
log("INFO", "서버 시작")
# [2024-03-27 10:30:00] [INFO] 서버 시작
log("WARNING", "메모리 부족", "system", "memory", threshold=80)
# [2024-03-27 10:30:01] [WARNING] 메모리 부족 | Tags: system, memory | Metadata: {threshold=80}
log("ERROR", "연결 실패", "network", "database", host="db.example.com", port=5432, timestamp=False)
# [ERROR] 연결 실패 | Tags: network, database | Metadata: {host=db.example.com, port=5432}
실전 예제 2: API 요청 래퍼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def api_request(method, endpoint, *params, timeout=30, **headers):
"""API 요청을 생성하는 함수 (시뮬레이션)"""
print(f"=== API 요청 ===")
print(f"메서드: {method}")
print(f"엔드포인트: {endpoint}")
print(f"타임아웃: {timeout}초")
if params:
print(f"경로 파라미터: {params}")
if headers:
print("헤더:")
for key, value in headers.items():
print(f" {key}: {value}")
return {"status": 200, "message": "성공"}
# 사용
# 기본 GET 요청
api_request("GET", "/users")
# 특정 사용자 조회 (경로 파라미터)
api_request("GET", "/users", 123)
# 인증 헤더 포함
api_request("POST", "/users", Authorization="Bearer token123", Content_Type="application/json")
# 모든 옵션 포함
api_request("DELETE", "/users", 456, timeout=60, Authorization="Bearer token123", X_Request_ID="abc-123")
실전 예제 3: 데이터베이스 쿼리 빌더
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def build_query(table, *columns, limit=None, **conditions):
"""SQL SELECT 쿼리를 생성하는 함수"""
# 컬럼 선택
if columns:
cols = ", ".join(columns)
else:
cols = "*"
# 기본 쿼리
query = f"SELECT {cols} FROM {table}"
# WHERE 조건
if conditions:
where_clauses = []
for key, value in conditions.items():
if isinstance(value, str):
where_clauses.append(f"{key} = '{value}'")
else:
where_clauses.append(f"{key} = {value}")
query += " WHERE " + " AND ".join(where_clauses)
# LIMIT
if limit:
query += f" LIMIT {limit}"
return query
# 사용
print(build_query("users"))
# SELECT * FROM users
print(build_query("users", "id", "name", "email"))
# SELECT id, name, email FROM users
print(build_query("users", "name", "age", city="서울"))
# SELECT name, age FROM users WHERE city = '서울'
print(build_query("users", "id", "name", age=25, active=True, limit=10))
# SELECT id, name FROM users WHERE age = 25 AND active = True LIMIT 10
🎯 학습 목표 6: *args와 **kwargs 함께 사용하기
기본 결합 패턴
*args와 **kwargs를 함께 사용하면 매우 유연한 함수를 만들 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def flexible_function(*args, **kwargs):
"""위치 인자와 키워드 인자를 모두 받는 함수"""
print(f"위치 인자 (args): {args}")
print(f"키워드 인자 (kwargs): {kwargs}")
# 다양한 호출 방법
flexible_function(1, 2, 3)
# 위치 인자 (args): (1, 2, 3)
# 키워드 인자 (kwargs): {}
flexible_function(a=1, b=2, c=3)
# 위치 인자 (args): ()
# 키워드 인자 (kwargs): {'a': 1, 'b': 2, 'c': 3}
flexible_function(1, 2, x=10, y=20)
# 위치 인자 (args): (1, 2)
# 키워드 인자 (kwargs): {'x': 10, 'y': 20}
실전 예제: 로깅 시스템
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def log_message(level, *messages, **metadata):
"""로그 메시지를 기록하는 함수
Args:
level: 로그 레벨 (필수)
*messages: 로그 메시지들 (가변)
**metadata: 추가 메타데이터 (가변)
"""
# 로그 레벨 출력
print(f"[{level.upper()}]", end=" ")
# 모든 메시지 결합
full_message = " ".join(str(msg) for msg in messages)
print(full_message)
# 메타데이터가 있으면 출력
if metadata:
print(f" 메타데이터: {metadata}")
# 다양한 사용 예시
log_message("info", "서버 시작됨")
# [INFO] 서버 시작됨
log_message("error", "연결 실패:", "타임아웃", user="홍길동", attempt=3)
# [ERROR] 연결 실패: 타임아웃
# 메타데이터: {'user': '홍길동', 'attempt': 3}
log_message("warning", "메모리 사용량", "80%", threshold=90, action="모니터링")
# [WARNING] 메모리 사용량 80%
# 메타데이터: {'threshold': 90, 'action': '모니터링'}
실전 예제: 범용 API 호출 래퍼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def api_call(endpoint, *path_params, method="GET", **query_params):
"""API 호출을 위한 범용 래퍼 함수
Args:
endpoint: API 엔드포인트 (필수)
*path_params: URL 경로 파라미터들
method: HTTP 메서드 (기본값: GET)
**query_params: 쿼리 파라미터들
"""
# URL 구성
url = f"https://api.example.com/{endpoint}"
# 경로 파라미터 추가
if path_params:
url += "/" + "/".join(str(p) for p in path_params)
# 쿼리 파라미터 추가
if query_params:
query_string = "&".join(f"{k}={v}" for k, v in query_params.items())
url += f"?{query_string}"
print(f"{method} {url}")
return url
# 다양한 API 호출 패턴
api_call("users")
# GET https://api.example.com/users
api_call("users", 123)
# GET https://api.example.com/users/123
api_call("posts", 456, "comments", page=2, limit=10)
# GET https://api.example.com/posts/456/comments?page=2&limit=10
api_call("products", method="POST", category="books", sort="price")
# POST https://api.example.com/products?category=books&sort=price
데코레이터 패턴에서의 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def smart_cache(*args, **kwargs):
"""함수 호출 결과를 캐싱하는 데코레이터"""
cache = {}
def decorator(func):
def wrapper(*func_args, **func_kwargs):
# 캐시 키 생성
cache_key = (func_args, tuple(sorted(func_kwargs.items())))
# 캐시에 있으면 반환
if cache_key in cache:
print(f"캐시 사용: {func.__name__}{func_args}")
return cache[cache_key]
# 없으면 실행 후 캐시에 저장
result = func(*func_args, **func_kwargs)
cache[cache_key] = result
print(f"캐시 저장: {func.__name__}{func_args}")
return result
return wrapper
return decorator
@smart_cache()
def expensive_calculation(a, b, c=10):
"""시간이 오래 걸리는 계산"""
print(f"계산 중... {a} + {b} + {c}")
return a + b + c
# 사용
print(expensive_calculation(1, 2))
# 계산 중... 1 + 2 + 10
# 캐시 저장: expensive_calculation(1, 2)
# 13
print(expensive_calculation(1, 2))
# 캐시 사용: expensive_calculation(1, 2)
# 13
print(expensive_calculation(1, 2, c=20))
# 계산 중... 1 + 2 + 20
# 캐시 저장: expensive_calculation(1, 2)
# 23
💡 실전 팁 & 주의사항
💡 Tip 1: args는 관례일 뿐
*args와 **kwargs의 이름은 단지 관례입니다. *numbers, **options 등 의미 있는 이름을 사용해도 됩니다.
1
2
3
4
5
6
def sum_numbers(*numbers): # *args보다 명확!
return sum(numbers)
def configure(**options): # **kwargs보다 명확!
for key, value in options.items():
print(f"{key}: {value}")
💡 Tip 2: 타입이 다릅니다
*args는 튜플, **kwargs는 딕셔너리로 받습니다.
1
2
3
4
5
def check_types(*args, **kwargs):
print(f"args 타입: {type(args)}") # <class 'tuple'>
print(f"kwargs 타입: {type(kwargs)}") # <class 'dict'>
check_types(1, 2, 3, a=4, b=5)
💡 Tip 3: 빈 인자도 가능
가변 인자는 0개의 인자도 받을 수 있습니다.
1
2
3
4
5
def flexible(*args, **kwargs):
if not args and not kwargs:
print("인자가 전달되지 않았습니다")
flexible() # 정상 작동
💡 Tip 4: 언패킹으로 함수 호출 간소화
리스트나 딕셔너리를 언패킹하면 코드가 깔끔해집니다.
1
2
3
4
5
6
7
8
9
# 길고 복잡한 방식
print(numbers[0], numbers[1], numbers[2])
# 간결한 방식
print(*numbers)
# 딕셔너리도 마찬가지
config = {"host": "localhost", "port": 8080}
connect(**config) # connect(host="localhost", port=8080)와 동일
💡 Tip 5: 문서화에 명시하기
가변 인자를 사용할 때는 독스트링에 명확히 설명하세요.
1
2
3
4
5
6
7
8
9
def create_user(username, *permissions, **metadata):
"""사용자를 생성합니다.
Args:
username (str): 사용자 이름 (필수)
*permissions: 권한 목록 (가변, 예: 'read', 'write', 'admin')
**metadata: 추가 사용자 정보 (가변, 예: email='...', age=25)
"""
pass
💡 Tip 6: 데코레이터에서 필수
데코레이터를 만들 때 *args와 **kwargs는 거의 필수입니다.
1
2
3
4
5
6
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"{func.__name__} 호출됨")
result = func(*args, **kwargs) # 원본 함수에 모든 인자 전달
return result
return wrapper
⚠️ 주의 1: 매개변수 순서는 엄격합니다
매개변수 순서를 지키지 않으면 SyntaxError가 발생합니다.
1
2
3
4
5
6
7
8
# ✅ 올바른 순서: 일반 → *args → 기본값 → **kwargs
def correct(a, b, *args, c=0, d=0, **kwargs):
pass
# ❌ 잘못된 순서
# def wrong1(*args, a, b): # *args 뒤에 필수 매개변수
# def wrong2(**kwargs, *args): # **kwargs는 반드시 마지막
# def wrong3(a=1, b): # 기본값 있는 것이 앞에
⚠️ 주의 2: args는 튜플이라 수정 불가
*args는 튜플이므로 요소를 직접 수정할 수 없습니다.
1
2
3
4
5
6
7
8
def modify_args(*args):
# ❌ 에러 발생
# args[0] = 100 # TypeError: 'tuple' object does not support item assignment
# ✅ 리스트로 변환 후 수정
args_list = list(args)
args_list[0] = 100
return args_list
⚠️ 주의 3: 언패킹 시 키 이름 일치 필수
** 언패킹 시 딕셔너리 키와 매개변수 이름이 정확히 일치해야 합니다.
1
2
3
4
5
6
7
8
9
10
def greet(name, age):
print(f"{name}, {age}세")
# ❌ 키가 일치하지 않으면 TypeError
person = {"username": "홍길동", "years": 25}
# greet(**person) # TypeError!
# ✅ 키가 정확히 일치해야 함
person = {"name": "홍길동", "age": 25}
greet(**person) # 정상 작동
⚠️ 주의 4: Python 예약어는 키워드 인자로 사용 불가
Python 예약어(class, def, return 등)는 키워드 인자 이름으로 사용할 수 없습니다.
1
2
3
4
5
6
7
8
9
# ❌ SyntaxError 발생
# create_user(class="student")
# ✅ 다른 이름 사용
create_user(user_class="student")
# ✅ 또는 딕셔너리로 전달 (하지만 함수 내부에서 처리 필요)
params = {"class": "student"}
# 함수 내부에서 params['class']로 접근
⚠️ 주의 5: 너무 많은 인자는 디버깅 어려움
*args와 **kwargs를 남용하면 코드 가독성과 디버깅이 어려워집니다.
1
2
3
4
5
6
7
8
9
10
11
# ❌ 너무 유연해서 오히려 문제
def process(*args, **kwargs):
# 어떤 인자를 받는지 불명확
pass
# ✅ 명시적인 매개변수 + 가변 인자 조합
def process(required_param, *optional_items, debug=False, **metadata):
"""필수 매개변수를 명시하고 가변 인자는 보조적으로 사용"""
if len(optional_items) > 10:
raise ValueError("선택 인자가 너무 많습니다")
# 처리 로직
⚠️ 주의 6: 위치 인자와 키워드 인자 혼동 주의
호출 시 위치 인자는 키워드 인자보다 먼저 와야 합니다.
1
2
3
4
5
6
7
8
def func(*args, **kwargs):
pass
# ✅ 올바른 호출
func(1, 2, 3, a=4, b=5)
# ❌ 잘못된 호출
# func(a=4, b=5, 1, 2, 3) # SyntaxError: 위치 인자는 키워드 인자 앞에!
🧪 연습 문제
문제 1: 평균 계산기
다음 요구사항을 만족하는 calculate_average 함수를 작성하세요.
- 개수 제한 없이 숫자를 받아 평균 계산
- 소수점 둘째 자리까지 반올림
- 숫자가 없으면 0 반환
💡 힌트
*args를 사용하고, len()과 sum()을 활용하세요.
1
2
3
4
def calculate_average(*numbers):
if not numbers:
return 0
# 평균 계산 및 반올림
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
def calculate_average(*numbers):
"""개수 제한 없이 숫자를 받아 평균 계산"""
if not numbers:
return 0
average = sum(numbers) / len(numbers)
return round(average, 2)
# 테스트
print(calculate_average(10, 20, 30)) # 20.0
print(calculate_average(85, 90, 78, 92, 88)) # 86.6
print(calculate_average(100)) # 100.0
print(calculate_average()) # 0
문제 2: 딕셔너리 병합 함수
다음 요구사항을 만족하는 merge_dicts 함수를 작성하세요.
- 여러 딕셔너리를 받아 하나로 병합
- 같은 키가 있으면 나중 값으로 덮어쓰기
- 딕셔너리가 없으면 빈 딕셔너리 반환
💡 힌트
**kwargs를 사용하지 않고, *dicts로 딕셔너리들을 받으세요.
1
2
3
4
5
def merge_dicts(*dicts):
result = {}
for d in dicts:
result.update(d)
return result
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def merge_dicts(*dicts):
"""여러 딕셔너리를 하나로 병합"""
result = {}
for dictionary in dicts:
result.update(dictionary)
return result
# 테스트
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
dict3 = {"a": 10, "e": 5} # 'a'가 중복 (덮어쓰기됨)
merged = merge_dicts(dict1, dict2, dict3)
print(merged)
# {'a': 10, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
# 빈 경우
print(merge_dicts()) # {}
문제 3: 함수 데코레이터 시뮬레이션
다음 요구사항을 만족하는 call_function 함수를 작성하세요.
- 함수와 그 함수의 인자들을 받음
- 함수 호출 전후에 로그 출력
- 함수 실행 시간 측정
- 결과 반환
💡 힌트
func를 첫 번째 매개변수로, *args와 **kwargs로 나머지를 받으세요.
1
2
3
4
5
6
7
8
import time
def call_function(func, *args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # 함수 호출
end = time.time()
# 로그 출력
return result
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import time
def call_function(func, *args, **kwargs):
"""함수를 호출하고 실행 시간을 측정"""
print(f"=== 함수 '{func.__name__}' 호출 ===")
print(f"위치 인자: {args}")
print(f"키워드 인자: {kwargs}")
start = time.time()
result = func(*args, **kwargs)
end = time.time()
elapsed = (end - start) * 1000 # 밀리초로 변환
print(f"실행 시간: {elapsed:.2f}ms")
print(f"결과: {result}")
print()
return result
# 테스트용 함수들
def add(a, b):
return a + b
def greet(name, greeting="안녕하세요"):
return f"{greeting}, {name}님!"
def calculate_sum(*numbers):
return sum(numbers)
# 테스트
call_function(add, 10, 20)
call_function(greet, "홍길동", greeting="Hello")
call_function(calculate_sum, 1, 2, 3, 4, 5)
출력:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
=== 함수 'add' 호출 ===
위치 인자: (10, 20)
키워드 인자: {}
실행 시간: 0.01ms
결과: 30
=== 함수 'greet' 호출 ===
위치 인자: ('홍길동',)
키워드 인자: {'greeting': 'Hello'}
실행 시간: 0.01ms
결과: Hello, 홍길동님!
=== 함수 'calculate_sum' 호출 ===
위치 인자: (1, 2, 3, 4, 5)
키워드 인자: {}
실행 시간: 0.02ms
결과: 15
📝 오늘 배운 내용 정리
- *args: 개수 제한 없는 위치 인자를 튜플로 받음
- **kwargs: 개수 제한 없는 키워드 인자를 딕셔너리로 받음
- 순서: 일반 → *args → 기본값 → **kwargs
- 언패킹:
*로 리스트/튜플,**로 딕셔너리 언패킹 - 활용: 유연한 함수, 래퍼 함수, 설정 관리
🎯 실습 과제
과제: 만능 프린터 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def smart_print(*items, sep=" ", end="\n", prefix="", suffix="", **styles):
"""다양한 옵션으로 출력하는 함수"""
# 스타일 적용
styled_items = []
for item in items:
styled_item = str(item)
# 스타일 옵션 적용
if styles.get("upper"):
styled_item = styled_item.upper()
if styles.get("lower"):
styled_item = styled_item.lower()
if styles.get("bold"):
styled_item = f"**{styled_item}**"
if styles.get("italic"):
styled_item = f"*{styled_item}*"
styled_items.append(styled_item)
# 출력 문자열 구성
output = prefix + sep.join(styled_items) + suffix
print(output, end=end)
# 테스트
smart_print("Hello", "World")
# Hello World
smart_print("Python", "is", "awesome", sep=" | ")
# Python | is | awesome
smart_print("Important", prefix=">>> ", suffix=" <<<")
# >>> Important <<<
smart_print("error", "warning", upper=True, bold=True)
# **ERROR** **WARNING**
smart_print("Title", "SubTitle", sep=": ", upper=True, end="\n\n")
# TITLE: SUBTITLE
smart_print("Item1", "Item2", "Item3", sep=", ", prefix="[ ", suffix=" ]", lower=True)
# [ item1, item2, item3 ]
❓ FAQ
Q1. *args와 **kwargs는 언제 사용하나요?
A: 함수가 받을 인자의 개수를 미리 알 수 없을 때 사용합니다.
1
2
3
4
5
6
7
8
# 예시: 로깅 함수는 메시지 개수를 모름
def log(*messages, level="INFO"):
print(f"[{level}]", *messages)
# 예시: 설정 함수는 옵션 종류를 모름
def configure(**options):
for key, value in options.items():
print(f"{key} = {value}")
사용 시기:
- 유틸리티 함수 (예: print, log)
- 래퍼 함수 (다른 함수를 감싸는 함수)
- 데코레이터
- API 클라이언트 (동적 파라미터)
Q2. *args와 **kwargs를 항상 함께 써야 하나요?
A: 아니요, 필요에 따라 하나만 사용하거나 둘 다 사용할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
# *args만 사용
def sum_all(*numbers):
return sum(numbers)
# **kwargs만 사용
def create_config(**settings):
return settings
# 둘 다 사용
def flexible_func(*args, **kwargs):
print(f"위치: {args}, 키워드: {kwargs}")
선택 기준:
- 위치 인자만 필요 →
*args만 - 키워드 인자만 필요 →
**kwargs만 - 둘 다 필요 → 함께 사용
Q3. args와 kwargs는 다른 이름으로 바꿔도 되나요?
A: 네, *와 **만 있으면 이름은 자유롭게 변경 가능합니다.
1
2
3
4
5
6
7
8
9
10
# 의미 있는 이름 사용 (권장)
def calculate_total(*prices):
return sum(prices)
def create_user(name, **user_info):
return {"name": name, **user_info}
# args/kwargs 사용 (관례)
def generic_func(*args, **kwargs):
pass
권장 사항:
- 의미가 명확하면 구체적인 이름 사용
- 범용적인 경우 args/kwargs 사용
- 일관성 유지가 중요
Q4. 매개변수 순서를 왜 지켜야 하나요?
A: Python 문법 규칙이며, 순서를 지키지 않으면 SyntaxError가 발생합니다.
1
2
3
4
5
6
7
8
# ✅ 올바른 순서
def func(a, b, *args, c=0, **kwargs):
pass
# ❌ 잘못된 순서들
# def wrong1(*args, a): # *args 뒤에 필수 매개변수
# def wrong2(**kwargs, a=0): # **kwargs는 마지막이어야 함
# def wrong3(a=0, b): # 기본값 매개변수가 필수보다 앞
올바른 순서: 필수 매개변수 → *args → 기본값 매개변수 → **kwargs
Q5. *와 **로 언패킹할 때 주의할 점은?
A: 언패킹 시 타입과 개수, 키 이름을 확인해야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def greet(name, age, city):
print(f"{name}, {age}세, {city}")
# ✅ 리스트 언패킹 (개수 일치)
data = ["홍길동", 25, "서울"]
greet(*data)
# ❌ 개수 불일치
# data = ["홍길동", 25]
# greet(*data) # TypeError: 인자 1개 부족
# ✅ 딕셔너리 언패킹 (키 이름 일치)
info = {"name": "홍길동", "age": 25, "city": "서울"}
greet(**info)
# ❌ 키 이름 불일치
# info = {"username": "홍길동", "age": 25, "city": "서울"}
# greet(**info) # TypeError: name이 없음
Q6. *args는 튜플인데 왜 수정이 안 되나요?
A: 튜플은 불변(immutable) 자료형이기 때문입니다.
1
2
3
4
5
6
7
8
9
10
11
def modify_args(*args):
# ❌ 직접 수정 불가
# args[0] = 100 # TypeError
# ✅ 리스트로 변환 후 수정
args_list = list(args)
args_list[0] = 100
return args_list
result = modify_args(1, 2, 3)
print(result) # [100, 2, 3]
이유: 튜플은 함수 내부에서 실수로 수정되는 것을 방지하기 위해 불변입니다.
Q7. 데코레이터에서 *args와 **kwargs가 필수인가요?
A: 범용 데코레이터를 만들려면 필수입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ✅ 범용 데코레이터 (모든 함수에 적용 가능)
def timer_decorator(func):
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs) # 모든 인자 전달
end = time.time()
print(f"{func.__name__} 실행 시간: {end - start:.4f}초")
return result
return wrapper
@timer_decorator
def calculate(a, b, c=10):
return a + b + c
@timer_decorator
def greet(name):
print(f"Hello, {name}!")
# 둘 다 정상 작동
calculate(1, 2)
greet("홍길동")
이유: 데코레이터는 어떤 함수든 감쌀 수 있어야 하므로 유연한 인자 처리가 필요합니다.
Q8. *args와 **kwargs를 남용하면 안 되는 이유는?
A: 코드 가독성과 유지보수성이 떨어지기 때문입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ❌ 나쁜 예: 무엇을 받는지 불명확
def process(*args, **kwargs):
# 어떤 인자가 필요한지 알 수 없음
pass
# ✅ 좋은 예: 명시적 + 가변 인자 조합
def process(user_id, action, *additional_data, debug=False, **metadata):
"""
user_id: 필수 사용자 ID
action: 필수 작업 유형
*additional_data: 선택적 추가 데이터
debug: 디버그 모드 (기본값: False)
**metadata: 선택적 메타데이터
"""
pass
권장 사항:
- 필수 매개변수는 명시적으로 선언
- 가변 인자는 보조적으로 사용
- 독스트링으로 명확히 문서화
🔗 관련 자료
- Python 공식 문서 - 임의 인자 목록 -
*args와**kwargs에 대한 공식 설명 - PEP 3102 - 키워드 전용 인자 - 키워드 전용 매개변수 제안서
- Real Python - Python args and kwargs - 실전 활용 예제와 모범 사례
- Python 공식 문서 - 언패킹 인자 목록 -
*와**언패킹 연산자 설명 - PEP 448 - 추가 언패킹 일반화 - Python 3.5+의 확장된 언패킹 기능
📚 이전 학습
Day 26: 기본 매개변수와 키워드 인자 ⭐
어제는 매개변수의 기본값을 설정하는 방법을 배웠습니다!
📚 다음 학습
Day 28: 람다 함수 (lambda) ⭐⭐⭐
내일은 한 줄로 함수를 정의하는 람다 함수를 배웁니다!
“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀
Day 27/100 Phase 3: 제어문과 함수 #100DaysOfPython
