0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python】可変長引数とは(*args,**kwargs)

Last updated at Posted at 2024-12-01

引数とは

まずは基本のおさらいから。
引数とは関数に対して渡す値のことです。
一般的には下記の例の通り関数側で引数の数が定められていることがほとんどです。
例の場合はarg1arg2の2つですね。

def func(arg1,arg2) :
    print(arg1)
    print(arg2)

func("引数1","引数2")
.実行結果
引数1
引数2

このfunc()を呼び出す際、引数の数に過不足があるとTypeErrorが発報されます。

エラーメッセージ

.引数不足 (1つしか指定していない)
TypeError: func() missing 1 required positional argument: 'arg2'
.引数過多 (3つ指定した)
TypeError: func() takes 2 positional arguments but 3 were given

可変長引数とは

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

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

func("3-1","3-2","3-3")の挙動
image.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つにデコレータという機能があります。
一般的には@による簡略記法が使われますが、今回はわかりやすいように簡略せずに記載します。

以下に実装するデコレータは関数実行前後に開始・終了宣言をコンソールに出力する機能を持たせたものです。

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

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

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

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

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

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

キーワード引数の考慮

引数を3つ指定するfunc3()を新設しました。
arg2arg3に関してはデフォルト値が設定されているため省略が可能です。
この関数を呼び出す際にarg2はデフォルト値を使用したいので指定を省略してarg3だけ設定して実行してみましょう。

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

この関数を先ほど作成したデコレータを介して実行してみます。

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
というように分掌されているわけです。

-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

重要なのは パック と アンパック

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

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

上記URLでは以下のような使用方法が記載されていますが、こちらは今まで紹介した可変長引数とは少し毛色が違っており、アンパックの機能を使用するために*演算子を使用しています。

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]

この例でも引数として使用してはいますが、パックしたものをアンパックしているわけではないため可変長引数としての利用はしていません。

他にも*演算子によるパックを以下のように使用することもできます。

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]

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

まとめ

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

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

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

最後に

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

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?