Skip to content

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**는 추가 키워드 인자를 딕셔너리로 모읍니다.

argskwargs라는 이름은 관례이며 필수는 아닙니다. 마법은 이름 자체가 아니라 *** 접두사에서 옵니다. *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은 함수 시그니처에서 다음과 같은 정확한 순서를 강제합니다:

  1. 일반 위치 매개변수
  2. *args (가변 위치)
  3. 키워드 전용 매개변수 (*args 이후)
  4. **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위치parama, b필수, 위치로 채워짐
2nd기본값param=valuec=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 대신 대안을 사용해야 할까요? 다음은 비교 표입니다:

접근 방식구문가장 적합한 경우단점
*argsdef f(*args)동일 타입의 위치 값 개수를 알 수 없을 때인자별 타입 힌트 없음, 명명된 접근 불가
**kwargsdef f(**kwargs)유연한 옵션, 다른 함수로 전달IDE 자동 완성 없음, 정적 타입 검사 없음
명시적 매개변수def f(a, b, c)알려지고 고정된 인자 집합경직됨; 매개변수 추가 시 기존 호출 깨짐
기본 매개변수def f(a, b=10)합리적인 기본값이 있는 선택적 매개변수여전히 모든 옵션을 미리 알아야 함
리스트/튜플 매개변수def f(items: list)정렬된 값 컬렉션호출자가 명시적으로 리스트 구성 필요
딕셔너리 매개변수def f(options: dict)구조화된 설정호출자가 명시적으로 딕셔너리 구성 필요
TypedDict/dataclassdef f(config: Config)구조화되고 타입 안전한 설정더 많은 상용구, 클래스 정의 필요

일반적인 지침:

  • 인자 집합이 알려지고 안정적일 때는 명시적 매개변수를 사용하세요.
  • 함수가 자연스럽게 가변 개수의 동일 타입 값에 대해 작동할 때는 ***args**를 사용하세요(print(), max(), min()처럼).
  • 다른 함수로 옵션을 전달하거나, 유연한 설정을 받아들이거나, 확장 가능한 API를 구축할 때는 ****kwargs**를 사용하세요.
  • 데코레이터, 프록시 함수, 또는 모든 인자 전달이 필요한 클래스 계층 구조를 작성할 때는 둘 다 사용하세요.
  • 구조화된 설정에 IDE 자동 완성과 정적 타입 검사를 원할 때는 TypedDict 또는 dataclass를 사용하세요.

*args와 **kwargs에 타입 힌트 추가하기

Python 3.11+에서는 UnpackTypedDict를 사용하여 *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는 무엇의 약자인가요?

argskwargs라는 이름은 각각 "arguments"와 "keyword arguments"의 약자입니다. 이들은 순전히 관례적인 이름입니다. 실제 마법은 *** 접두사 연산자에서 옵니다. 단일 별표 *는 Python에게 추가 위치 인자를 튜플로 패킹하라고 알려주고, 이중 별표 **는 추가 키워드 인자를 딕셔너리로 패킹합니다. 이 특정 이름들은 거의 모든 Python 코드베이스, 튜토리얼, 라이브러리에서 볼 수 있으며 모든 Python 개발자가 즉시 인식하기 때문에 대부분의 경우 이러한 이름을 사용하는 것이 강력히 권장되지만, 언어가 이러한 특정 이름을 요구하는 것은 아닙니다.

args와 kwargs가 아닌 다른 이름을 사용할 수 있나요?

네. argskwargs라는 이름은 관례이며 구문 요건은 아닙니다. 언패킹 동작은 전적으로 *** 접두사에서 옵니다. *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 코드를 작성하게 될 것입니다.

📚