"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

Python関数の引数の定義・与え方・取得方法・注意点などをまとめておく

Pythonの関数に与える引数の与え方には色々バリエーションがあり、使い方の注意点などもありますので、度忘れ防止用に、ざっくりまとめておきます。

f:id:arakan_no_boku:20190427205325j:plain

 

Pythonの引数の種類

 

ざっとあげるとこんな感じ。

  • 通常の引数(位置引数)
  • キーワード引数(位置引数としても指定可)
  • キーワード専用引数(位置で与えるとエラー)
  • 可変長引数
  • 可変長キーワード引数

あと、使い方に注意が必要な引数としては。

があります。

 

通常の引数(位置引数)

 

説明するまでもないですが。

def normal_arg(number):
    print(number ** 2)

こんなやつです。 

引数を定義した位置(順序)にあわせて、値を与えるので「位置引数」になります。

 

キーワード引数(位置引数としても指定可)

 

これも、説明するまでもないレベルですが「keyword=value」の形で、キーワードとデフォルト値を引数として与えときます。

def normal_arg(number, first=2, second='答えは'):
    print(second, ':', number ** first)

キーワードと初期値の組合せで指定します。

使う場合。

当然ながら、キーワードを指定して実行時に与えるやり方ができます。

キーワード引数で与えるなら順番を入れ替えても大丈夫です。

normal_arg(3, second='変更後', first=3)

 

引数の省略もできます。(キーワード引数の初期値でよければ)

normal_arg(3) 

とか

normal_arg(3, first=3) 

 

普通に「位置引数」のように与えることもできます。

normal_arg(3, 3, '変更後')

 

キーワード専用引数(位置で与えるとエラー) 

 

キーワード引数は便利ですが、位置引数としても与えることができるので、間違って引数を与えてしまうリスクがあります。

なので、位置引数としての与え方を許さない「キーワード専用引数」があります。

引数に「*」をはさんで、キーワード専用引数の開始位置を示せば、そこから後ろのキーワード引数は「キーワード専用引数」になります。

def normal_arg(number, *, first=2, second='答えは'):
    print(second, ':', number ** first)

こうすると、位置引数では指定できなくなります。

それ以外はキーワード引数と同じなので、順番を入れ替えても大丈夫です。

normal_arg(3, second='変更後', first=3)

 

引数の省略もできます。(キーワード引数の初期値でよければ)

normal_arg(3) 

とか

normal_arg(3, first=3) 

 

でも、これはできません。

normal_arg(3, 3, '変更後')

こんなエラーメッセージがでます。

TypeError: normal_arg() takes 1 positional argument but 3 were given 

 

可変長引数

 

引数がいくつになるかわからないケースでは、可変長引数を使えます。

*argsのように「*」をひとつつけた引数を定義します。

慣習的に「*args」にしてますが、別に他の名前でもかまいません。

def variable_arg(number, *args):
    result = number
    for i, value in enumerate(args):
        result += value
    print(result)

実行するときは、こんなかんじです。

variable_arg(1, 2, 3, 4, 5)

可変長部分が指定されなければ、空のタプルが渡されるだけなので、そこを考慮されていれば問題なく処理できます。

variable_arg(1)

 

可変長キーワード引数

 

キーワード引数を可変長で与える場合には、「**」を引数名の頭につけます。

キーワードと値がセットになった辞書で受け渡されます。

例えば。

def variable_arg(number, **kwargs):
    result = number
    for key, value in zip(kwargs.keys(), kwargs.values()):
        print(key, ':', value * number)

実行時の引数の与え方はこんな感じになります。

variable_arg(2, one=1, two=2, three=3, four=4)

実行した結果は。

one : 2
two : 4
three : 6
four : 8

 なるほど・・、これは便利です。

 

イテレータで与えられた引数

 

よく考えればわかるのですが、一瞬勘違いしそうな「バグの種」です。

ここで言うイテレータとは、ジェネレータで生成されたものです。

arakan-pgm-ai.hatenablog.com

例えば。

以下の式で生成される「sample_itr」は、Listです。

sample_itr = [n for n in range(1, 6)]

でも。

以下のジェネレータ式<丸括弧()で囲ってます>で生成されるのが「イテレータ」です。

sample_itr = (n for n in range(1, 6))

printしてみると、上記は

 [1, 2, 3, 4, 5]

 ですけど、後者は

<generator object main.<locals>.<genexpr> at 0x00000297E36EA3B8>

です。 

この2つは同じようにforループの中で使えますが、決定的な違いとして、Listの方は繰り返し使っても同じ結果を取得できますが、イテレータの方は繰り返し使うことができない(同じ結果が保証されない)ことです。

これがあるので、引数の「 number_list」をsum()とforループで2回使っているような関数が、イテレータが渡されるとなった途端にバグを抱えることになります。

def contain_bugs(number_list):
    total = sum(number_list)
    result = 0
    for n in number_list:
        result += n
    print('total=', total, 'result=', result)

 

例です。

これを以下のようにして実行するとします。

contain_bugs(get_itr(6))

この引数の「get_itr」関数が以下のようにListを返す実装なら、問題はないです。

def get_itr(number):
    return [n for n in range(1, number)]

実行結果は 「total= 15 result= 15」と正しく計算されます。

でも、以下のようにジェネレータ式でイテレータを返す実装だと問題がでます。

def get_itr(number):
    return (n for n in range(1, number))

突然結果が「total= 15 result= 0」となってしまうわけです。

これを回避するためには、イテレータを一旦コピーして関数内でListに変換することで、繰り返し利用可能にする必要があります。

def contain_bugs(number_list):
    copyed_list = list(number_list)
    total = sum(copyed_list)
    result = 0
    for n in copyed_list:
        result += n
    print('total=', total, 'result=', result)

ジェネレータを使う場合は、結構気をつけないといけないってことですね。

もちろん、これは関数のから「yield」で返される場合も同じです。

さて。

今回は、こんなところで。

ではでは。