先ほどのprint関数のパラメーターリストに出てきた「*objects」について「可変長引数を受け取るためのパラメーター」と述べた。可変長引数とは、「0個以上の引数」のこと。先ほどの例を再掲しよう。
print('some', 'value')
print関数は「0個以上の位置引数」を第1パラメーターのobjectsに受け取る(実は他のパラメーターは位置引数として指定することはできず、キーワード引数として実引数を渡すしかない。その理由は後述)。このように、関数呼び出しごとに、その数が異なる引数のことを「可変長引数」と呼ぶ。
第7回の「文字列の書式指定」で紹介した文字列のformatメソッドも同様だ。例を以下に示す。
'{0} + {1} = {2}'.format(1, 2, 1+2)
このメソッドも呼び出し時に引数の数が決まる。このように、実際に呼び出すときにならないと、引数が何個になるかが分からない関数やメソッドが必要となることがある。そこで任意の数の引数(可変長引数)を受け取るための仕組みが用意されている。Pythonでは可変長引数には次の2種類がある。
「可変長位置引数」については、これまでの例からお分かりだろうが、任意の数の位置引数を好きなだけ受け渡すための機構だ。「可変長キーワード引数」は、関数のパラメーターリストにはないキーワードを使って、「キーワード=その値」形式で関数に渡される引数のことだ。
可変長位置引数を受け取るパラメーターには「*」を前置する。可変長位置引数を受け取るように、myfunc関数を修正してみよう。
def myfunc(param1, param2, *args):
return f'param1: {param1}, param2: {param2}, other: {args}'
このコードでは、第3パラメーターの名前を「param3」から「args」に変更して、その前に「*」を付加している(戻り値は前回同様だ)。幾つかの引数をmyfunc関数に渡してみよう。
print(myfunc(1, 2)) # 引数を2つだけ渡す
print(myfunc(1, 2, 3)) # 引数を3つ渡す
print(myfunc(1, 2, 3, 4)) # 引数を4つ渡す
実行結果は次のようになる。
最初の呼び出しでは引数を2つしか渡していない。だが、可変長引数は「0個以上の任意の数で構成される」ので問題はない。位置引数は並べられた順に、対応するパラメーターへ渡される。そのため、最初の呼び出しでは2つの引数が2つのパラメーターに順に渡される。それ以上の数の引数を渡したときに、余った分を受け取るのが第3パラメーターのargsだ。逆に、argsがないときには、余計な引数を関数に与えることはできないことは覚えておこう。
注目してほしいのは、最初の呼び出しでは「others:」の隣に空のかっこ「()」が表示されていることだ。そして、2つ目の呼び出しでは引数を3つ渡している。3つ目の引数「3」が「余った引数」なので、argsにはこれが渡される。このとき、「others:」の隣にはかっこに囲まれて今度は「(3,)」と表示されている。同様に、3つ目の呼び出しでは、「(3, 4)」という表示になっている。このかっこ表記は何だろう。
実は、可変長位置引数は「タプル」と呼ばれるデータ構造に受け渡されるようになっている。タプルはリストと同様、複数の要素を格納する反復可能オブジェクトだ(詳しい解説は第19回「タプル」を参照のこと)。
タプルはかっこ「()」で囲まれてその要素が表示される。ただし、要素が1つしかないタプルを「(3)」のように表記すると、それがタプルなのか、整数値の「3」をかっこで囲んだだけのものなのかが分からない。そこで、最後にカンマ「,」を付けて、それがタプルだと分かるようにしている。これが2つ目の呼び出し結果に「(3,)」という表記が含まれている理由だ。3つ目の呼び出しでは、2つの引数が余ったので、それらがタプルに格納されて「(3, 4)」と表記されている。
タプルは反復可能なオブジェクトなので、for文で反復処理できる。そこで、先ほどのコードを次のようにしてみよう。
def myfunc(param1, param2, *args):
tmp = f'param1: {param1}, param2: {param2}'
index = 3 # 「paramX」という文字列を作成するのに使う
for item in args: # args(タプル)の各要素を取り出して文字列を作成
tmp += f', param{index}: {item}'
index += 1
return tmp
ここでは、for文を使って、タプルの要素を順次取り出して、それを使って、戻り値の組み立てを行っている。先ほどと同様のコードを試しに実行してみよう。
print(myfunc(1, 2)) # 引数を2つだけ渡す
print(myfunc(1, 2, 3)) # 引数を3つ渡す
print(myfunc(1, 2, 3, 4)) # 引数を4つ渡す
実行結果を以下に示す。
ここでは戻り値となる文字列を作成しただけだが、上のコードと同様な方法でタプルに格納された可変長位置引数をfor文を使って反復的に処理できることが分かったはずだ。
なお、可変長位置引数を受け取るパラメーターよりも右側に記述したパラメーターは「全てキーワード引数しか受け付けない」ようになる。例えば、myfunc関数を次のように修正してみよう。
def myfunc(*args, param1):
return f'param1: {param1}, others: {args}'
このコードは、第1パラメーターのargsが可変長位置引数を受け取るようになっている。この場合、カンマで区切った任意の数の位置引数がargsへ引き渡されるので、第2パラメーターのparam1に位置引数を渡しようがない。そのため、このパラメーターに実引数を渡すにはキーワード引数を使うしかない。print関数の挙動を変更する引数(区切り文字など)をキーワード引数を使って渡すしかないのも、これと同じ理由からだ。
実際には、「*」付きのパラメーターおよびパラメーターリストに単独で記述する「*」の後にあるパラメーターは全てキーワード引数を使って値を渡す必要がある(単独の「*」は以降のパラメーターにはキーワード引数を使って値を渡すことを強制することを指示するものであって、「*」が何かの値を受け取るわけではないことに注意。後述)。
第7回の「置換フィールドに番号や名前を付ける」ではformatメソッドを次のようにして使っていた。
x = 1
y = 100
result = '{x} + {y} = {add_result}'.format(add_result=x+y, x=x, y=y)
print(result)
上の例では、「キーワード=実引数の値」という形式でformatメソッドに引数を与えている。通常のキーワード引数では「キーワードはパラメーター名と一致させる」必要があるが、上のような使い方ではパラメーターの名前とキーワードを一致させることはできない(置換フィールドに付ける名前が、formatメソッドに与えるキーワード引数のキーワードになるので、事前にformatメソッドのパラメーターとしてそれらを決定できない)。
このような場合に使われるのが「可変長キーワード引数」だ。これは任意の数の「キーワード=実引数の値」の組を1つのパラメーターで受け取るための仕組みだ。可変長キーワード引数を受け取るパラメーターには「**」を前置する。以下に例を示す。
def myfunc(param1, **kwargs):
return f'param1: {param1}, others: {kwargs}'
第2パラメーターのkwargsには「**」が前置されている。よって、これは可変長キーワード引数を受け取る。実際の呼び出し例を以下に示す。
print(myfunc(param2='param2', param1=1, foo='foo'))
ここでは、全てキーワード引数を使ってパラメーターに引数の値を渡している。実行結果を見てみよう。
実行結果を見ると、「param1=1」については、その値(整数値の1)が第1パラメーターのparam1に渡されている。他の「キーワード=実引数の値」という組については、第2パラメーターのkwargsにまとめて格納されていることが分かる。
「others:」の隣には「{'param2': 'param2', 'foo': 'foo'}」とあるが、これはkwargsが辞書であり、そこに「キーワードと実引数の値」の組が格納されていることを意味している(辞書については「引数のアンパック(展開)」で簡単に見た)。辞書も反復処理が可能なので、今度はmyfunc関数を次のように変更してみよう。
def myfunc(param1, **kwargs):
tmp = f'param1: {param1}'
for item in kwargs.items():
tmp += f', {item[0]}: {item[1]}'
return tmp
このコードの詳細を詳しく理解する必要はないが、簡単に説明しておこう。まず、辞書が持つitemsメソッドを使うと「キーワードと値」の組を「(キーワード, 値)」というタプルの形式で順番に取り出せる(反復可能オブジェクト)。また、タプルの先頭要素は「[0]」というインデックスを指定して、次の要素は「[1]」というインデックスを指定して取り出せる。上のコードではこれらのことを利用して、戻り値となる文字列を組み立てている。
例えば、先ほどと同じコードを試してみよう。
print(myfunc(param2='param2', param1=1, foo='foo'))
実行結果を以下に示す。
辞書に格納されたキーワード引数から文字列がうまいこと作成されたことが分かるはずだ。
辞書について詳しく説明していないので、上のコードを完璧に理解する必要はない。ここで重要なのは、可変長位置引数を受け取る場合にはタプルにそれらが保存され、可変長キーワード引数では辞書にそれらが保存される違いはあるが、「各要素はfor文を使ってほぼ同様な形式で取り扱える」ということだ。
先ほど「*付きのパラメーターおよびパラメーターリストに単独で記述する*の後にあるパラメーターは全てキーワード引数を使って値を渡す必要がある」と述べたのを覚えているだろうか。特に後者のパラメーターリストに単独で記述する「*」より後ろにあるパラメーターのことをキーワード専用引数やキーワード専用パラメーターなどと呼ぶ。
また、Python 3.8以降では位置専用引数と呼ばれる機能もサポートされた。これはパラメーターリストに単独で記述する「/」よりも前にあるパラメーターは位置引数としてしか値を渡せない。つまり、「パラメーター名=値」のようにキーワード引数として値を渡すことができないパラメーターとなる。
以下に例を示す。まずは位置専用引数から。myfunc関数を次のようにしてみよう。
def myfunc(param1, /, param2, param3): # param1は位置専用引数
return f'param1: {param1}, param2: {param2}, param3: {param3}'
パラメーターリスト中でparam1とparam2の間に「/」があるので、それよりも前にあるparam1は位置引数のみを受け取るようになる。実際に呼び出してみるとどうなるだろう。
print(myfunc(1, 2, 3)) # OK
print(myfunc(1, param3=3, param2=2)) # OK
print(myfunc(param2=2, param1=1, param3=3)) # NG
最初の呼び出しは全て位置引数として値を渡しているので成功する。次の呼び出しでは、第1引数だけが位置引数で残りはキーワード引数となっているので、これも成功する。最後の呼び出しは全てがキーワード引数として値を渡しているが、既に見た通り、param1は位置引数のみを受け取るため、これは失敗する。
実行結果を以下に示す。
可変長位置引数を受け取る「*args」より後ろに並べられたパラメーターがキーワード引数しか受け取れないことは既に述べた。だが、パラメーターリストに単独で「*」を記述した場合も、それより後ろのパラメーターはキーワード専用引数となる。以下に例を示す。
def myfunc(param1, *, param2, param3=3): # param2とparam3はキーワード専用引数
return f'param1: {param1}, param2: {param2}, param3: {param3}'
パラメーターリストを見ると、param1とparam2の間に「*」があるのでそれより後ろのparam2とparam3はキーワード引数のみを受け取る(param3にはデフォルト引数値を指定している)。
実際の呼び出し例を以下に示す。
myfunc(param2=2, param3=3, param1=1) # OK
myfunc(1, param2=2) # OK
myfunc(1, 2) # NG
最初の呼び出しは全てをキーワード引数として値を渡している。myfunc関数のパラメーターリストには「/」がないので、param1は位置引数もキーワード引数も受け取れるので、この呼び出しは成功する。
次の呼び出しは第1引数が位置引数、第2引数がキーワード引数となっている。今も述べた通り、param1はどちらも受け取れることと、param2はキーワード専用引数となっているが、その通りにキーワード引数として値を渡していること、param3はデフォルト引数値を持っていることから、この呼び出しも成功する。
最後の呼び出しは位置引数として値を2つ渡している。しかし、myfunc関数は位置引数/キーワード引数を受け取れるパラメーターが1つ、キーワード引数しか受け取れないパラメーターが2つ(1つはデフォルト引数値を持つ)ので、この呼び出しは失敗する。
実行結果を以下に示す。
最後に注意しておきたいのは、可変長位置引数と可変長キーワード引数を受け取るパラメーターをパラメーターリストのどこに置くかだ。既に述べたが、可変長位置引数を受け取るパラメーターよりも右側に置いたパラメーターは全てキーワード引数で値を渡す必要がある。また、可変長キーワード引数を受け取るパラメーターはパラメーターリストの最後にしか置けない。よって、パラメーターリストは次のような順番でパラメーターを記述していくことになるだろう。
小規模な関数を定義するときには、キーワード引数のみを受け取るパラメーターがどうしても必要ということはそれほど多くないと思われるので、一般的には可変長引数を受け取るパラメーターはパラメーターリストの最後に記述することになると考えよう。よって、可変長位置引数と可変長キーワード引数を受け取る関数では、パラメーターリストの最後が次のようになる。
def myfunc(param1, ……, *args, **kwargs):
# argsとkwargsを使って何らかの処理を行うコード
return …… # 処理結果を返す
一方、多機能な関数を定義するときには、デフォルト引数値を指定した上で多数のキーワード専用引数を持つ関数となることが多くなるかもしれない。このときには、上で述べたような順序でパラメーターを並べていくことになるだろう。いずれにしても、可変長のキーワード引数はパラメーターリストの最後に指定する。
今回は関数の引数をテーマに、位置引数とキーワード引数、デフォルト引数値、可変長引数を紹介した。次回は「関数のスコープ」や「ローカル変数」などについて見ていこう。
「Python入門」
Copyright© Digital Advantage Corp. All Rights Reserved.