Python *args 與 **kwargs

*args**kwargs 讓函數可以接受不定數量的參數,增加函數的彈性。

*args - 可變數量位置參數

*args 會將所有額外的位置參數收集成一個 tuple:

def add(*args):
    print(f"args 的型別: {type(args)}")
    print(f"args 的內容: {args}")
    return sum(args)

print(add(1, 2))           # 3
print(add(1, 2, 3, 4, 5))  # 15
print(add())               # 0

輸出:

args 的型別: <class 'tuple'>
args 的內容: (1, 2)
3
args 的型別: <class 'tuple'>
args 的內容: (1, 2, 3, 4, 5)
15
args 的型別: <class 'tuple'>
args 的內容: ()
0

搭配一般參數

def greet(greeting, *names):
    for name in names:
        print(f"{greeting}, {name}!")

greet("Hello", "Alice", "Bob", "Charlie")

輸出:

Hello, Alice!
Hello, Bob!
Hello, Charlie!
*args 必須放在一般位置參數之後。

**kwargs - 可變數量關鍵字參數

**kwargs 會將所有額外的關鍵字參數收集成一個 dict:

def print_info(**kwargs):
    print(f"kwargs 的型別: {type(kwargs)}")
    print(f"kwargs 的內容: {kwargs}")
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25, city="Taipei")

輸出:

kwargs 的型別: <class 'dict'>
kwargs 的內容: {'name': 'Alice', 'age': 25, 'city': 'Taipei'}
name: Alice
age: 25
city: Taipei

搭配一般參數

def create_user(username, **details):
    user = {"username": username}
    user.update(details)
    return user

user = create_user("alice", email="alice@example.com", age=25)
print(user)
# {'username': 'alice', 'email': 'alice@example.com', 'age': 25}

同時使用 *args 和 **kwargs

參數順序:一般參數 → *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, x=10, y=20)

輸出:

a: 1
b: 2
args: (3, 4, 5)
option: False
kwargs: {'x': 10, 'y': 20}

解包 (Unpacking)

用 * 解包序列

def add(a, b, c):
    return a + b + c

numbers = [1, 2, 3]
print(add(*numbers))  # 6

# 等同於
print(add(1, 2, 3))   # 6

用 ** 解包字典

def greet(name, greeting):
    print(f"{greeting}, {name}!")

data = {"name": "Alice", "greeting": "Hello"}
greet(**data)  # Hello, Alice!

# 等同於
greet(name="Alice", greeting="Hello")

合併序列和字典

# 合併 list
list1 = [1, 2, 3]
list2 = [4, 5, 6]
merged = [*list1, *list2]
print(merged)  # [1, 2, 3, 4, 5, 6]

# 合併 dict
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = {**dict1, **dict2}
print(merged)  # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

實際應用

包裝函數 (Wrapper)

def log_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"Result: {result}")
        return result
    return wrapper

@log_call
def add(a, b):
    return a + b

add(3, 5)

輸出:

Calling add with args=(3, 5), kwargs={}
Result: 8

傳遞參數給父類別

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class Dog(Animal):
    def __init__(self, breed, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.breed = breed

dog = Dog("Labrador", name="Buddy", age=3)
print(f"{dog.name} is a {dog.breed}")  # Buddy is a Labrador

建立彈性的 API

def make_request(url, method="GET", **options):
    print(f"{method} {url}")
    for key, value in options.items():
        print(f"  {key}: {value}")

make_request(
    "https://api.example.com/users",
    method="POST",
    headers={"Content-Type": "application/json"},
    timeout=30,
    data={"name": "Alice"}
)

建立設定物件

def create_config(required_setting, **optional_settings):
    config = {
        "required": required_setting,
        "debug": False,
        "timeout": 30,
        "retries": 3
    }
    config.update(optional_settings)
    return config

config = create_config("my_app", debug=True, timeout=60)
print(config)
# {'required': 'my_app', 'debug': True, 'timeout': 60, 'retries': 3}

命名慣例

argskwargs 只是慣例名稱,你可以用任何名稱,但使用這些名稱可以讓程式碼更容易理解:

# 這樣也可以,但不建議
def func(*numbers, **options):
    pass

# 建議使用標準名稱
def func(*args, **kwargs):
    pass