【Python】可変長引数とは（*args,**kwargs）

# 引数とは
まずは基本のおさらいから。
引数とは関数に対して渡す値のことです。
一般的には下記の例の通り関数側で引数の数が定められていることがほとんどです。
例の場合は`arg1`と`arg2`の2つですね。
```python
def func(arg1,arg2) :
    print(arg1)
    print(arg2)

func("引数1","引数2")
```
```.実行結果
引数1
引数2
```
この`func()`を呼び出す際、引数の数に過不足があると`TypeError`が発報されます。

::: note info
エラーメッセージ
```.引数不足　(1つしか指定していない)
TypeError: func() missing 1 required positional argument: 'arg2'
```
```.引数過多　(3つ指定した)
TypeError: func() takes 2 positional arguments but 3 were given
```
</details>
:::

# 可変長引数とは
先述した一般的な用途とは別に、一部の特殊な関数では引数を可変長で受け取りたい場合があります。
そういった場合に使用されるのが可変長引数です。
可変長引数は引数の先頭を`*`とすることで定義できます。
慣習として`*args`という名称が使用されますが、別の名前でも機能は変わりません。
```python
def func(*args) :
    print("args   :",type(args),args)
    print("")

func("1-1")
func("2-1","2-2")
func("3-1","3-2","3-3")
```
```.実行結果
args   : <class 'tuple'> ('1-1',)

args   : <class 'tuple'> ('2-1', '2-2')

args   : <class 'tuple'> ('3-1', '3-2', '3-3')
```

同じ`func()`を3回を呼び出しており、いずれも引数の数が違いますがエラーにはならず正しく処理が行えています。
出力結果を見るとわかる通り、関数側では引数をタプルにして受け取っているので引数が増えてもタプルの要素数が増えるだけでエラーにはならないという仕組みです。
このように引数を タプルなどに集約する工程を **パック** といいます。

:::note info
`func("3-1","3-2","3-3")`の挙動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3920588/46f1c4d8-73e7-9e8c-fd1e-44f10a4a0a31.png)
:::



# 可変長引数の用途
これだけ聞くとわざわざ可変長引数なんて機能を用意する意味がないのでは？と感じますよね。
以下のように最初からタプルを受け取るような実装にすれば済む話です。
```func.py
def func(args:tuple):
    print("args   :",type(args),args)
    print("")

func(("1-1"))
func(("2-1","2-2"))
func(("3-1","3-2","3-3"))
```
この疑問を解決するには可変長引数がどのような時に使用されるかを理解する必要があります。

よく使用される用途の1つにデコレータという機能があります。
一般的には`@`による簡略記法が使われますが、今回はわかりやすいように簡略せずに記載します。

以下に実装するデコレータは関数実行前後に開始・終了宣言をコンソールに出力する機能を持たせたものです。
```python
def decorator(fn,*args):
    """関数実行前後に開始宣言・終了宣言を行うデコレータ

    Args:
        fn (function): 実行したい関数
        *args: 実行したい関数に渡す引数
    """
    print(f"【{fn.__name__}】 start")
    # 関数を実行
    fn(*args)
    print(f"【{fn.__name__}】 end\n")

def func1(arg1):
    print("func1_arg1   :",type(arg1),arg1)

def func2(arg1,arg2):
    print("func2_arg1   :",type(arg1),arg1)
    print("func2_arg2   :",type(arg2),arg2)

# デコレータ呼び出し
decorator(func1,"1-1")
decorator(func2,"2-1","2-2")
```
```.実行結果
【func1】 start
func1_arg1   : <class 'str'> 1-1
【func1】 end

【func2】 start
func2_arg1   : <class 'str'> 2-1
func2_arg2   : <class 'str'> 2-2
【func2】 end
```

