Python *args와 **kwargs 완벽 가이드: 가변 인자 완전 정복
Updated on
모든 Python 개발자는 언젠가 이 벽에 부딪힙니다. 라이브러리의 소스 코드를 열어보거나, 동료의 pull request를 검토하거나, 오픈소스 프로젝트를 살펴보다가 *args와 **kwargs가 가득한 함수 시그니처를 보게 됩니다. 별표는 무엇을 의미할까요? 왜 하나와 두 개가 있을까요? 언제 어떤 것을 사용해야 할까요? 잘못 사용하면 "takes 2 positional arguments but 5 were given"이나 "got an unexpected keyword argument" 같은 혼란스러운 TypeError 메시지가 발생하며, 간단한 함수 호출이 갑자기 디버깅 세션으로 변합니다.
직접 유연한 함수를 작성해야 할 때 문제는 더 심각해집니다. 모든 매개변수를 하드코딩하면 API가 경직됩니다. 너무 많은 명명된 매개변수를 받아들이면 함수 시그니처가 읽기 어려워집니다. 명확성을 희생하지 않고도 가변 개수의 인자를 받아들일 수 있는 방법이 필요합니다.
Python은 두 가지 특별한 구문 기능으로 이를 해결합니다: 가변 위치 인자를 위한 *args와 가변 키워드 인자를 위한 **kwargs. 이 가이드는 두 가지를 기초부터 설명하고, 언패킹 연산자를 다루며, 실전 패턴을 살펴보고, 가장 흔한 실수를 피하는 방법을 도와드립니다.
*args와 **kwargs란 무엇인가?
Python에서 *args와 **kwargs는 함수 정의에서 가변 개수의 인자를 받아들이기 위한 관례입니다.
- **
*args**는 추가 위치 인자를 튜플로 모읍니다. - **
**kwargs**는 추가 키워드 인자를 딕셔너리로 모읍니다.
args와 kwargs라는 이름은 관례이며 필수는 아닙니다. 마법은 이름 자체가 아니라 *와 ** 접두사에서 옵니다. *values와 **options로 작성해도 동일하게 작동합니다.
가장 간단한 예시는 다음과 같습니다:
def show_args(*args, **kwargs):
print(f"args = {args}")
print(f"kwargs = {kwargs}")
show_args(1, 2, 3, name="Alice", age=30)
# args = (1, 2, 3)
# kwargs = {'name': 'Alice', 'age': 30}위치 인자(이름 없이 전달된 값)는 튜플 형태로 args에 들어갑니다. 키워드 인자(key=value 구문으로 전달된 값)는 딕셔너리 형태로 kwargs에 들어갑니다. 이것이 핵심 개념 전부입니다.
*args 이해: 가변 위치 인자
매개변수 이름 앞의 단일 별표 *는 Python에게 남은 모든 위치 인자를 튜플로 묶으라고 알려줍니다. 이를 통해 함수는 임의 개수의 위치 값을 받아들일 수 있습니다.
기본 문법과 사용법
def add_all(*args):
"""어떤 개수의 값이라도 합산합니다."""
total = 0
for num in args:
total += num
return total
print(add_all(1, 2)) # 3
print(add_all(1, 2, 3, 4, 5)) # 15
print(add_all(10)) # 10
print(add_all()) # 0함수 내부에서 args는 일반 Python 튜플입니다. 이를 순회하거나, 인덱싱하거나, 길이를 확인하거나, 다른 함수에 전달할 수 있습니다.
def describe_args(*args):
print(f"Type: {type(args)}")
print(f"Length: {len(args)}")
print(f"First element: {args[0] if args else 'N/A'}")
print(f"Contents: {args}")
describe_args("hello", 42, True)
# Type: <class 'tuple'>
# Length: 3
# First element: hello
# Contents: ('hello', 42, True)일반 매개변수와 *args 함께 사용하기
표준 매개변수와 *args를 혼합할 수 있습니다. 모든 일반 위치 매개변수가 먼저 채워지고, 남은 위치 인자는 *args로 들어갑니다:
def log_message(level, *args):
"""심각도 레벨과 함께 메시지를 로깅합니다."""
message = " ".join(str(a) for a in args)
print(f"[{level.upper()}] {message}")
log_message("info", "Server started on port", 8080)
# [INFO] Server started on port 8080
log_message("error", "Connection failed:", "timeout after", 30, "seconds")
# [ERROR] Connection failed: timeout after 30 seconds실전 예제: 유연한 평균 함수
def average(*values):
"""임의 개수의 값에 대한 평균을 계산합니다."""
if not values:
raise ValueError("average() requires at least one argument")
return sum(values) / len(values)
print(average(85, 90, 78)) # 84.33333333333333
print(average(100)) # 100.0
print(average(72, 88, 95, 67, 91)) # 82.6실전 예제: 문자열 포맷팅 헬퍼
def build_path(*segments):
"""경로 세그먼트를 슬래시로 연결하고 불필요한 슬래시를 제거합니다."""
cleaned = [seg.strip("/") for seg in segments if seg]
return "/" + "/".join(cleaned)
print(build_path("api", "v2", "users", "123"))
# /api/v2/users/123
print(build_path("/data/", "/reports/", "2026/", "sales.csv"))
# /data/reports/2026/sales.csv**kwargs 이해: 가변 키워드 인자
매개변수 이름 앞의 이중 별표 **는 Python에게 남은 모든 키워드 인자를 딕셔너리로 묶으라고 알려줍니다. 이를 통해 함수는 임의 개수의 명명된 값을 받아들일 수 있습니다.
기본 문법과 사용법
def print_info(**kwargs):
"""키-값 쌍을 포맷된 방식으로 출력합니다."""
for key, value in kwargs.items():
print(f" {key}: {value}")
print_info(name="Alice", age=30, city="Seattle")
# name: Alice
# age: 30
# city: Seattle함수 내부에서 kwargs는 표준 Python 딕셔너리입니다. .get(), .keys(), .values(), .items() 및 기타 모든 딕셔너리 메서드를 사용할 수 있습니다.
def describe_kwargs(**kwargs):
print(f"Type: {type(kwargs)}")
print(f"Keys: {list(kwargs.keys())}")
print(f"Values: {list(kwargs.values())}")
describe_kwargs(x=10, y=20, z=30)
# Type: <class 'dict'>
# Keys: ['x', 'y', 'z']
# Values: [10, 20, 30]실전 예제: 설정 구성기
def create_connection(host, port, **kwargs):
"""선택적 설정으로 데이터베이스 연결을 생성합니다."""
config = {
"host": host,
"port": port,
"timeout": kwargs.get("timeout", 30),
"retries": kwargs.get("retries", 3),
"ssl": kwargs.get("ssl", True),
"pool_size": kwargs.get("pool_size", 5),
}
# 호출자가 제공한 추가 설정 추가
for key, value in kwargs.items():
if key not in config:
config[key] = value
return config
# 기본 사용법
basic = create_connection("localhost", 5432)
print(basic)
# {'host': 'localhost', 'port': 5432, 'timeout': 30, 'retries': 3, 'ssl': True, 'pool_size': 5}
# 사용자 정의 옵션 사용
custom = create_connection(
"db.example.com", 5432,
timeout=60,
ssl=False,
application_name="my_app"
)
print(custom)
# {'host': 'db.example.com', 'port': 5432, 'timeout': 60, 'retries': 3, 'ssl': False, 'pool_size': 5, 'application_name': 'my_app'}실전 예제: HTML 태그 빌더
def html_tag(tag, content="", **attributes):
"""선택적 속성을 가진 HTML 태그를 생성합니다."""
attr_str = ""
for key, value in attributes.items():
# Python 네이밍을 HTML로 변환 (class_ -> class)
html_key = key.rstrip("_")
attr_str += f' {html_key}="{value}"'
if content:
return f"<{tag}{attr_str}>{content}</{tag}>"
return f"<{tag}{attr_str} />"
print(html_tag("a", "Click here", href="https://example.com", class_="btn"))
# <a href="https://example.com" class="btn">Click here</a>
print(html_tag("img", src="photo.jpg", alt="A photo", width="200"))
# <img src="photo.jpg" alt="A photo" width="200" />
print(html_tag("p", "Hello world", id="intro", style="color: blue"))
# <p id="intro" style="color: blue">Hello world</p>*args와 **kwargs 함께 사용하기
같은 함수 정의에서 둘 다 사용하여 위치 인자와 키워드 인자의 어떤 조합이든 받아들일 수 있습니다. 매개변수 순서는 엄격한 규칙을 따릅니다.
매개변수 순서 규칙
Python은 함수 시그니처에서 다음과 같은 정확한 순서를 강제합니다:
- 일반 위치 매개변수
*args(가변 위치)- 키워드 전용 매개변수 (
*args이후) **kwargs(가변 키워드)
def example(a, b, *args, option=True, **kwargs):
print(f"a = {a}")
print(f"b = {b}")
print(f"args = {args}")
print(f"option = {option}")
print(f"kwargs = {kwargs}")
example(1, 2, 3, 4, 5, option=False, color="red", size=10)
# a = 1
# b = 2
# args = (3, 4, 5)
# option = False
# kwargs = {'color': 'red', 'size': 10}매개변수 유형과 순서를 요약하면 다음과 같습니다:
| 위치 | 유형 | 구문 | 예시 | 설명 |
|---|---|---|---|---|
| 1st | 위치 | param | a, b | 필수, 위치로 채워짐 |
| 2nd | 기본값 | param=value | c=10 | 선택적, 위치 또는 이름으로 채워짐 |
| 3rd | 가변 위치 | *args | *args | 추가 위치 인자 수집 |
| 4th | 키워드 전용 | param (* 이후) | option=True | 이름으로만 전달해야 함 |
| 5th | 가변 키워드 | **kwargs | **kwargs | 추가 키워드 인자 수집 |
일반적인 패턴: 전달 함수
*args와 **kwargs를 사용하는 가장 유용한 패턴 중 하나는 모든 인자를 다른 함수로 전달하는 함수를 만드는 것입니다:
def timed_call(func, *args, **kwargs):
"""함수를 호출하고 실행 시간을 측정합니다."""
import time
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
def expensive_sum(a, b, c):
import time
time.sleep(0.1)
return a + b + c
result = timed_call(expensive_sum, 10, 20, c=30)
# expensive_sum took 0.1003s
print(result)
# 60*와 **로 언패킹하기
*와 ** 연산자는 양방향으로 작동합니다: 함수 정의에서 인자를 패킹하고, 함수 호출 및 기타 컨텍스트에서 인자를 언패킹합니다.
*로 리스트와 튜플 언패킹
반복 가능한 객체를 개별 위치 인자로 펼치려면 *를 사용합니다:
def add(a, b, c):
return a + b + c
numbers = [10, 20, 30]
# 언패킹 없이 - TypeError 발생
# add(numbers) # TypeError: add() missing 2 required positional arguments
# 언패킹 사용 - 리스트를 별도의 인자로 펼침
result = add(*numbers)
print(result) # 60
# 튜플, 집합 및 모든 반복 가능한 객체에서 작동
coords = (5, 10, 15)
print(add(*coords)) # 30*를 할당 및 리스트 구성에서도 사용할 수 있습니다(Python 3.5+):
# 할당에서 확장 언패킹
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
# 리스트/튜플 구성에서 언패킹
list_a = [1, 2, 3]
list_b = [4, 5, 6]
combined = [*list_a, *list_b]
print(combined) # [1, 2, 3, 4, 5, 6]
# 추가 요소와 함께 언패킹
extended = [0, *list_a, 99, *list_b, 100]
print(extended) # [0, 1, 2, 3, 99, 4, 5, 6, 100]**로 딕셔너리 언패킹
딕셔너리를 키워드 인자로 언패킹하려면 **를 사용합니다:
def create_user(name, email, role="viewer"):
return {"name": name, "email": email, "role": role}
user_data = {"name": "Alice", "email": "alice@example.com", "role": "admin"}
# 딕셔너리를 키워드 인자로 언패킹
user = create_user(**user_data)
print(user)
# {'name': 'Alice', 'email': 'alice@example.com', 'role': 'admin'}**로 딕셔너리 병합
**의 가장 실용적인 용도 중 하나는 딕셔너리 병합입니다:
defaults = {"color": "blue", "size": 12, "font": "Arial"}
user_prefs = {"color": "red", "size": 16}
# 병합: user_prefs가 defaults를 덮어씀
merged = {**defaults, **user_prefs}
print(merged)
# {'color': 'red', 'size': 16, 'font': 'Arial'}
# Python 3.9+에서는 | 연산자도 지원
merged_new = defaults | user_prefs
print(merged_new)
# {'color': 'red', 'size': 16, 'font': 'Arial'}
# 병합 중 추가 키 추가
final = {**defaults, **user_prefs, "theme": "dark"}
print(final)
# {'color': 'red', 'size': 16, 'font': 'Arial', 'theme': 'dark'}*와 ** 언패킹 결합
함수를 호출할 때 두 연산자를 함께 사용할 수 있습니다:
def report(title, *items, separator="---", **metadata):
print(f"== {title} ==")
for item in items:
print(f" - {item}")
print(separator)
for key, value in metadata.items():
print(f" {key}: {value}")
positional = ["Task A", "Task B", "Task C"]
options = {"author": "Alice", "date": "2026-02-14"}
report("Sprint Review", *positional, separator="===", **options)
# == Sprint Review ==
# - Task A
# - Task B
# - Task C
# ===
# author: Alice
# date: 2026-02-14실전 패턴
패턴 1: 데코레이터 함수
프로덕션 Python에서 *args와 **kwargs를 가장 많이 사용하는 것은 데코레이터 작성입니다. 데코레이터는 하나의 함수를 다른 함수로 감쌉니다. 래핑된 함수의 시그니처를 미리 알 수 없으므로 모든 인자를 전달하려면 *args와 **kwargs를 사용해야 합니다:
import functools
import time
def retry(max_attempts=3, delay=1.0):
"""실패 시 함수를 최대 max_attempts번 재시도합니다."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"Attempt {attempt}/{max_attempts} failed: {e}")
if attempt < max_attempts:
time.sleep(delay)
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def fetch_data(url, timeout=10):
"""실패할 수 있는 데이터 가져오기를 시뮬레이션합니다."""
import random
if random.random() < 0.6:
raise ConnectionError(f"Failed to connect to {url}")
return f"Data from {url}"
# 데코레이터는 url과 timeout을 *args/**kwargs를 통해 전달
result = fetch_data("https://api.example.com", timeout=5)
print(result)패턴 2: 서브클래스 init 전달
서브클래싱할 때 종종 생성자 인자를 부모 클래스로 전달해야 합니다. *args와 **kwargs는 이를 깔끔하게 만듭니다:
class Animal:
def __init__(self, name, species, sound="..."):
self.name = name
self.species = species
self.sound = sound
def speak(self):
return f"{self.name} says {self.sound}"
class Dog(Animal):
def __init__(self, *args, breed="Unknown", **kwargs):
super().__init__(*args, **kwargs)
self.breed = breed
def info(self):
return f"{self.name} ({self.breed}) - {self.species}"
# 모든 Animal 매개변수가 원활하게 전달됨
dog = Dog("Rex", "Canine", sound="Woof!", breed="German Shepherd")
print(dog.speak()) # Rex says Woof!
print(dog.info()) # Rex (German Shepherd) - Canine이 패턴은 Django, Flask, SQLAlchemy와 같은 프레임워크에서 기본 클래스를 확장할 때 특히 필수적입니다.
패턴 3: 래퍼 및 프록시 함수
원본 함수를 변경하지 않고 함수 호출을 가로채거나 수정해야 할 때:
def log_call(func):
"""함수의 모든 호출과 인자를 로깅합니다."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
print(f"Calling {func.__name__}({signature})")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result!r}")
return result
return wrapper
@log_call
def calculate_discount(price, discount_pct, tax_rate=0.08):
discounted = price * (1 - discount_pct / 100)
return round(discounted * (1 + tax_rate), 2)
calculate_discount(100, 20, tax_rate=0.1)
# Calling calculate_discount(100, 20, tax_rate=0.1)
# calculate_discount returned 88.0패턴 4: API 클라이언트 빌더
유연한 API 래퍼를 구축하는 것은 고전적인 사용 사례입니다:
import json
class APIClient:
def __init__(self, base_url, **default_headers):
self.base_url = base_url.rstrip("/")
self.default_headers = {
"Content-Type": "application/json",
"Accept": "application/json",
**default_headers,
}
def request(self, method, endpoint, *args, **kwargs):
"""병합된 헤더와 매개변수로 요청을 구성합니다."""
url = f"{self.base_url}/{endpoint.lstrip('/')}"
headers = {**self.default_headers, **kwargs.pop("headers", {})}
request_info = {
"method": method,
"url": url,
"headers": headers,
**kwargs,
}
print(json.dumps(request_info, indent=2, default=str))
return request_info
def get(self, endpoint, **kwargs):
return self.request("GET", endpoint, **kwargs)
def post(self, endpoint, **kwargs):
return self.request("POST", endpoint, **kwargs)
# 사용법
client = APIClient(
"https://api.example.com",
Authorization="Bearer token123"
)
client.get("/users", params={"page": 1, "limit": 50})
client.post("/users", data={"name": "Alice"}, headers={"X-Request-ID": "abc123"})패턴 5: 데이터 과학 -- 동적 플로팅 매개변수
데이터 분석 함수를 구축할 때 **kwargs를 사용하면 기본 라이브러리로 설정을 전달할 수 있습니다:
import pandas as pd
def analyze_column(df, column, **plot_kwargs):
"""데이터프레임 열을 분석하고 요약 통계를 생성합니다."""
stats = {
"count": df[column].count(),
"mean": df[column].mean(),
"std": df[column].std(),
"min": df[column].min(),
"max": df[column].max(),
}
print(f"\nAnalysis of '{column}':")
for stat, value in stats.items():
print(f" {stat}: {value:.2f}")
# 추가 kwargs를 플롯 함수로 전달
plot_defaults = {"kind": "hist", "bins": 20, "title": f"Distribution of {column}"}
plot_config = {**plot_defaults, **plot_kwargs}
# df[column].plot(**plot_config) # matplotlib 설치 시 주석 해제
print(f" Plot config: {plot_config}")
return stats
# 샘플 데이터 생성
df = pd.DataFrame({
"revenue": [100, 250, 180, 320, 275, 410, 195, 360],
"quantity": [5, 12, 8, 15, 13, 20, 9, 17],
})
# 기본 분석
analyze_column(df, "revenue")
# **kwargs를 통해 사용자 정의 플롯 설정 전달
analyze_column(df, "revenue", kind="box", color="steelblue", figsize=(10, 6))Jupyter 노트북에서 작업하고 함수 시그니처를 대화식으로 실험하고 싶다면, RunCell (opens in a new tab)은 AI 기반 노트북 환경을 제공하여 *args와 **kwargs 패턴을 테스트하고, 매개변수 처리에 대한 실시간 제안을 받으며, 워크플로우를 떠나지 않고 인자 전달 문제를 디버깅할 수 있습니다.
흔한 실수와 해결 방법
*args와 **kwargs를 사용할 때 Python 개발자가 가장 자주 마주치는 오류와 그 해결책은 다음과 같습니다:
| 실수 | 오류 메시지 | 원인 | 해결책 |
|---|---|---|---|
| 잘못된 매개변수 순서 | SyntaxError: invalid syntax | **kwargs를 *args 앞에 배치 | 항상 순서 사용: 일반, *args, 키워드 전용, **kwargs |
| 리스트 전달 대신 언패킹 | TypeError: func() missing required arguments | *[1,2,3] 대신 [1,2,3] 전달 | 언패킹 사용: func(*my_list) |
| 중복 키워드 인자 | TypeError: got multiple values for argument 'x' | 위치 인자와 **kwargs에 같은 키 | 위치 인자와 딕셔너리 키 간 중복 제거 |
| kwargs 직접 수정 | 예상치 못한 부작용 | kwargs 딕셔너리 변경 | kwargs.copy() 또는 {**kwargs, ...} 사용 |
| super().__init__에서 *args 잊음 | TypeError: __init__() missing arguments | 부모 클래스에 인자 전달 안 함 | super().__init__(*args, **kwargs) 사용 |
| kwargs와 가변 기본값 사용 | 호출 간 공유 상태 | def func(data={}) | 기본값으로 None 사용: def func(data=None) |
예시: 중복 키워드 인자
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
data = {"name": "Alice", "greeting": "Hi"}
# 잘못됨: name이 위치 인자와 **data 모두에서 전달됨
# greet("Alice", **data)
# TypeError: greet() got multiple values for argument 'name'
# 올바름: 언패킹을 통해서만 전달
print(greet(**data))
# Hi, Alice!
# 또는: 중복 키 제거
print(greet("Alice", **{"greeting": "Hi"}))
# Hi, Alice!예시: 안전하게 kwargs 수정하기
def process(name, **kwargs):
# 잘못됨: kwargs를 직접 수정하면 호출자의 딕셔너리에 영향
# kwargs["processed"] = True
# 올바름: 새 딕셔너리 생성
config = {**kwargs, "processed": True}
return {"name": name, **config}
settings = {"timeout": 30, "retries": 3}
result = process("task1", **settings)
print(result)
# {'name': 'task1', 'timeout': 30, 'retries': 3, 'processed': True}
# 원본 딕셔너리는 변경되지 않음
print(settings)
# {'timeout': 30, 'retries': 3}*args/**kwargs vs 다른 접근 방식
언제 *args와 **kwargs 대신 대안을 사용해야 할까요? 다음은 비교 표입니다:
| 접근 방식 | 구문 | 가장 적합한 경우 | 단점 |
|---|---|---|---|
*args | def f(*args) | 동일 타입의 위치 값 개수를 알 수 없을 때 | 인자별 타입 힌트 없음, 명명된 접근 불가 |
**kwargs | def f(**kwargs) | 유연한 옵션, 다른 함수로 전달 | IDE 자동 완성 없음, 정적 타입 검사 없음 |
| 명시적 매개변수 | def f(a, b, c) | 알려지고 고정된 인자 집합 | 경직됨; 매개변수 추가 시 기존 호출 깨짐 |
| 기본 매개변수 | def f(a, b=10) | 합리적인 기본값이 있는 선택적 매개변수 | 여전히 모든 옵션을 미리 알아야 함 |
| 리스트/튜플 매개변수 | def f(items: list) | 정렬된 값 컬렉션 | 호출자가 명시적으로 리스트 구성 필요 |
| 딕셔너리 매개변수 | def f(options: dict) | 구조화된 설정 | 호출자가 명시적으로 딕셔너리 구성 필요 |
| TypedDict/dataclass | def f(config: Config) | 구조화되고 타입 안전한 설정 | 더 많은 상용구, 클래스 정의 필요 |
일반적인 지침:
- 인자 집합이 알려지고 안정적일 때는 명시적 매개변수를 사용하세요.
- 함수가 자연스럽게 가변 개수의 동일 타입 값에 대해 작동할 때는 **
*args**를 사용하세요(print(),max(),min()처럼). - 다른 함수로 옵션을 전달하거나, 유연한 설정을 받아들이거나, 확장 가능한 API를 구축할 때는 **
**kwargs**를 사용하세요. - 데코레이터, 프록시 함수, 또는 모든 인자 전달이 필요한 클래스 계층 구조를 작성할 때는 둘 다 사용하세요.
- 구조화된 설정에 IDE 자동 완성과 정적 타입 검사를 원할 때는 TypedDict 또는 dataclass를 사용하세요.
*args와 **kwargs에 타입 힌트 추가하기
Python 3.11+에서는 Unpack과 TypedDict를 사용하여 *args와 **kwargs에 타입 힌트를 추가할 수 있습니다:
# 기본 타입 힌트 (모든 인자 동일 타입)
def add_numbers(*args: float) -> float:
return sum(args)
def set_options(**kwargs: str) -> dict[str, str]:
return kwargs
# Python 3.11+: TypedDict로 정밀한 kwargs 타이핑
from typing import TypedDict, Unpack
class ConnectionOptions(TypedDict, total=False):
timeout: int
retries: int
ssl: bool
def connect(host: str, port: int, **kwargs: Unpack[ConnectionOptions]) -> dict:
return {"host": host, "port": port, **kwargs}
# 이제 IDE는 유효한 키워드 인자를 알고 있음
result = connect("localhost", 5432, timeout=30, ssl=True)
print(result)
# {'host': 'localhost', 'port': 5432, 'timeout': 30, 'ssl': True}자주 묻는 질문
*args와 **kwargs는 무엇의 약자인가요?
args와 kwargs라는 이름은 각각 "arguments"와 "keyword arguments"의 약자입니다. 이들은 순전히 관례적인 이름입니다. 실제 마법은 *와 ** 접두사 연산자에서 옵니다. 단일 별표 *는 Python에게 추가 위치 인자를 튜플로 패킹하라고 알려주고, 이중 별표 **는 추가 키워드 인자를 딕셔너리로 패킹합니다. 이 특정 이름들은 거의 모든 Python 코드베이스, 튜토리얼, 라이브러리에서 볼 수 있으며 모든 Python 개발자가 즉시 인식하기 때문에 대부분의 경우 이러한 이름을 사용하는 것이 강력히 권장되지만, 언어가 이러한 특정 이름을 요구하는 것은 아닙니다.
args와 kwargs가 아닌 다른 이름을 사용할 수 있나요?
네. args와 kwargs라는 이름은 관례이며 구문 요건은 아닙니다. 언패킹 동작은 전적으로 *와 ** 접두사에서 옵니다. *values, *items, *numbers, **options, **config, **params 또는 기타 유효한 Python 식별자를 작성할 수 있습니다. 그러나 모든 Python 개발자가 즉시 인식하기 때문에 *args와 **kwargs를 사용하는 것이 대부분의 경우 강력히 권장됩니다. 파일 처리 함수에서 *paths나 HTTP 클라이언트에서 **headers처럼 더 설명적인 이름이 가독성을 실질적으로 향상시킬 때만 사용자 정의 이름을 사용하세요.
Python 함수에서 매개변수의 올바른 순서는 무엇인가요?
Python은 엄격한 순서를 강제합니다: 먼저 일반 위치 매개변수가 오고, 그 다음 *args, 그 다음 키워드 전용 매개변수(기본값 포함), 마지막으로 **kwargs가 옵니다. 전체 순서는 def func(pos1, pos2, default1=val, *args, kw_only1, kw_only2=val, **kwargs)입니다. 이 순서를 위반하면 SyntaxError가 발생합니다. "위치, 스타-인자, 키워드 전용, 더블-스타-kwargs"라는 기억법이 유용합니다. 별표 개수가 왼쪽에서 오른쪽으로 증가합니다.
*args와 리스트 매개변수 중 언제 무엇을 사용해야 하나요?
각 인자가 별개의 독립적인 값이고 호출자가 컨테이너를 구성하지 않고 자연스럽게 전달해야 할 때 *args를 사용하세요. print("a", "b", "c")가 print(["a", "b", "c"])보다 더 자연스럽습니다. 값들이 논리적으로 호출자가 이미 변수에 가지고 있는 컬렉션을 형성하거나, 컬렉션과 다른 매개변수를 구분해야 할 때 리스트 매개변수를 사용하세요. max(), min(), print()와 같은 내장 함수는 호출 규칙이 자연스럽게 느껴지기 때문에 *args를 사용하고, sorted(iterable)와 같은 함수는 입력이 본질적으로 시퀀스이기 때문에 단일 반복 가능 객체를 받습니다.
*args와 **kwargs는 느린가요?
*args와 **kwargs의 오버헤드는 최소합니다. Python은 각 호출마다 *args에 대해 튜플을, **kwargs에 대해 딕셔너리를 생성하며, 이는 작은 메모리 할당을 포함합니다. 벤치마크에서 명시적 매개변수와 비교한 차이는 일반적으로 호출당 수백 나노초입니다. 거의 모든 실제 코드에서 무시할 수 있는 수준입니다. 이 오버헤드가 측정 가능해지려면 수백만 번의 호출이 빡빡한 루프에서 이루어져야 합니다. *args/**kwargs를 피하기보다는 알고리즘 효율성과 I/O 최적화에 집중하세요. 제공하는 유연성과 코드 유지보수성이 미세한 성능 비용보다 훨씬 큽니다.
결론
Python의 *args와 **kwargs는 언어에서 가장 실용적인 기능 중 두 가지입니다. 이들은 근본적인 문제를 해결합니다: 가독성을 희생하지 않고도 가변 개수의 인자를 받아들일 수 있는 함수를 작성하는 방법.
핵심 요점:
- **
*args**는 추가 위치 인자를 튜플로 수집합니다. 함수가 임의 개수의 값을 받아들여야 할 때 사용하세요. - **
**kwargs**는 추가 키워드 인자를 딕셔너리로 수집합니다. 유연한 옵션, 설정 전달, 확장 가능한 API에 사용하세요. - 매개변수 순서는 항상: 일반,
*args, 키워드 전용,**kwargs입니다. - 언패킹은
*와**가 함수 호출, 리스트 구성, 딕셔너리 병합에서 작동합니다. - 데코레이터는 가장 중요한 실전 사용 사례입니다. 어떤 함수의 시그니처와 관계없이 래핑할 수 있도록
*args와**kwargs에 의존합니다.
알려지고 안정적인 시그니처를 가진 함수에는 명시적 매개변수부터 시작하세요. 유연성이 필요할 때 *args와 **kwargs를 사용하세요: 데코레이터, 서브클래스 전달, 래퍼 함수, API 빌더. 일단 패킹과 언패킹 메커니즘을 내면화하면 더 깔끔하고 재사용 가능한 Python 코드를 작성하게 될 것입니다.