初めてのPython実践試験学習 第24回「可変長位置引数と可変長キーワード引数」

こんにちは、吉政創成 菱沼です。

今回もPythonエンジニア育成推進協会のPython 3 エンジニア認定実践試験の主教材「Python実践レシピ/技術評論社」を使って学習中です。

関数にデータを受け渡すのに利用される引数の中で、前回はデフォルト値付き引数について学びました。今回は、可変長位置引数と可変長キーワード引数について学びます。

可変長位置引数とは何か

※Pythonでは関数定義時に使う引数を仮引数(parameter)とし、関数呼び出し時に渡す引数を実引数(argument)と呼ぶそうです。書籍もそれに合わせて記述されています。

関数を定義する際、仮引数の前に「*(アスタリスク)」を付けて、可変長の位置引数を受け取れるようにした引数を可変長位置引数というそうです。渡された複数の値はタプルとして順番にまとめられます。

これによって、例えば関数を実行する際に、仮引数に引き渡される引数の数があらかじめ決まっていなくても処理を続行することができるようです。

ではここで書籍のサンプルコードを確認してみます。

P.54

def func_sum(*args):
    total = 0
    for num in args:
        total += num
    return total

func_sum(1, 2, 3, 4, 5)
15

※可変長位置引数にする際、「args」がよく利用されるようですが、必ずしも「args」でなければならないわけではないとのこと。

引数の数を変えてやってみます。

確かに引数の数が増えても減っても、問題なく計算が行われました。

  • 可変長位置引数にはリストやタプルも渡せる

上記のコードでは単純に数値を引数として渡して、タプルとしてまとめていますが、逆にリストやタプルの要素を引き渡すこともできます。引き渡す際はリストやタプルの名前の前に「*(アスタリスク)」を付けます。そのサンプルがこちら。

# リスト
num = [1, 2, 3, 4, 5]
func_sum(*num)
15

# タプル
num2 = (1, 2, 3, 4, 5, 6)
func_sum(*num2)
21

可変長キーワード引数とは何か

次に可変長キーワード引数について確認します。

可変長キーワード引数は、仮引数の前に「**」をつけて定義するそうです。慣例的に「**kwargs」という名前が使われますが、仮引数名の前に「**」が付いていれば別の名前でも問題ないとのこと。

実行する際、「key=値」で渡されるので、キーと値がペアになった状態で辞書にまとめられます。

ではここで、書籍の引用とサンプルコードです。

—————-

P.55

**kwargsは複数のキーワード引数を辞書として受け取ります。**kwargsは一番最後に指定します。

def sample_func(name, **kwargs):
    print(f'{name=}')
    for key, value in kwargs.items():
        print(f'{key}: {value}')

        
sample_func('john', age=30, email='john@example.com')
name='john'
age: 30
email: john@example.com

—————-

**kwargsが最後とされるのは、「余ったキーワード引数」をまとめて受け取るためだそうです。

本当に辞書として管理されているのか、ちょっときになったので、print(f'{key}: {value}')の後に、print(f'{kwargs=}')を入れて実行してみたいと思います。その結果がこちら。

たしかに辞書として渡されていることが確認できました。

ちなみにname='john'が辞書に入らなかったのは、nameが通常の引数に設定されていて、それ以外の値が余ったキーワード引数となっているためで、kwargsに入るのは通常の引数として受け取られなかったものだったためです。

可変長キーワード引数には辞書を渡すこともできる

可変長位置引数で、リストやタプルを展開して渡せるという話がありましたが、可変長キーワード引数には辞書を渡すことができるそうです。辞書を渡したいときは、辞書の名前の前に「**」を付けてあげれば、辞書を展開してキーワード引数として渡せるとのこと。

ではここで書籍のサンプルコードです。

user_dict = {'name': 'john', 'age': 30, 'email': 'john@example.com'}
sample_func(**user_dict)
name='john'
age: 30
email: john@example.com

確かに辞書名に**を付けたら渡せることが確認できました。

可変長位置引数とほかの引数を組み合わせて使ってみる、順番は?

