目次
Python関数の引数の定義・与え方・取得方法・注意点
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
なるほど・・、これは便利です。
イテレータで与えられた引数
よく考えればわかるのですが、一瞬勘違いしそうな「バグの種」です。
ここで言うイテレータとは、ジェネレータで生成されたものです。
例えば。
以下の式で生成される「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」で返される場合も同じです。
さて。
今回は、こんなところで。
ではでは。