ここで見てもらいたいのは`decorator()`です。
`decorator()`は関数オブジェクト`fn`を受け取っており、内部で`fn`を実行する必要があります。
ここで受け取っている`fn`の引数は1つかもしれないし、2つかもしれない。あるいは引数なしかもしれませんよね。
この`fn`を実行する際に与える引数を可変長引数にすることで、いずれのパターンにおいても動的に対応できるようになります。
この流れを踏まえたうえで内部での動きを確認してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3920588/6f457884-e1d1-0c42-bcc9-963eef729f10.png)

①　デコレータが受け取る第二引数は**可変長引数**になっているため、先述した通り仮引数の第二引数以降は**パック**されてタプル型で連携されます。

②　`decorator`内で`func2`を実行する際の引数に**可変長引数**を設定することでタプルの要素を展開し、各要素を`func2`の実引数に割り当てます。
   これを**アンパック**といいます。

:::note info
**パック**と**アンパック**については後述しますが重要な処理です。
名前も併せて覚えておきましょう。
:::

このような仕組みを実現するためには可変長引数がなければ実装することはできません。

頭文字に`*`をつける可変長引数に関しては上記の通りですが、これだけでは対応できないケースがあります。

# キーワード引数の考慮
引数を3つ指定する`func3()`を新設しました。
`arg2`と`arg3`に関してはデフォルト値が設定されているため省略が可能です。
この関数を呼び出す際に`arg2`はデフォルト値を使用したいので指定を省略して`arg3`だけ設定して実行してみましょう。
```python
def func3(arg1,arg2="default 3-2",arg3="default 3-3"):
    print("func3_arg1   :",type(arg1),arg1)
    print("func3_arg2   :",type(arg2),arg2)
    print("func3_arg3   :",type(arg3),arg3)

func3("3-1",arg3="3-3")
```
```.実行結果
func3_arg1   : <class 'str'> 3-1
func3_arg2   : <class 'str'> default 3-2
func3_arg3   : <class 'str'> 3-3
```
この関数を先ほど作成したデコレータを介して実行してみます。

```diff_python
def decorator(fn,*args):
    print(f"【{fn.__name__}】 start")
    # 関数を実行
    fn(*args)
    print(f"【{fn.__name__}】 end\n")

def func1(arg1):
    print("func1_arg1   :",type(arg1),arg1)

def func2(arg1,arg2):
    print("func2_arg1   :",type(arg1),arg1)
    print("func2_arg2   :",type(arg2),arg2)

+def func3(arg1,arg2="default 3-2",arg3="default 3-3"):
+    print("func3_arg1   :",type(arg1),arg1)
+    print("func3_arg2   :",type(arg2),arg2)
+    print("func3_arg3   :",type(arg3),arg3)

decorator(func1,"1-1")
decorator(func2,"2-1","2-2")
+decorator(func3,"3-1",arg3="3-3")
```
```.実行結果
TypeError: decorator() got an unexpected keyword argument 'arg3'
```
エラーになりました。
実は`*args`は位置引数にしか対応しておらず、キーワード変数には対応していないことが原因です。
タプルの要素には`arg3="3-3"`のような形式でデータ格納できませんから、キーワード変数に対しては別の対応策が必要です。

ここで登場するのが先頭に`**`をつけることで定義できる可変長引数です。
こちらも慣習では`**kwargs`という名称が使用されますが、別の名前でも機能します。

つまり可変長引数は
　位置引数　　　　：　*args
 　キーワード引数　：　**kwargs
というように分掌されているわけです。

```diff_python
-def decorator(fn,*args):
+def decorator(fn,*args,**kwargs):
    print(f"【{fn.__name__}】 start")
    # 関数を実行
-   fn(*args)
+   fn(*args,**kwargs)
    print(f"【{fn.__name__}】 end\n")

def func1(arg1):
    print("func1_arg1   :",type(arg1),arg1)

def func2(arg1,arg2):
    print("func2_arg1   :",type(arg1),arg1)
    print("func2_arg2   :",type(arg2),arg2)

def func3(arg1,arg2="default 3-2",arg3="default 3-3"):
    print("func3_arg1   :",type(arg1),arg1)
    print("func3_arg2   :",type(arg2),arg2)
    print("func3_arg3   :",type(arg3),arg3)

decorator(func1,"1-1")
decorator(func2,"2-1","2-2")
decorator(func3,"3-1",arg3="3-3")
```