可変長位置引数も、ほかのタイプの引数と組み合わせて使用することが可能です。

デフォルト値付き引数を学んだ折、この引数は最後に書くというものがありました。では、今回の可変長位置引数はどこにいるべきものなのでしょうか。順番に確認していきます。

位置引数×可変長位置引数

まずは位置引数と一緒に使うケースのサンプルコードです。

このケースでは、通常の位置引数の後ろに可変長位置引数が記述されています。

def sample_func(param1, param2, *args):
    print(f'{param1=}')
    print(f'{param2=}')
    print(f'{args=}')

    
sample_func(1, 2, 3, 4, 5)
param1=1
param2=2
args=(3, 4, 5)

このコードでは、位置引数がふたつ(param1,param2)で、最後に可変長位置引数が定義されています。

param1,param2には引き渡された値が順番に渡され、残りの3つの値がargsに渡されています。

複数渡された位置引数のうち、通常の引数として受け取られなかった残りの値を、args がまとめて受け取っています。

位置引数×デフォルト付き引数×可変長位置引数

ではデフォルト付き引数があった場合はどうなるのか。

こちら、書籍からのサンプルコードを実行した結果です。

関数で定義されている仮引数はそれぞれ次のような役割の引数です。

  • param1:位置引数
  • default_arg:デフォルト付き引数(位置引数としても、キーワード引数としても値を渡せる)
  • *args:可変長位置引数(残りの位置引数を引き受ける)

エラーが出た原因を考えてみます

  1. sample_func2(1, default_arg=10, 2, 3, 4, 5)
    キーワード引数の配置が違うというエラーが出ています。
    少々ややこしいのですが、default_argは関数定義の時点ではデフォルト付き引数ですが、default_arg=10 のように名前を指定して値を渡した場合、その値はキーワード引数として扱われるのだそうです。そのため、default_arg=10 の後ろに位置引数を書くことはできないということになります。
  1. sample_func2(1, 2, 3, 4, 5, default_arg=10)
    default_argに複数の値が指定されているというエラーが出ています。
    param1には1が、default_argには2が、argsには(3, 4, 5)がタプルとして入ったにもかかわらず、最後にキーワード引数で10が指定されているため、default_argに2つの値が指定されているということです。
  1. sample_func2(1, 10, 2, 3, 4, 5)
    問題なく処理が完了しています。
    結果を見てわかる通り、param1に1、default_argに10、args に (2, 3, 4, 5) がタプルとして入っています。

引数を書く順番のまとめ

というわけで、ここまでの学習で出てきた各引数の順番をまとめてみます。

  1.  位置引数
  2. デフォルト付き引数
  3.  可変長位置引数(*args)
  4.  キーワード専用引数
  5. 可変長キーワード引数(**kwargs)

ちなみに書籍では、2と3が逆で、先に可変長引数を先に書くように勧められています。これについて確認してみたところ、実装上はどちらでも問題がないようです。世の中の慣例的にはこちらの順番で使われることが多いようです。

ではここで、書籍のサンプルコードを試してみます。

def sample_func(param1, *args, default_arg=0, **kwargs):
    print(f'{param1=}')
    print(f'{args=}')
    print(f'{default_arg=}')
    print(f'{kwargs=}')

    
sample_func(1, 2, 3, default_arg=100, keyword1='keyword1', keyword2='keyword2')
param1=1
args=(2, 3)
default_arg=100
kwargs={'keyword1': 'keyword1', 'keyword2': 'keyword2'}

問題なく最後まで結果が表示されました。

ちなみに書籍には、「このように何でも受け取るような関数は複雑になり、可読性が低くなります。実際は必要最低限の引数を、適切に組み合わせて定義するほうがよい」と書かれています。

確かに、実際の実務で使おうと思うと、少しこんがらがってしまいそうですね…。便利な反面、引数が増えすぎると処理が分かりにくくなるため、必要に応じて使い分けることが大切そうです。

それではきりが良いので今回はこちらで終了です。

お付き合いいただきありがとうございました。

実践試験について知りたい方は以下をご覧ください。

●Python3エンジニア認定実践試験

PAGE TOP