関数の可変長引数
Python では関数の引数を可変長引数として定義することができます。
多くの場合、 *args 、**kwargs と表記して説明されますが、引数名にアスタリスク(*) をひとつ、もしくは2つ付加することを表したもので、文法として args や kwargs でなければいけないとことではなく、慣例として使用されているだけです。
*args: 複数の引数をタプルとして受け取る
**kwargs: 複数のキーワード引数を辞書として受け取る
*args を使って任意個数の引数を受け取る
例えば次の例を見てみましょう。
code: 0104_fixed_args.py
def mysum(arg1, arg2, arg3):
print('SUM:', arg1 + arg2 + arg3)
mysum(1,2,3)
mysum(1,2,3,4,5) # エラーになる
ここで定義している関数 mysum() は3つの引数をとります。
初回の呼び出しは正常に処理されますが、5つ引数を与えた2度目の呼び出しではエラーになってしまいます。
code: bash
$ python 0104_fixed_args.py
SUM: 6
Traceback (most recent call last):
File "sample_args1.py", line 5, in <module>
mysum(1,2,3,4,5)
TypeError: mysum() takes 3 positional arguments but 5 were given
この mysum() を任意の数だけ引数を受け取れるようにするためには、
次のように記述します。
code: 0105_variable_args.py
def mysum(arg1, arg2, *args):
total = 0
for n in args:
total += n
print('SUM:', total)
mysum(1,2)
mysum(1,2,3)
mysum(1,2,3,4,5) # エラーにならない
問:
Q1: このサンプルの関数mysum()の不自然な点に気づきましたか?
Q2: どうすれば本来に通りに動作するでしょうか?
位置引数(positional argument) だけしか指定されなかった場合は、*argsには空のタプルが渡されます。
この例にあるように *args を位置引数の最後に書くことが多いのですが、次のようにすることもできます。ただし、この場合はarg1とarg2をキーワード引数とする必要があります。
code: 0106_variable_args_with_kwargs.py
def mysum(*args, arg1, arg2):
total = 0
for n in args:
total += n
print('SUM:', total)
mysum(3,arg1=1,arg2=2)
mysum(3,4,5,arg1=1,arg2=2)
*args で可変長引数を受け取る場合は、キーワード引数を与えることができません。
このため、関数呼び出しでキーワード引数(keyword argument) のあとに位置引数を指定することができないため、可読性が悪くなってしまいます。
また、*args の可変引数だけあるような場合では、位置引数は特に記述する必要はないのですが、プログラムの可読性を考えると、明らかに必要になる位置引数は内容を示す変数名で受けたほうが、当然プログラムは読みやすくなります。
**kwargs を使って任意個数の引数を受け取る
可変長引数にキーワード引数を与えたいときは、**kwargs を使います。
次の例を見てみましょう。
code: 0107_kwargs.py
def info(**kwargs):
print("\nData type of argument:",type(kwargs))
for key, value in kwargs.items():
print(f'{key} is {value}')
info(Firstname="Freddie", Lastname="Mercury", Age=45, Phone=1234567890)
info(Firstname="Brian", Lastname="May",
Midlename="Harold", Country="England", Age=75, Phone=9876543210)
2重アスタリスク(**)を変数の前に付加して定義すると、任意個数のキーワード引数を辞書として受け取ることができます。
キーワード引数を強制させる
code: python
def func(a, b, c='1', d='2', e='3'):
return 'Hello'
このコードでは、最低2つ引数を与えると動作します。また、3番目以降の引数は、キーワード引数で与えなくてもかまいません。
code: python
...: def func(a, b, c='1', d='2', e='3'):
...: return 'Hello'
...:
...:
これを次のようにアスタリスク(*) を置いておくと、それ以降の引数はキーワード引数として与えないとエラーになります。
code: python
def func(a, b, *, c='1', d='2', e='3'):
return 'Hello'
code: python
...: def func(a, b, *, c='1', d='2', e='3'):
...: return 'Hello'
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-9a77683b4345> in <module>
----> 1 func(1,2,3,4,5)
TypeError: func() takes 2 positional arguments but 5 were given
In 5: func(1,2,c=3,d=4,e=5) このときのアスタリスク自体は意味を持たないのですが、キーワード引数を強制さる機能を持ちます。
位置引数を強制する
Python3.8 からスラッシュ(/) で位置引数を強制できるようになりました。
スラッシュより前に記述した変数は位置引数としてしか与えることができません。
code: python
...: def func(a, /, b, c='1', d='2', e='3'):
...: return 'Hello'
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-5-5a1f328a80f7> in <module>
----> 1 func(a=1, b=2)
TypeError: func() got some positional-only arguments passed as keyword arguments: 'a'
*args や **kwargs などの可変引数を使うと関数定義の自由度が増します。