Pythonの可変長引数(*args, **kwargs)を理解する

Pythonの*argsと**kwargsについて解説します。

Pythonの可変長引数(*args, **kwargs)を理解する

可変長引数とは?

Pythonの可変長引数とは、長さの決まっていない引数のことです。通常、関数の定義の際にいくつ引数を渡すのかを決めますが、可変長引数を指定すると、関数の実行時に任意の長さの引数を渡すことができるようになります。

Pythonにおいては、関数の定義で引数の頭に*もしくは**をつけることで、可変長引数(任意の数の引数)を指定することができます。Pythonにおいては慣例として*args**kwargsという引数名が使われますが、アスタリスクが頭についていれば別の引数名でも大丈夫です。

アスタリスクを1つ(*)つけた可変長引数は、任意の数のキーワードなしの引数をタプルで受け取ります

アスタリスクを2つ(**)つけた可変長引数は、任意の数のキーワード付きの引数を辞書型で受け取ります

要するに、*args**kwargsの違いは、任意の長さの引数それぞれに名前(キーと値のセット)がついているかどうかという点になります。それぞれ解説します。

任意の数の引数をタプルとして受け取る(*args)

Pythonの関数の引数名の頭に*(アスタリスク)を1つ付けることで、任意の数の引数を受け取ることができるようになります。それぞれの引数はキーがついておらず、関数の内部的にはタプルとして扱われます。

# *argsは任意の数の引数をタプルとして受け取る

def test_stats(*args):
    top = max(args)
    bottom = min(args)
    average = int(sum(args) / len(args))
    return f"top: {top}, bottom: {bottom}, average: {average}"

# 任意の数の引数を取ることができる
print(test_stats(25, 44, 89, 56, 73, 11))
# 'top: 89, bottom: 11, average: 49'


# 複数の引数は関数内でタプルとして扱われる
def test_function(*args):
    print(f"args: {args}")
    print(f"type: {type(args)}")

test_function("this", "is", "test", "function")
# args: ('this', 'is', 'test', 'function')
# type: <class 'tuple'>

通常の関数の引数(位置引数)といっしょに使うこともできます。位置引数を指定した後に、同様に*argsの形で引数を定義します。

関数の実行時に位置引数しか指定しない場合には、argsは空のタプルになります。

# 位置引数と*argsを組み合わせる

# 基本料金とチップと食べた料理の料金を計算
def calc_bill(basic_charge, tips, *args):
    return basic_charge + tips + sum(args)

print(calc_bill(500, 600, 350, 450, 800))
# 2700

# *argsに何も指定しない場合は空のタプルになる
print(calc_bill(500, 600))
# 1100

任意の数の引数を辞書として受け取る(**kwargs)

Pythonの関数の引数名の頭に**(アスタリスク2つ)を付けることで、任意の数のキーワード引数を受け取ることができるようになります。関数の内部的には辞書として扱われます。

こちらも位置引数とセットで使用することができ、位置引数は通常の変数、**kwargs部分は辞書として扱われます。

# *kwargsは任意の数のキーワード引数をタプルとして受け取る

# キーと値を指定して引数を取ることができる
def kwargs_function(**kwargs):
    print(f"kwargs: {kwargs}")
    print(f"type: {type(kwargs)}")

kwargs_function(key1=100, key2="key2 value", key3=["This", "is", "list"])
# kwargs: {'key1': 100, 'key2': 'key2 value', 'key3': ['This', 'is', 'list']}
# type: <class 'dict'>



def kwargs_function_with_vars(var1, var2, **kwargs):
    print(f"var1: {var1}")
    print(f"var2: {var2}")
    print(f"kwargs: {kwargs}")


kwargs_function_with_vars(1, 2, key1="test")
# var1: 1
# var2: 2
# kwargs: {'key1': 'test'}

**kwargsを引数として定義して、関数内部で辞書として扱われるといっても、辞書をそのまま引数に取ることはできません。

その代わり、関数の実行時に辞書オブジェクトの頭に**(アスタリスク2つ)を付けると、それぞれのキーと値を展開して引数として渡すことができ丸。

# 辞書オブジェクトを展開して引数に渡す

def kwargs_function(**kwargs):
    print(f"kwargs: {kwargs}")
    print(f"type: {type(kwargs)}")


key_values = {'key1': 100, 'key2': 'test', 'key3': [1, 2, 3]}

# 辞書型オブジェクトをそのまま引数に渡せない
kwargs_function(key_values)
# TypeError: kwargs_function() takes 0 positional arguments but 1 was given


# **をつけることでキーと値を展開して渡すことができる
kwargs_function(**key_values)
# kwargs: {'key1': 100, 'key2': 'test', 'key3': [1, 2, 3]}
# type: <class 'dict'>