引数とは
まずは基本のおさらいから。
引数とは関数に対して渡す値のことです。
一般的には下記の例の通り関数側で引数の数が定められていることがほとんどです。
例の場合はarg1
とarg2
の2つですね。
def func(arg1,arg2) :
print(arg1)
print(arg2)
func("引数1","引数2")
引数1
引数2
このfunc()
を呼び出す際、引数の数に過不足があるとTypeError
が発報されます。
エラーメッセージ
TypeError: func() missing 1 required positional argument: 'arg2'
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回を呼び出しており、いずれも引数の数が違いますがエラーにはならず正しく処理が行えています。
出力結果を見るとわかる通り、関数側では引数をタプルにして受け取っているので引数が増えてもタプルの要素数が増えるだけでエラーにはならないという仕組みです。
このように引数を タプルなどに集約する工程を パック といいます。
可変長引数の用途
これだけ聞くとわざわざ可変長引数なんて機能を用意する意味がないのでは?と感じますよね。
以下のように最初からタプルを受け取るような実装にすれば済む話です。
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
を実行する際に与える引数を可変長引数にすることで、いずれのパターンにおいても動的に対応できるようになります。
この流れを踏まえたうえで内部での動きを確認してみましょう。
① デコレータが受け取る第二引数は可変長引数になっているため、先述した通り仮引数の第二引数以降はパックされてタプル型で連携されます。
② decorator
内でfunc2
を実行する際の引数に可変長引数を設定することでタプルの要素を展開し、各要素をfunc2
の実引数に割り当てます。
これをアンパックといいます。
パックとアンパックについては後述しますが重要な処理です。
名前も併せて覚えておきましょう。
このような仕組みを実現するためには可変長引数がなければ実装することはできません。
頭文字に*
をつける可変長引数に関しては上記の通りですが、これだけでは対応できないケースがあります。
キーワード引数の考慮
引数を3つ指定するfunc3()
を新設しました。
arg2
とarg3
に関してはデフォルト値が設定されているため省略が可能です。
この関数を呼び出す際に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
にキーワード引数用の可変長引数を追加することでエラーなく実行することができました。
重要なのは パック と アンパック
今回は主題を可変長引数としているため、*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 |
・*
演算子と**
演算子はあくまでもパック・アンパックを行う演算子
・*
や**
は仮引数に設定された場合と実引数に設定された場合で挙動が真逆になる
実引数に指定された場合 → 仮引数をパック
仮引数に設定された場合 → 実引数にアンパック
最後に
可変長引数について今回はデコレータを例に挙げて解説しましたが、ほかにも関数オブジェクトを引数として受け取る処理とセットで利用されていることが多いです。
本質はパック・アンパックの機能を元に成り立っている機能であるということをご認識いただければと思います。