```.実行結果
【func1】 start
func1_arg1   : <class 'str'> 1-1
【func1】 end

【func2】 start
func2_arg1   : <class 'str'> 2-1
func2_arg2   : <class 'str'> 2-2
【func2】 end

【func3】 start
func3_arg1   : <class 'str'> 3-1
func3_arg2   : <class 'str'> default 3-2
func3_arg3   : <class 'str'> 3-3
【func3】 end
```

`decorator`にキーワード引数用の可変長引数を追加することでエラーなく実行することができました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3920588/6ba3e269-0f38-0743-ac86-032a14ad89ec.png)

# 重要なのは パック と アンパック
今回は主題を可変長引数としているため、`*args`と`**kwargs`に焦点を当てた解説を行いました。
そのため一部誤解が生じている可能性があるため、少しだけ細かい話をさせてください。

ここまで読んでいただいた方は「先頭に`*`や`**`がついている場合は可変長引数である。」と認識されたかもしれませんが、正しくは違います。
`*`と`**`は**パック、アンパックを行う演算子**であると認識してください。
Pythonの公式リファレンスにもそのような記載があります。

https://docs.python.org/ja/3.13/tutorial/controlflow.html#unpacking-argument-lists

上記URLでは以下のような使用方法が記載されていますが、こちらは今まで紹介した可変長引数とは少し毛色が違っており、アンパックの機能を使用するために`*`演算子を使用しています。
```python
list(range(3, 6))            # normal call with separate arguments
# [3, 4, 5]

args = [3, 6]
list(range(*args))            # call with arguments unpacked from a list
# [3, 4, 5]
```
この例でも引数として使用してはいますが、パックしたものをアンパックしているわけではないため可変長引数としての利用はしていません。

他にも`*`演算子によるパックを以下のように使用することもできます。
```python
l = [1, 2, 3, 4, 5]
# 第三要素以降をパックしてcに格納
a, b, *c = l
print(type(a),a)
print(type(b),b)
print(type(c),c)
```
```.実行結果
<class 'int'> 1
<class 'int'> 2
<class 'list'> [3, 4, 5]
```

つまりは可変長引数の機能を実現するために**パック**、**アンパック**が利用されていると考えるのが正しい解釈といえます。

# まとめ
```python
def func(*args,**kwargs):
    print("args   :",type(args),args)
    print("kwargs :",type(kwargs),kwargs)

func("引数1","引数2",arg3="引数3",arg4="引数4")
```
```.実行結果
args   : <class 'tuple'> ('引数1', '引数2')
kwargs : <class 'dict'> {'arg3': '引数3', 'arg4': '引数4'}
```

・可変長引数は **位置引数用** と **キーワード引数用** の2種類

|  | 位置引数 | キーワード引数 |
|:-:|:-:|:-:|
| **変数名先頭**  | * | ** |
| **変数名慣習** | *args | **kwargs |
| **変数型** | tuple | dict |

・`*`演算子と`**`演算子はあくまでもパック・アンパックを行う演算子

・`*`や`**`は**仮引数に設定された場合**と**実引数に設定された場合**で挙動が真逆になる
　実引数に指定された場合　→　仮引数をパック
　仮引数に設定された場合　→　実引数にアンパック

# 最後に
可変長引数について今回はデコレータを例に挙げて解説しましたが、ほかにも**関数オブジェクトを引数として受け取る処理**とセットで利用されていることが多いです。

本質はパック・アンパックの機能を元に成り立っている機能であるということをご認識いただければと思います。