search
LoginSignup
4

More than 1 year has passed since last update.

posted at

updated at

Pythonの関数入門

まえおき

  • この記事は執筆中のやさしくはじめるPythonプログラミングの本の特定の章の部分抜粋です。
  • 入門本なので初心者の方向けです。
  • 関数の章の内容が主になります。
  • Qiita記事にマッチしていない箇所(「章」や「ページ」といった単語が使っていたり、改行数が余分だったり、リンクが対応していない等)があるという点はご留意ください。面倒なのでQiita用に調整するのやりたくない。気になる方は↑のリンクの電子書籍版をご利用ください。
  • コメントなどでフィードバックいただいた場合、書籍側にも活用・反映させていただく場合があります。

同じ処理のコードを使いまわす : 関数入門

この章ではPythonの関数について学んでいきます。そもそも関数ってなんだ?という感じですが、その辺りの説明を含めて説明していきます。

関数をうまく使いこなすとコードを書く量を減らせたり、同じコードを何度も書かずに済んだり、読みやすいコードにしたりとメリットがたくさんあります。

そもそも関数ってなんだろう?

まずは関数自体の説明から進めます。

関数は英語でfunctionとなります。数学でも関数と呼ばれるものがあり、そちらも英語だとfunctionとなっていますが、数学とプログラムでは関数の内容が異なります。別物なので注意してください。

Pythonのプログラムにおける関数は、主に以下のような特徴を持ちます。

  • 一定の量のプログラムのまとまりである。
  • 色々な場所からその関数のプログラムを実行することができる。
  • 任意の受け取った内容に応じて、関数内のプログラムの挙動を変えることができる。
  • 関数のプログラムの内容に応じて、任意の結果を返却することができる。

といったように、数とついているから数値に関連するもの?と思えるかもしれませんが、関数は数値以外の例えば文字列やリストなども様々なものを入力に受け付けてくれます。日本語だと少し紛らわしいですが、関数の英語は特に数に絡んだ単語は無く、単純にfunctionとなっている点も頭の片隅に入れておいてください。

functionは関数という意味以外に、「機能」「働き」といった意味も持ちます。プログラムの関数は、実行すると「なんらかの機能のプログラムが実行される」といったくらいに考えておくといいかもしれません。

関数の最初の一歩

Pythonの関数を作るには、defというキーワードを使います。defの後に半角のスペースを1つ入れて、その後にその関数の名前を書き、半角の()の括弧を書いて行末に半角のコロンの:を付けることで関数を作ることができます。

def 関数名():といった具合です。関数名のところは基本的に半角の英数字を使います。

関数のプログラムの内容は、defで定義した関数の次の行から、インデントを1個分(半角のスペース4つ分)追加してそこに書いていきます。

サンプルとして、1 + 1の内容をprintで出力するという関数のプログラム内容で、関数名をprint_one_plus_oneとしたい場合は以下のようなコードになります。

def print_one_plus_one():
    print(1 + 1)

前章のインデント関係のセクションで、「インデントはプログラムの階層構造を表す」「右にいくほどプログラムの階層の深い」と説明しました。

関数のプログラムでも、この「右に行くほどプログラムの階層が深い」というルールは健在です。

前述のコードでインデント1つ分(半角スペース4個)が設定されている、print(1 + 1)という部分に着目してみましょう。

この部分は「関数の中のプログラム」に該当します。つまり「print_one_plus_oneという関数」の中の「print(1 + 1)というプログラム」といったように、インデントが増えたことによってプログラムの階層が深くなっています(インデントの分、プログラムが中に入っています)。

また、リストや辞書におけるインデントの説明のセクションでは、インデントを以下のように省いたコードでも一応動きはする(プログラムのエラーにはならない)と説明しました。

int_list = [
1,
2,
3,
4,
]

一方で、関数ではインデントが意味を持ちます。「インデントを含んだプログラムの開始位置」が「関数内のプログラムの開始位置」を意味し、「インデントを含まないプログラムの直前の位置」が「関数内のプログラムの終了位置」を意味します。この辺りは少々分かりづらい気もするので、プログラムを踏まえてしっかりと見ていきます。

ひとまずは正しいインデントを設定しないと関数のプログラムが動いてくれないという点を頭にぽれて置いてください。

試しに、関数の後にインデントを入れないコードを書いて実行してみましょう。

def print_one_plus_one():
print(1 + 1)
  File "<ipython-input-12-a31292ca450b>", line 2
    print(1 + 1)
        ^
IndentationError: expected an indented block

上記のように、関数の後にインデントが無いとエラーになってしまいます。IndentationErrorはインデントが正しくないことによって発生するエラーで、indented blockはインデントされたコードのかたまりの意味、expectedは「想定されている」といった意味合いなので、「インデントが追加されたコード部分が来ることが想定されているけれども、そうなっていない(インデントが無い)」といったようなエラーのメッセージになります。

なお、コードではない行、例えばただの空の行(プログラムの書かれていない行)であればインデントが無くても問題ありません。たとえば、以下のようにdigit_value = 1 + 1の行とprint(digit_value)の間に空の行でインデントの無い行があっても問題ありません。

def print_one_plus_one():
    digit_value = 1 + 1

    print(digit_value)

関数を終わらせるには、インデントを無くしてコードを書きます。例えば、関数の内容は関数を実行(次のセクションで触れます)しないとコードの内容は実行されませんが、以下のコードではprint(3 + 5)という部分はインデントが無い(=関数の内容が終わっている)ので、関数を呼び出さなくても即時で実行され8という結果が表示されます。

def print_one_plus_one():
    digit_value = 1 + 1
    print(digit_value)


print(3 + 5)

コード実行結果の出力内容:

8

 

関数を実行する

作った関数を呼び出す(実行する)には、関数名()といったように、関数名の後に半角の()の括弧を利用します。

少し前に作ったprint_one_plus_oneという関数を実行したければ、print_one_plus_one()といったように書きます。コードを実行してみると関数の中のプログラム(print(1 + 1))が実行されて、2が出力されることが分かります。

def print_one_plus_one():
    print(1 + 1)


print_one_plus_one()

コード実行結果の出力内容:

2

 

関数と関数の外の変数の話 : スコープ入門

関数の中で作った変数は特殊な挙動をします。

説明のためにまずは以下のコードを書いて実行してみてください。

def print_one_plus_one():
    digit_value = 1 + 1
    print(digit_value)


print_one_plus_one()
digit_value += 1
2
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-21-50561b28b02f> in <module>
      5 
      6 print_one_plus_one()
----> 7 digit_value += 1

NameError: name 'digit_value' is not defined

print_one_plus_one関数がまずは実行され(print_one_plus_one())、そこでdigit_valueという変数が作られ(digit_value = 1 + 1)、その後関数の外でdigit_valueの変数に1をプラスする(digit_value += 1)というコードです。

しかしながら実行してみると「digit_valueという変数が定義されていないよ」というエラーメッセージ(name 'digit_value' is not defined)になってしまいました。

関数内のコードで変数を作っているのに何故でしょう?

実は、関数の中で作られた変数は、基本的には関数が実行し終わると消えてしまいます。そのため、関数内で作った変数などはそのままだと関数の外のコード(インデントが無い部分)や他の関数から使ったり(参照するともよく呼ばれます)はできません。

関数という箱の中に変数が色々入っているのをイメージしてください。別の関数は別の箱となります。お互いに、変数は箱の中に入っているものしか使うことはできません。

この箱のように、参照できる変数の範囲のことをスコープと言います。英語だとscopeとなります。「範囲」や「視野」といった意味を持つ単語です。プログラミングでは「変数などのアクセスできる範囲」といった意味になります。

スコープについてもう少し見ていきます。

先ほどは関数の中の変数を関数の外でアクセスしようとしたらエラーになりました。今度は逆に関数の外で作られた変数に対して関数の中からアクセスするとどうなるでしょうか?

関数の外で作られた変数の関数内での参照は、変数の型によって挙動が変わります。

まずは例として整数(int)の変数で試してみます。

int_variable = 100


def add_one():
    int_variable += 1


add_one()
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-4-e73c01cb25ac> in <module>
      6 
      7 
----> 8 add_one()

<ipython-input-4-e73c01cb25ac> in add_one()
      3 
      4 def add_one():
----> 5     int_variable += 1
      6 
      7 

UnboundLocalError: local variable 'int_variable' referenced before assignment

実行してみると上記のようなエラーになります。
Unboundは「束縛されていない」といった意味の他に、「未製本の」とか「割り当てられていない」といった意味を持ちます。

また、関数の中の変数のことをローカル変数(英語でlocal variable)と言います。特定の地域だけのルールをローカルルールなどと言ったりしますが、ローカル変数も似たような感じで「特定の関数の中だけの変数」という意味になります。

referencedは参照されたという意味で、assignmentは割り振りといったような意味を持ちます。ここでは、変数が作成されていないと読み替えていただいても問題ありません。

よってlocal variable 'int_variable' referenced before assignmentというエラーメッセージは、「int_variableというローカル変数が作成前に参照されているよ」というメッセージになります。

ローカル変数と対になるもので、グローバル変数というものがあります。ローカル変数は特定の関数でしか参照ができない一方で、グローバル変数は設定次第で色々なところで参照することができます。

関数の外で変数を作るとそれはグローバル変数になります。実は関数の章の前に今まで色々作ってきた変数はグローバル変数に該当します。

前述のUnboundLocalErrorになったコードでは、先にint_variable = 100というコードでグローバル変数を作っています。且つ、グローバル変数は色々な場所で参照できると説明しました。

それであればadd_one関数の中でint_variableを参照しているところでエラーになるのはおかしいのでは?という感じですし、グローバル変数なのにエラーメッセージでローカル変数云々と表示されるのはおかしくない?と感じられるかもしれません。

なぜこのような挙動になっているのかというと、Pythonのプログラムからすると「これはローカル変数なの?」「グローバル変数なの?」ということが分からないためです。

グローバル変数とローカル変数には同じ名前を使うこともできます。そのため、このままだとプログラム側からするとどっちで扱うべきか判断ができず、ローカル変数で整数が扱われている形になります。整数などのグローバル変数を扱いたい場合には「これはグローバル変数だよ」と明示的にプログラムに教えてあげる必要があります。

特定の変数をグローバル変数だとプログラムに教えてあげるには関数内でglobal 変数名と書きます。前述のコードのint_variableという変数をグローバル変数として扱う場合にはglobal int_variableといった形になります。

エラーが起きないように書き直すと、以下のようになります。ちゃんとグローバル変数の100の値に1プラスされて101が出力されていることを確認できます。

int_variable = 100


def add_one():
    global int_variable
    int_variable += 1
    print(int_variable)


add_one()

コード実行結果の出力内容:

101

 

関数の外(グローバル変数)で事前に変数を作る前に、関数内でglobalを使ってから変数を作り、関数実行後に関数の外でその変数を参照してみるとどうなるでしょうか?

コードを書いて実行してみます。

def define_global_variable():
    global int_variable_2
    int_variable_2 = 200


define_global_variable()
print(int_variable_2)

コード実行結果の出力内容:

200

 

今回は新しい変数名としてint_variable_2としました。define_global_variable()部分で関数が実行され、関数の中身でグローバル変数として変数が作成(global int_variable_2int_variable_2 = 200)され、その後に関数の外でその変数を参照(出力)しています(print(int_variable_2))。

globalの記述がなかった時にはエラーになっていたものが、エラーが発生せずに変数の200という内容が出力できていることが分かります。

型によってはglobalを省略してもグローバル変数にアクセスできる

先ほどのセクションで、整数などのグローバル変数を関数内でそのままアクセスしようとエラーになることを確認しました。

この挙動は整数以外の文字列(str)や浮動小数点数(float)、真偽値(bool)などの型の値でも同様の挙動になります。

一方で、リストや辞書などの値の場合は挙動が変わります。どう違うのか1つ1つ見ていきましょう。

まずは関数の外のグローバル変数を作って、関数の中で(globalの記述を省いた形で)その変数を参照してみます。

dict_variable = {'name': 'orange'}


def print_dict_variable():
    print(dict_variable)


print_dict_variable()

コード実行結果の出力内容:

{'name': 'orange'}

 

関数の外で辞書のグローバル変数を作成し(dict_variable = {'name': 'orange'})、関数を実行(print_dict_variable())しています。関数の内部では辞書のグローバル変数の内容を出力(print(dict_variable))しています。

前のセクションで整数の変数で試した時には、このような書き方だとglobalで変数を指定しないと(ローカル変数が未生成という判定になって)エラーになってしまいましたが、辞書のグローバル変数の場合はglobalで指定しなくてもエラーにならず、値もちゃんと出力できていることが分かります。

このように、辞書やその他リストなどでもグローバル変数を関数内で参照することができます。整数などとはスコープの挙動が異なるので注意してください。

続いて、グローバル変数と同じ変数名で、関数の中でローカル変数を作った場合の挙動を確認してみます(globalの指定は省略します)。

dict_variable = {'name': 'orange'}


def change_dict_variable():
    dict_variable = {'name': 'apple'}


change_dict_variable()
print(dict_variable)

コード実行結果の出力内容:

{'name': 'orange'}

 

まずは辞書のグローバル変数を作成(dict_variable = {'name': 'orange'})し、その後関数を実行(change_dict_variable())しています。関数の中ではグローバル変数と同じ名前で新しい辞書の変数を作成(dict_variable = {'name': 'apple'})しています。最後に、関数の外でグローバル変数の内容を出力(print(dict_variable))しています。

出力結果({'name': 'orange'})を見ると分かるように、関数内で設定した値({'name': 'apple'})は反映されておらず、グローバル変数を作ったときの値そのままになっています。

これは、関数内で新しくリストや辞書などを作った時は、同じ名前のグローバル変数があってもローカル変数として扱われていることを意味します。つまり、関数の内容が実行され終わったらローカル変数の内容は無くなってしまうので、結果的にグローバル変数の値がそのまま残っているという形になります。

少し複雑ですね。全部は覚えられなくても、必要になった時にコードを動かして試してみるなどして、その都度思い出せれば問題はありません。条件によって色々スコープの挙動が変わるという点だけは頭の片隅に入れておいてください。

最後にもう一つ、辞書のグローバル変数を事前に作成しつつ、関数の中でglobalを使って同名の変数を指定して、新しい辞書を設定するケースでの挙動を試してみましょう。

dict_variable = {'name': 'orange'}


def change_dict_variable():
    global dict_variable
    dict_variable = {'name': 'apple'}


change_dict_variable()
print(dict_variable)

コード実行結果の出力内容:

{'name': 'apple'}

 

前のコードと比べて関数内のglobal dict_variableの部分が追加になっただけです。しかし、この記述によってdict_variableの変数がローカル変数扱いではなくグローバル変数扱いになったため、関数実行後の出力処理(print(dict_variable))の結果が{'name': 'orange'}(グローバル変数設定時の値)でなはく{'name': 'apple'}(関数内での設定値)に変わっています。

このように値の型やglobal指定の有無で挙動が変わってきます。複雑なので、シンプルにするように以下のような工夫も役立ちます。

  • グローバル変数を使わない、もしくは使っても最小限にする(後で触れる引数という機能で代替できるケースが多いので引数を主に使う)。
  • グローバル変数として扱いたい場合には、数値や辞書など関係なくglobalで明示するようにする(辞書などでも省略しない)。
  • 紛らわしくなりそう(支障が出そう)なときは、グローバル変数とローカル変数で同じ名前を使うのを避ける。

また、型ごとに関数内でのグローバル変数の参照でglobalが要るかどうかの判別については、厳密な定義ではありませんが指標として、

  • 単一の値(整数や文字列など)はglobalの指定が必要
  • 単一の値をたくさん格納するような大きな値(リストや辞書など)はglobalの指定を省略可

くらいに覚えておくと分かりやすいかもしれません。

そもそもなんで関数を使うの?

もちろん関数を設けずにそのままコードを書いても動くプログラムは実現できます。ではなぜわざわざ関数を使うのでしょうか?

理由は色々ありますが、大きなものとして「コードの重複を減らす」という目的と、「コードを読みやすくする」の2点があります。

ここまでのコードサンプルだと短いコードばかりだったため、メリットが分かりづらいかもしれません。

しかし現実の仕事などでは、長いプログラムやたくさんのファイルのプログラムを扱うことが多くなります。そういった複雑な環境では関数のメリットが生きてきます。

たとえば仕事で20行くらいのコードの処理を書いたとします。このくらいであれば何も問題にはなりません。

しかし同じような処理が将来必要になって倍の40行が必要になったり、さらに増えて60行になったり...と繰り返している間に数百行、数千行、数万行...となってきてしまいます。特に、何年も続くプロジェクトの仕事だと顕著です。

毎回毎回同じようなコードを書いていると、コードを把握するのが大変になってきます。変数なども膨大になってきますから、「この変数はどのように使われているの?」といったようにコードを追っていくのも大変になりますし、関係無い変数を操作してししまうといったようなミスも多発するようになっていきます。

また、将来必要な処理が変わって、コードを編集する必要が出てきたとします。同じようなコードが数十箇所とあったらミスせずにコードを変えるのがとても大変になります。どこか一つでも変更が漏れている箇所があったりしたら不具合(バグとも呼ばれます)やエラーになったりしてしまいます。

プログラムを書く仕事では頻繁に機能を更新したり追加したりが求められますので、これでは大分辛い感じがしますね。

一方でもし処理が関数を実行する形で、実際の処理のコードは関数内にかかれていたらどうなるでしょう?

関数内のコードだけ変更すれば済むので変更漏れを防ぐことができます。また、変更に必要な作業時間もとても少なく済みます。

このように関数をうまく使うことでコードの重複している処理の部分を関数化したりして、変更などにかかる時間やリスクなどを減らすことができます。

もう一点の大きなメリットとして、関数の使い方次第ではありますがコードが読みやすくなるという点が挙げられます。

プログラムを読んだりするときには、要素が少ない方が読みやすくなります。例えば変数が300個くらいあって、後々の章で出てくる条件分岐などもたくさんあるようなコードだと内容の正確な把握などがとても大変になりますしミスしがちになります。

一方で、関数は前のセクションのスコープで触れた通り、ローカル変数などは「その関数内でだけ使われる」「関数が終わったらローカル変数は無くなる」性質を持ちます。

この性質によって、例えば300個の変数が必要な処理をたくさんの関数に分割して1つの関数につき5個とかに細かく分割したとするとコードがとても読みやすくなります。

よく「人間が一度に覚えられる要素は3~9個くらいだ」みたいな話を聞きますが、プログラムも同様で一度にたくさんの要素があると把握や短期間の記憶などが困難になってきます。

電話番号などで3つの数字や4つの数字などでハイフンを入れて分割して、把握しやすくするといったようなことに近いかもしれません。関数で分割して、1つの関数当たりの変数や処理の数を減らすことで、電話番号を分割するようにコードを把握しやすくミスしにくいコードにすることができます。

その他にも処理の少ない関数にたくさん分割していくことで、テストが書きやすくなるといったメリットもあります。テストなどに関しては結構発展的な内容になるため、後々の章で詳しく説明します。

色々なデータに応じて振る舞いを変える : 引数設定

固定の値のみを使った関数の処理よりも、変数のように可変の値を使って関数を作った方が汎用的で使いまわしがやりやすくなります。

たとえば、「5 × 3の計算をする」という関数よりも、任意のxの値で「xを3倍する計算をする」といった方が該当の関数を使えるケースが多くなります。

前のセクションで触れたグローバル変数などを使っても任意の変数を使うこともできるのですが、こういった場合には関数の「引数」と呼ばれる機能を使うのが一般的です。引数は英語ではargumentと言います。

日本語で引という名前になっていますが、プログラムにおける引数は数値以外も受け付けます。文字列や真偽値、リストなど色々引数に指定することができます。

英語のargumentは引数という意味の他にも、「議論する」「言い争う」という意味のargueの英単語からも予想できるように、「議論」や「主張」といった意味も持ちます。

一見引数と議論などは意味的に全然関係無さそうな感じではありますが、原義的には「論拠(議論を支持するための理由や証拠など)」から派生して、「他の量を求めるために利用される元の量」といった具合に変化してきたことに由来するそうです。

関数の処理の結果が引数の値によって変動するという性質を考えると、argumentという単語も少ししっくり来ますね。

日本語としての引数という単語は、意味的には「関数に引き渡す値」といったものに近いかもしれません。


引数を使うことで関数に任意の値を渡すことができます。

渡された引数はその関数のローカル変数として扱われます。前のセクションで「ローカル変数はその関数内でだけ使われる」「関数が終わったらローカル変数は無くなる」という性質を持つため、グローバル変数をたくさん使ってコードを書くよりも読みやすくミスしにくいコードを書くことができます。

ここまでのサンプルコードでは全て引数を使わない関数を作ってきました。

引数を受け付ける関数を作るには、関数名の後の()の括弧の中に引数名を指定していきます。

例えば前述の「任意のxの値で、xを3倍する計算をする」という処理で、引数にxを受け付けるmultiply_threeという名前の関数を作るにはdef multiply_three(x):というように書きます。

関数を実行する時に引数の値を指定するには、multiply_three()という書き方ではなく()の括弧の中に引数に指定したい値を設定します。例えば引数に10を指定したい場合にはmultiply_three(10)といったように書きます。

実際にコードを書いて、関数を実行してみて結果を確認してみましょう。

def multiply_three(x):
    multiplied_value = x * 3
    print(multiplied_value)


multiply_three(10)

コード実行結果の出力内容:

30

 

multiply_three(10)部分で関数を実行し、引数として10を指定しています。関数(def multiply_three(x):)の中では、引数に与えられたxの値(実行時に指定した値になるので、今回はxは10になっています)を3倍しています(multiplied_value = x * 3)。

最後に3倍した値をprintで出力しています。出力結果は10の3倍で30となっています。

複数の引数を設定する

先ほどはxという引数で、xの値を3倍する関数を作りました。今度は「3倍する」方の値も引数で実行時に変えるようにしましょう。3倍部分の代わりに、yという引数を受け付けるようにします。

2つ目以降の引数を指定するには、半角のコンマを引数間に加えます。スペースを入れなくてもエラーにはなりませんが、コンマの後に半角のスペースを1つ入れるのがPythonのコーディング規約で定められているのでそのように書きます。

この場合の関数の引数部分は(x, y)といったようなコードになります。

def multiply_x_by_y(x, y):
    multiplied_value = x * y
    print(multiplied_value)


multiply_x_by_y(10, 5)

コード実行結果の出力内容:

50

 

複数の引数を持つ関数を実行する場合には、そちらも半角のコンマ区切りで値を設定します。xに10、yに5を設定したければmultiply_x_by_y(10, 5)といったように関数実行部分を書きます。

関数内の処理では、* 3のように3倍するコードの代わりにyを掛ける形のコードにしてあります(multiplied_value = x * y)。

結果として、xの10とyの5による掛け算でprintで出力される値は50になっています。

3個以上の引数を使いたい場合には、コンマを追加していくことで任意の数の引数を設定することができます。例えばx, y, zという3つの引数を取る関数を作りたい場合には以下のように書きます。

def multiply_x_y_z(x, y, z):
    multiplied_value = x * y * z
    print(multiplied_value)


multiply_x_y_z(10, 5, 3)

コード実行結果の出力内容:

150

 

引数部分が(x, y, z)と三つの値になっており、関数実行時もmultiply_x_by_y(10, 5, 3)といったように3つの値を半角のコンマ区切りで指定しています。

printによる出力結果はxyzの三つの引数の10 × 5 × 3で150となっています。

このように引数は複数の値を設定できますが、それぞれ順番に第一引数、第二引数、第三引数...といったように呼ばれます。今回の関数で言えば、multiply_x_y_z関数の第一引数はxとなり、第二引数はy、第三引数はzとなります。

渡された引数の数が一致していない場合は・・・

例えば3つの引数を受け付ける関数で、実行時に2つだけ値を指定するとどうなるのでしょうか?コードを書いて試してみましょう。

def multiply_x_y_z(x, y, z):
    multiplied_value = x * y * z
    print(multiplied_value)


multiply_x_y_z(10, 5)

コードの内容としてはx, y, zの3つの引数を受け付ける関数で、実行時にmultiply_x_y_z(10, 5)といったように引数に2つだけ値を渡しています(zの引数の値が指定されていません)。

TypeError                                 Traceback (most recent call last)
<ipython-input-27-8cab8c50c583> in <module>
      4 
      5 
----> 6 multiply_x_y_z(10, 5)

TypeError: multiply_x_y_z() missing 1 required positional argument: 'z'

実行してみるとエラーとなりました。エラーメッセージを読んでみましょう。

missingは「足りていない」、requiredは「必須の」といった意味になります。argumentは引数の英単語でしたね。

ざっくり訳すと「'z'という名前の必須の(省略できない)位置の引数が足りていないよ」というエラーメッセージになります。

このように関数を実行した時に必要な引数の指定が足りていない場合にはエラーになります。このエラーが出たときには「何の引数が足りていないのか」を確認し、実行しているコードを見て引数の数が合っているのかチェックしましょう。

指定しなくてもエラーにならない設定 : 引数のデフォルト値

先ほどのセクションのエラーでrequired positional argumentと出てきて、必須の引数といったエラーメッセージになっていたことからも分かる通り、「必須ではない(省略できる)」引数設定も行うことができます。

こういった省略可能な引数のことを「オプション引数(英語だとoptional arguments)」などと呼びます。

また、省略した場合に「引数がどんな値になるか」は「デフォルト値」と呼ばれるもので指定します。引数にデフォルト値を設定するには引数名の直後に半角のイコールの=の記号と設定したいデフォルト値を設定します。例えば、xという引数に10のデフォルト値を設定したい場合にはx=10といったように書きます。

変数設定を作るときなどにはイコールの前後に半角のスペースを入れていましたが、デフォルト引数ではイコールの前後にはスペースを入れないのが正しいPythonのコーディング規約です。変数設定などとは異なるので注意してください。

スペースを入れてしまってもエラーにはなりませんが、コーディング規約に準じていないコードになってしまいます。

コードを書いて試してみましょう。

def print_x(x=10):
    print(x)

内容は引数に指定された値をprintで出力するだけのシンプルな関数です。引数もxという名前の引数1つだけです。

引数の部分がx=10となっていることを確認してください。これは「xの引数指定を省略した場合はデフォルト値の10が設定される」という記述になります。

関数を実行してみましょう。まずはxの引数を省略せずに実行してみます。xの引数には30を指定します。

print_x(30)

コード実行結果の出力内容:

30

 

引数に指定した30の値が出力されました。

このように、デフォルト値が設定されている引数に対して値を省略せずに指定すると、指定された値が優先(今回は30)され、デフォルト値の10は無視されることが分かります。

今度は引数を省略して実行してみます。()の括弧の間が何もない(引数の値を指定していない)点に注目してください。

print_x()

コード実行結果の出力内容:

10

 

結果は引数のデフォルト値として設定してある10が出力されています。

このように、デフォルト値を引数に設定した場合には、

  • 引数へ値を指定した場合 -> 指定した値が設定される。
  • 引数を省略した場合 -> 引数のデフォルト値が設定される

という挙動になります。

デフォルト値の使いどころですが、主に「通常はこのデフォルト値で大丈夫だけど、たまに値を変えたい」といったケースで使います。

逆に、引数に指定する値が基本的に毎回異なるようなケースではデフォルト値を設定すべきではありません。

そのようなケースでは通常の引数が必須となる形で関数を作っておくべきです。毎回引数の指定が必要なのに、指定をうっかり忘れてしまった時などにデフォルト値が設定されていると(引数を指定し忘れてしまっていても)エラーにならず、ミスに気づかないケースが発生しうるためです。

また、デフォルト値を持つ引数を設定する時には注意点として「デフォルト値が設定されている引数は必須の引数の後になっていないといけない」というルールが存在します。

たとえば以下のようにデフォルト値を持つ引数(x=10)の後にデフォルト値を持たない必須の引数(y)を関数に設定するとエラーになります。

def multiply_x_by_y(x=10, y):
    multiplied_value = x * y
    print(multiplied_value)
  File "<ipython-input-34-13a53a0612f1>", line 1
    def multiply_x_by_y(x=10, y):
                       ^
SyntaxError: non-default argument follows default argument

エラーメッセージを訳すと「デフォルト値を持たない引数がデフォルト値を持つ引数の後に来ているよ」といった内容になります。

これはmultiply_x_by_y(10)といったように関数を実行した際に、先にある引数が省略可能な一方で後の引数は省略不可(デフォルト値が無い)状態になっているので、(10)の値が第一引数のxに対する値なのか第二引数のyに対する値なのかがプログラム側から判断ができなくなってしまうためです。

そのためデフォルト値を持つ引数は必須の引数の後に配置しないといけまぜん。以下のように、xとyの2つの引数でx側は必須の引数、y側はデフォルト値を持つ省略可能な引数を設定するケースではエラーは無く動きます。

def multiply_x_by_y(x, y=10):
    multiplied_value = x * y
    print(multiplied_value)

こちらのエラーの出ないケースでは、multiply_x_by_y(30)といったように1つの引数のみ指定して実行すれば必須の第一引数のxに対する値だと分かりますし、multiply_x_by_y(30, 20)と引数を省略せずに2つとも値を指定した場合にもxの値は30、yの値は20といったように問題なくプログラム側でコードを解釈することができます。

必須の引数が複数ある場合にも、デフォルト値を持つ引数はそれらの後に来なくてはなりません。

例えば以下のように必須の引数xの後にデフォルト値を持つ引数yが来ていて、さらにその後に必須の引数zを設定するといったことはできません(エラーになります)。

def multiply_x_y_z(x, y=20, z):
    multiplied_value = x * y * z
    print(multiplied_value)
  File "<ipython-input-39-81a253f339fe>", line 1
    def multiply_x_y_z(x, y=20, z):
                      ^
SyntaxError: non-default argument follows default argument

このように必須の引数は複数ある場合も、以下のようにデフォルト値を持つ引数(z=20)はデフォルト値を持たない必須の引数の後に持ってこないといけません。

def multiply_x_y_z(x, y, z=20):
    multiplied_value = x * y * z
    print(multiplied_value)

デフォルト値を持つ引数を複数設定したい場合でも、必須の引数の後になっていればエラーにはなりません。たとえばyとzの引数がデフォルト値を持つ設定にしたい場合には以下のようにデフォルト値を持たない必須の引数xの後にそれぞれが続いていればエラーにはなりません。

def multiply_x_y_z(x, y=20, z=30):
    multiplied_value = x * y * z
    print(multiplied_value)

関数の引数が変わっても影響を受けにくくて読みやすい : キーワード引数

例えば以下のようなたくさんの引数を持つ関数があったとします。

def calculate_cost(manufacturing_cost, labor_cost, delivery_cost, tax):
    sum_value = manufacturing_cost + labor_cost + delivery_cost + tax
    return sum_value

関数を実行しようとすると以下のようになります。

calculate_cost(1000, 400, 150, 50)

一応このくらいの数であれば各値がどの引数に該当するのかは一応把握できるといえば把握できます。しかし分かりづらく感じますし、うっかり順番ミスなどをしてしまいそうです。

さらに引数の数が増えたらどうなるでしょうか?例えば10個の引数が必要な関数だったり、15個の引数が必要な関数などをイメージしてみてください。

ミスせず、順番通りに引数の値を指定するのが大分難しそうですね。また、その関数が頻繁に引数の数が更新で変わってしまうようなケースを想像してみてください。途中の引数が無くなったりすることで、その後の引数の順番がずれたりしてとても混乱します。

そのようなケースの対策として、Pythonでは「キーワード引数(英語でkeyword arguments)」という機能が存在します。

キーワード引数を使うことで、引数の多い関数でも読みやすいコードを書くことができます。

実際に関数実行部分のコードを書いてみましょう。キーワード引数を使うには、関数実行箇所で引数名=引数に指定する値という形で書くことで使えます。例えば、前述のサンプルコードの関数で言えばmanufacturing_costの引数でキーワード引数を使うにはmanufacturing_cost=1000といったように書きます。

その他の複数の関数を指定するときには半角のコンマで区切るといったルールは普通に引数指定と同じです。

calculate_cost(
    manufacturing_cost=1000,
    labor_cost=400,
    delivery_cost=150,
    tax=50)

キーワード引数を使うことで各引数の値がどの引数なのかすぐに分かるようになりました。例えばdelivery_costであれば150といった具合です。

また、キーワード引数を使うと引数名の分コードが横に長くなって読みづらくなってしまうので、上記のコードでは引数ごとに改行を入れています。

キーワード引数には前述のように「引数が多い関数で内容が把握しやすく、ミスしにくくなる」というメリットの他にもいくつかのメリットがあります。

例えば、デフォルト値を持つ引数が多い関数で「後ろの方の一部の引数だけ特定の値を設定したい」といったケースで便利です。

以下のような関数があったとします。

def multiply_x_y_z(x=10, y=20, z=30):
    multiplied_value = x * y * z
    print(multiplied_value)

この関数でx、y、zそれぞれの引数で全てにデフォルト値があります(引数が省略できる形になっています)。

この関数で、zの引数だけ100という値に変更が必要になったようなケースを想定してみます。普通の引数の指定だと順番に値を指定しないといけないので、zの値を指定しようとしたらxとyもデフォルト値と同じ値を指定しないといけません(multiply_x_y_z(10, 20, 100)といったように)。

3つくらいの引数ならまだしも、引数が多くなってくると辛くなってきます。特に、後ろの方の引数が利用頻度が多くなったケースなどは辛さが増してしまいます(コードを書いた当時は優先度順で引数が設定されていても、時間経過やアップデートなどで後ろの方の引数の方が重要度が高くなるということは起こりえます)。

また、コードがアップデートされて関数のデフォルト値が変わるとどうなるでしょう?例えば、関数のデフォルト値がdef multiply_x_y_z(x=100, y=200, z=300):といったように更新されたとします。

このようなケースでは関数の実行側(multiply_x_y_z(10, 20, 100)といったようなコード)のも漏らさずに更新しないと、「デフォルト値と同じ値を想定して指定していた」といったケースで予期せぬ挙動になってしまうかもしれません(関数の実行箇所がたくさんあると、ミス無く修正するのが手間になります)。

一方でキーワード引数を使うと、特定の引数名の引数のみ直接値を指定して関数を実行することができます。

例えばzの引数のみ値を指定し、他はデフォルト値のまま設定したい場合には以下のように書くことができます。

multiply_x_y_z(100)

このように書くことで余分な記述が少なくて済みますし、関数でデフォルト値が変更になった時に影響を少なくすることができます。

もう一点キーワード引数で考慮すべき点として、「デフォルト値を取る引数は基本的にキーワード引数で指定するのが好ましい」という点があります。

デフォルト値を取る引数に関しては、「設定が任意(無くてもいい)」という性質を持つ都合、日々のアップデートなどで追加になったり逆に削除されたり、もしくは引数の順番が変更されたりが発生するケースがあります。

そのためデフォルト値を持つ引数の順番が変わっても大丈夫なように、基本的にデフォルト値を持つ引数に関しては毎回キーワード引数で指定するようにしておくと影響を受けづらく堅牢なコードになります。

参照している関数を変えなければいいのでは?と思えるかもしれませんが、仕事では他の方が作ったライブラリ(便利で汎用的なコード集のようなものです)を使うケースが多く発生しますが、そういったライブラリがセキュリティ的な都合や古いバージョンでサポートが切れてしまったためにアップデートしないといけなくなるといったケースも発生してきます。

そういった場合にアップデートで動かなくなってしまったりバグの要因になったりを減らすため、適切にキーワード引数を活用していきましょう。

値渡しと参照の値渡しの話

前のセクションで触れた型ごとのグローバル変数とローカル変数の挙動の違いの件に少し似ていますが、引数にも「値渡し」と「参照の値渡し」という概念が存在します。

少々日本語の意味合いが分かりづらい単語ではありますが、それぞれ以下のような特徴を持ちます。

  • 引数の値渡し -> 引数に指定した変数などの値がコピーして渡されます。そのため、関数内でその変数を更新しても関数の外の変数には影響は出ません。
  • 引数の参照の値渡し -> 引数に指定された変数などの値はコピーされません。そのため、関数内でその値を更新すると関数の外の変数でも内容が変わります。

それぞれ、値の型によって値渡しと参照の値渡しに挙動が分かれます。

  • 整数(int)や浮動小数点数(float)、文字列(str)や真偽値など -> 値渡し
  • 辞書やリストなど -> 参照の値渡し

となります。

分かりづらいのでコードを書いて試していきましょう。

まずは値渡しとなる、整数を引数に指定するケースを試してみます。

def add_one_to_x(x):
    x += 1


cat_age = 10
add_one_to_x(x=cat_age)
print(cat_age)

コード実行結果の出力内容:

10

 

まずはcat_ageという名前の変数を関数の外で作成しています。値には10を設定しています。

その後add_one_to_x(x=cat_age)部分で関数を実行し、引数の値としてcat_ageの変数を指定しています。

関数の中では引数の値に1を加え、最後にprint(cat_age)で変数の値を出力しています。

出力結果は関数の中で1を加えているにも関わらず10となっています(11にはなっていません)。

今回引数に指定したcat_ageという変数は整数の値です。そのため値渡しとなり、値渡しでは関数へ値がコピーされて渡される(元の変数とは別のものとして渡される)ので関数の中でその値が更新されても元の変数には影響は発生せずに10のままとなっています。

今度は参照の値渡しとなるケースをコードを書いて試していってみます。先ほどは整数の値を引数に指定しましたが今回は辞書を引数に指定します。

my_age = 25
my_age += 2
print(my_age)

コード実行結果の出力内容:

27

 

最初にcat_info_dictという変数にageというキーを持つ辞書を設定しています。

その後関数を実行し、引数にcat_info_dictの変数を指定しています。

関数の中では引数に指定された辞書のageのキーの値に1を加えています。

最後にprintでcat_info_dictの変数の内容を出力しています。

出力結果を見ると分かるように、関数の中で実行された辞書の値の更新が元の変数にも反映されており、辞書のageのキーの結果が11になっていることが分かります。

このように辞書やリストなどを指定した際には参照の値渡しとなり、元の変数に対しても変更がかかります。

型によって挙動が変わるので、変数が更新されないことを想定していたのに「更新がされてしまう」ケースと、変数が更新されることを想定していたのに「更新がされない」ケース両方でうっかりミスが発生しがちなので注意しましょう。

ちなみに、何故このように型によって挙動が変わるのでしょうか?大きな理由としては2つあり、1つ目は「基本的にはローカル変数のように、関数の外に影響が出ない方がミスが少なくできる」という点があります。

ローカル変数やグローバル変数などのセクションで触れたように、ローカル変数はその関数の実行が終わると無くなります。そのため関数の外の領域に変数がたくさん出来てしまったり、他の関数からは参照できないといったように、ミスをしにくくなるという面でコードを扱う上で安全性が高まります。

整数や文字列などが値渡しで渡された時には、ローカル変数と同じような扱いになります。つまり、関数の実行が終わると引数に指定された値(元の変数などからコピーされた値)は破棄されます。

これによってローカル変数を扱うのと同じように安全性を加味した実装ができます。

では何故リストや辞書は参照の値渡しとなり、コピーされずに引数に渡されるのでしょうか?
コピーされてローカル変数として扱われた方が安全なのにどうしてでしょう?

理由としては、リストや辞書などはたくさんの数値や文字列を格納できるので、コピーしてしまうと処理で負荷が高くなってしまうケースということが考えられます。

例えばリストに数百万件の数値が格納されていたらどうでしょう?関数を実行する度にそのリストがコピーされていたら、処理時間が長くなってしまいますし、そのような大きなリストがいくつもコピーされるとパソコンのメモリも多く必要となってしまいます(スペックの低いパソコンでコードを実行したりが難しくなります)。

そのため、関数実行時の負荷を下げるためにリストや辞書などの場合には値渡しでコピーはされずにそのまま変数の値が渡されるようになっています。このようにコピーされることなく値が引数に渡されるという点は、少ない負荷で関数が実行できるという2つ目のメリットになります。

そう考えると、数値や文字列などもコピーせずに渡された方が負荷が少なくていいのでは?という気もしてきますが、数値や文字列単体の値はコピーされても最近のパソコンであればほぼ「誤差」と言えるレベルの負荷です(あまりにも長文な文字列を指定した場合などは除きます)。

そのくらいの負荷であれば値がコピーされてローカル変数のように扱われた方が、安全面でメリットが大きいため値渡しとして値がコピーされるようになっています。

このような理由からメリット・デメリットを踏まえて型によって値渡しと参照の値渡しの挙動の違いが発生します。

混同してしまうとミスの元になるので、気を付けていきましょう。

とはいえ、慣れないうちは分かりづらい概念ではあるので、少しずつプログラミングに慣れていただく中でこれらも身に付けていけば問題ありません。コードを書き続けてさえいれば、恐らく将来値渡しや参照の値渡しによる「想定した挙動になっていない」という経験をすると思います。

そういった経験を積むことで段々と自然と覚えていくものです。焦らずにコードを書き続けることを優先してください。

関数の結果を返す : 返却値設定

前の値渡しのセクションで、引数の整数や文字列などの値はコピーされて渡され、関数内でのそれらの値の変更は関数外には反映されないと書きました。

しかし、関数の結果を受け取って、関数外の変数に設定したいようなケースも多く存在します。

そのような場合には関数の返却値(戻り値や返り値などとも呼ばれます)という機能を使うことで実現できます。返却値は英語ではreturn valueとなります。

返却値を関数で設定する場合には関数内でreturn 返却する値といったように書きます。

実際にコードを書いて動かしてみましょう。以下のコードでは関数内のreturn added_valueの部分が返却値関係の記述となっています。

def add_one(x):
    added_value = x + 1
    return added_value


returned_value = add_one(x=10)
print(returned_value)

コード実行結果の出力内容:

11

 

返却値が設定されている関数を実行すると、値が返ってくるようになります。その値を変数などに設定することで返却値の内容を取得することができます。

前述のコードではreturned_value = add_one(x=10)の部分が該当します。add_one(x=10)部分で関数を実行し、値が返ってくるのでreturned_value =という変数へ値を設定する記述で関数で設定されている返却値の値を取得取得することができます。

関数の内容はxという引数に指定された値にプラス1して、その値を返却する(return added_value)だけです。

返却値の値を出力(print(returned_value))してみると、関数内でプラス1された結果の11が出力されることを確認できます。

returnの記述は処理を停止する挙動も含まれる

returnの記述には返却値を設定する以外にも「関数の処理を停止させる」という意味も持ちます。関数内でreturn部分に遭遇した場合にはそこで関数の処理は停止して、その後の処理は実行されません。

処理の停止のみを目的とした場合には、返却値の値を設定せずにreturnだけ書くこともできます。

例えば以下のように関数のすぐ下にreturnの記述がある場合、その後の処理は実行されません。

def add_one(x):
    return
    added_value = x + 1
    return added_value


returned_value = add_one(x=20)
print(returned_value)

コード実行結果の出力内容:

None

 

関数内の最初にreturnの記述があるので、その後の処理は実行されません。そのreturnの箇所の時点で処理が停止します。

つまり、その後のadded_value = x + 1return added_valueの処理は実行されなくなります。

実際に関数を実行(returned_value = add_one(x=20))して、その結果を出力(print(returned_value))してみても、プラス1した結果は出力されずにNoneという内容が出力されます。Noneは後の章で詳しく触れますが「何もない」といったような値です。returnだけで返却値が設定されずに処理を停止しているので、返却値がNoneとなってしまっています。

計算結果などは返ってきておらず、return added_valueなどの部分が実行されていないことが分かります。

現状だとこのような関数の処理を途中で止めるという制御が何に使うのだろう?という感じではありますが、後々の章で学ぶ条件分岐などで役に立ちます。たとえば「〇〇の条件では処理を停止する」みたいな制御ができるようになります。

複数の値を返却値に設定する

返却値には複数の値を設定できます。その場合には半角のコンマ区切りで複数の値を設定します。

例えば以下のコードではreturn added_x, added_yという部分で、複数の返却値をコンマ区切りで設定しています。

def add_one(x, y):
    added_x = x + 1
    added_y = y + 1
    return added_x, added_y


returned_x, returned_y = add_one(x=100, y=200)
print('returned_x:', returned_x, 'returned_y:', returned_y)

コード実行結果の出力内容:

returned_x: 101 returned_y: 201

 

関数の内容としてはxとyという2つの引数を受け取り、それぞれにプラス1して2つとも返却値として返却しているという内容になります。

returned_x, returned_y = add_one(x=100, y=200)という部分を見ると分かる通り、関数を実行した後の変数に設定する値の部分もコンマ区切りで設定する必要があります。

最後に返却値の内容をprintで出力しています(print('returned_x:', returned_x, 'returned_y:', returned_y))。

後々の章で詳しく触れますが、printもコンマ区切りで複数の値を同時に出力することができます(ラベルとしての文字列2つと2つの変数の値の出力のために合計四つの値をprintに指定しています)。

出力された内容を確認すると、引数のxとyに指定した値をプラス1した値がそれぞれの返却値の変数で確認できます。

複数の値の返却値設定は便利ですが、キーワード引数ではなく通常の引数でたくさんの引数を使おうとするとミスみやすくなるのと同様に、たくさんの返却値があるとミスを誘発しやすくなります。引数側と異なりキーワード返却値といった機能は無いのであまり多くならない程度に適度に使っていきましょう。

引数とアスタリスクの特殊な挙動

重要度 : ★★☆☆☆(最初は知らなくてもいいかも)

以下のコードのようにx, y, zの3つの引数を受け取る関数(add_three_value)があったとします。また、3つの値を格納するリストの変数(argument_list)もあり、これを順番に変数に指定したいとします。

def add_three_value(x, y, z):
    total = x + y + z
    return total


argument_list = [10, 20, 30]

引数の3つの値はそれぞれ整数で指定する必要があるため、リストをそのまま指定するだけでは引数の数が合っていない(1つしか引数が指定されておらず、残り2つの引数が足りていない)というエラーになってしまいます。

add_three_value(argument_list)
TypeError: add_three_value() missing 2 required positional arguments: 'y' and 'z'

このようにリストの中の値をそれぞれの引数に指定したいといったケースでは、Pythonの特殊な書き方として関数実行時の値の指定でリストの変数の直前に半角のアスタリスクの*の記号を設定するとリストの中身を順番に引数に割り振ってくれます。

以下のコードのサンプルでは*argument_listというコード部分が該当します。

returned_value = add_three_value(*argument_list)
print(returned_value)

コード実行結果の出力内容:

60

 

今度はエラー無く動作しています。また、x, y, zにそれぞれリストの中身の10, 20, 30が渡され、合計の60の値が返却されていることが出力結果を見ると分かります。

このアスタリスクを使った書き方は、リストの中身が展開されて引数に割り振られるので、引数の数とリスト内の値の数が一致していないとエラーになってしまいます。

例えば以下のように3つの引数を受け取る関数に対して、4つの値を格納したリストを指定するとエラーになってしまいます。

def add_three_value(x, y, z):
    total = x + y + z
    return total


argument_list = [100, 200, 300, 400]
add_three_value(*argument_list)
TypeError: add_three_value() takes 3 positional arguments but 4 were given

エラーメッセージを見てみると、「add_three_value()という関数は3つの引数を必要とするけど、4つ値が指定されているよ」といったような内容になっており、関数の引数の数と実際に指定された値の数の不一致によってエラーになっていることが分かります。

ただし前のセクションで触れたデフォルト値が設定されていて省略可能な引数が含まれている場合には引数の数とリストの値の数が一致していなくても問題はありません。以下のようにx, y, zの3つの引数を持つ関数で、zにデフォルト値が設定されている(zは省略できる)場合にはリストの件数は2件でも3件でもどちらでもエラーにはなりません(2件省略したりはできないので値が1件のリストなどを指定した場合にはエラーにはなります)。

リストは一つのアスタリスクを指定することで対応ができました。ではキーワード引数に関してはどうでしょう?たとえば以下のような、前回までと同様にx, y, zの3つの引数を持つ関数に対して、キーに引数名が設定された辞書を指定したいようなケースです。

def add_three_value(x, y, z):
    total = x + y + z
    return total


argument_dict = {
    'x': 10,
    'y': 20,
    'z': 30,
}

このような辞書を使ってキーワード引数的に引数に値をPythonで指定したい場合には、関数実行時の引数の値の直前に半角のアスタリスクの記号を2個設定することで対応ができます。例えば、**argument_dictといったように辞書の前にアスタリスクを2つ記述します。

total = add_three_value(**argument_dict)
print(total)

コード実行結果の出力内容:

60

 

こちらの辞書によるキーワード引数の指定の仕方でも、リストの時と同様にデフォルト値が設定されている引数を除いてキーワード引数の指定が不足しているとエラーになります。たとえば前述の関数でyのキーワード引数が辞書に含まれていない形でコードを実行してみると以下のようなエラーになります。

def add_three_value(x, y, z):
    total = x + y + z
    return total


argument_dict = {
    'x': 10,
    'z': 30,
}

add_three_value(**argument_dict)
TypeError: add_three_value() missing 1 required positional argument: 'y'

任意の個数の引数を受け付けるようにする

通常は以下のコードで2個の引数を受け付ける関数に対して3つの引数を指定したケースのように、関数に設定されてある個数を超える数の値を引数に指定するとエラーになります。

def add_two_value(x, y):
    total = x + y
    return total


add_two_value(100, 200, 300)
TypeError: add_two_value() takes 2 positional arguments but 3 were given

エラーメッセージを読んでみると、「add_two_value 関数は2つの引数を受け取るけれども、引数が3つ指定されているよ」といったような内容になります。このように、基本的には関数で受け付けられる引数の数は制限がされます。

一方で任意の個数の引数を受け付けるようにすると、「3つの引数の値で計算」したり「5つの引数の値で計算」したりと1つの関数で柔軟な挙動を実装することができます。

そういった関数を作るには、関数実行時と似たような形で関数の引数部分に半角のアスタリスクを使います。引数名は慣習的に「任意の関数の引数(argument)群」としてargsという名前が使われることが多めです。アスタリスクを付与して*argsという形で使われることが良くあります。

*argsの引数はタプルで設定されます(以前の章で触れた、値の変更が効かないリストのような型になります)。

def print_args(*args):
    print(args)

関数の内容は渡された*argsの引数の内容をprintで出力するだけです。

試しに3つの引数を指定して実行してみましょう。

print_args(100, 200, 300)

コード実行結果の出力内容:

(100, 200, 300)

 

*argsの出力内容を見ると、3つの値が格納されたものがちゃんと出力されていることが分かります。また、*argsしか引数に無い関数であるのに3つの引数を指定してもエラーになっていないことが分かります。

今度は5つの引数を指定して関数を実行してみましょう。

print_args(100, 200, 300, 400, 500)

コード実行結果の出力内容:

(100, 200, 300, 400, 500)

 

こちらもエラーは発生せずに、指定した且つ5つの引数の内容が関数内で出力されていることが確認できます。

なお、*argsといった記述で設定されるargs引数は、リストのようにインデックスで値を参照することもできます。例えば先頭の値(0のインデックス)を参照したい場合はargs[0]といったように書けば、引数の先頭の値にアクセスすることができます。

以下のコードでは、関数内で先頭の引数をprintで出力しており、結果として先頭の100が表示されています(print(args[0]))。

def print_args(*args):
    print(args[0])


print_args(100, 200, 300)

コード実行結果の出力内容:

100

 

任意の引数名のキーワード引数を受け付けるようにする

先ほどは任意の個数の引数を受け付けてくれる関数を作りました。今度は任意の引数名のキーワード引数を受け付けてくれる関数について説明していきます。

通常の引数同様、そのままでは関数で設定されていない引数をキーワード引数で指定すると以下のコードのようにエラーになってしまいます(関数の引数に存在しないxというキーワード引数を指定しています)。

def multiply_two():
    return x * 2


multiply_two(x=100)
TypeError: multiply_two() got an unexpected keyword argument 'x'

エラーメッセージを読んで見ると、「multiply_two関数がxという想定外のキーワード引数を受け取りました」といったような内容になります。xという引数が定義されていないのにxという引数が指定されているためこのようなエラーメッセージになっています。

任意のキーワード引数を受け付けてくれるように関数を設定するには、引数名の直前に半角のアスタリスクを二つ連続させて関数の引数に設定します。この引数にはよくkwargsという引数名が使われます。キーワード引数の英語のkey*word **argumentsを短縮させた引数名となります。2つのアスタリスクと共に`*kwargs`といったように書かれます。

コードを書いて試してみます。

def print_kwargs(**kwargs):
    print(kwargs)


print_kwargs(x=100, y=200)

コード実行結果の出力内容:

{'x': 100, 'y': 200}

 

指定された各キーワード引数はkwargsの中に格納されます。kwargsは辞書になっており、キーには引数名(今回のサンプルではxやy)が設定され、各値(今回のサンプルでは100や200)が格納されます。

本章の演習問題 その1

[1]. 整数の引数を1つ受け取り、その引数の値を2倍にして返却する関数を作ってみましょう。また、その関数を実行し受け取った返却値をprintで出力してみましょう。引数名は何でも大丈夫です。

[2]. 以下のコードではエラーになってしまいました。エラーにならないように修正してみましょう。

def print_value(value):
print(value)
  File "<ipython-input-21-def08fe6f21b>", line 2
    print(value)
        ^
IndentationError: expected an indented block

※答案例は次のページにあります。

演習問題その1の答案例

[1]. 関数を作るにはdef 関数名():といったように書きます。また、問題では「整数の引数を1つ受け取り」とあるので、答案例ではxという引数を設定しています(別の名前でも構いません)。

def multiply_two(x):
    x *= 2
    return x


multiplied_x = multiply_two(100)
print(multiplied_x)

コード実行結果の出力内容:

200

 

また、返却値を設定するには関数内でreturn 返却値といったように書きます。上記のコードではreturn x部分が該当します。

[2]. 関数の中にはインデントが1つ必要です。インデントは半角のスペース4つで設定します。スペースの数が4つ以外(2つなど)でもエラーにはなりませんが、Pythonのコーディング規約で4つと定められているため、特殊な理由などが無い場合を除いて4つのスペースを設定します。

以下のようにprint(value)部分の前にインデントが加えられたコードにすればエラーは発生しません。

def print_value(value):
    print(value)

本章の演習問題 その2

[1]. 以下のようにdog_ageという変数にプラス1する処理の関数を作って実行してみたところエラーになってしまいました。エラーが出ないようにコードを編集してみましょう。

dog_age = 10


def increment_dog_age():
    dog_age += 1


increment_dog_age()
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-23-37cd012f49bf> in <module>
      6 
      7 
----> 8 increment_dog_age()

<ipython-input-23-37cd012f49bf> in increment_dog_age()
      3 
      4 def increment_dog_age():
----> 5     dog_age += 1
      6 
      7 

UnboundLocalError: local variable 'dog_age' referenced before assignment

[2]. 3つの引数x, y, zを受け付ける形で、それぞれの引数の合計を返却する関数を作ってみましょう。また、その関数を実行してみて結果をprintで出力してみましょう。

※答案例は次のページにあります。

演習問題その2の答案例

[1]. dog_ageという変数は整数です。整数のグローバル変数は関数内では直接参照はできません。整数のグローバル変数を関数内で参照するにはglobalと変数を指定するか、もしくは引数と返却値を使ってローカル変数として扱う必要があります。

関数内でglobalと指定するケースでは以下のように書きます。エラーなく実行され、結果のdog_ageの値がプラス1されて11になっていることが確認できます。

dog_age = 10


def increment_dog_age():
    global dog_age
    dog_age += 1


increment_dog_age()
print(dog_age)

コード実行結果の出力内容:

11

 

もしくは引数と返却値を使って以下のようにも書けます。どちらを使っても同じ結果にはなりますがグローバル変数を関数内で使用することは可能であれば減らした方が好ましいので、基本的にはこのような書き方が推奨されます。

dog_age = 10


def increment_dog_age(dog_age):
    dog_age += 1
    return dog_age


dog_age = increment_dog_age(dog_age)
print(dog_age)

コード実行結果の出力内容:

11

 

[2]. 複数の引数を設定したい場合には、半角のコンマを各引数の間に設定します。コンマの後には半角のスペースを1つ入れます。

def calculate_total(x, y, z):
    return x + y + z


total = calculate_total(100, 200, 300)
print(total)

コード実行結果の出力内容:

600

 

本章の演習問題 その3

[1]. 以下のコードを実行するとエラーになってしまいます。エラーが出ないように調整してみましょう。

def calculate_x_plus_y(x, y):
    total = x + y
    return total


calculate_x_plus_y(100)
      4 
      5 
----> 6 calculate_x_plus_y(100)

TypeError: calculate_x_plus_y() missing 1 required positional argument: 'y'

[2]. xという名前の引数で、指定を省略できる形で、且つ指定が省略された場合に100の値が設定される引数を持った関数を作ってみましょう。関数の処理内容は何でも大丈夫です。

[3]. 以下の関数でyの引数だけ指定する形で関数を実行してください。

def calculate_total(x=100, y=200, z=300):
    total = x + y + z
    return total

※答案例は次のページにあります。

演習問題その3の答案例

[1]. x, yと2つの引数を必要とする関数である一方で、関数の実行箇所が1つの引数しか指定されていません。関数の実行箇所で引数を2つにすればエラーが無くなります。

def calculate_x_plus_y(x, y):
    total = x + y
    return total


total = calculate_x_plus_y(100, 200)
print(total)

コード実行結果の出力内容:

300

 

[2]. 省略が可能な引数を指定するには引数にデフォルト値を設定します。デフォルト値を設定するには引数=デフォルト値という形で間に半角のイコールの記号を設定します。イコールの前後にスペースは入れずに記述します。今回の問題ではx=100というように書きます。

def print_x(x=100):
    print(x)


print_x()

コード実行結果の出力内容:

100

 

[3]. 3つある引数のうち、真ん中のyだけ引数を指定したい場合にはキーワード引数を使います。キーワード引数を利用するには関数を実行するときに引数名=値といった形で指定します。今回の問題ではy=500といった形式で指定します。

total = calculate_total(y=500)
print(total)

コード実行結果の出力内容:

900

 

本章の演習問題 その4

[1]. 以下のコードのarg_value_listのリストの変数を、calculate_total関数の引数へリストのまま指定して関数を実行してみてください。

arg_value_list = [100, 200, 300]


def calculate_total(x, y, z):
    total = x + y + z
    return total

※補足 : 以下のようにリストの値を個別に引数に指定せずに、リストのまま指定してみてください。

calculate_total(
    arg_value_list[0],
    arg_value_list[1],
    arg_value_list[2],
)

[2]. 任意の個数の引数(3つだったり5つだったり)を受け付ける関数を作ってみてください。関数の処理内容は何でも構いません。

※答案例は次のページにあります。

演習問題その4の答案例

[1]. リストの中の各値を引数に割り振るには、リストの直前に半角のアスタリスクを指定します。今回の問題では*arg_value_listといった書き方をします。

total = calculate_total(*arg_value_list)
print(total)

コード実行結果の出力内容:

600

 

[2]. 任意の個数の引数を受け付けてくれる関数を作るには、関数の引数設定部分に*argsといったように半角のアスタリスクを引数名の前に設定します。

def print_args(*args):
    print(args)


print_args(100, 200, 300)

コード実行結果の出力内容:

(100, 200, 300)

 

章のまとめ

  • 一連のプログラムのまとまりで、色々な場所から実行でき、引数や返却値などを使って様々な挙動を実装できる機能を関数と言います。
  • 関数を使うことで、コードの重複などを減らすことができ保守性の高いコードを書くことができます。
  • 関数を作るにはdefというキーワードを使います。
  • 関数内では半角スペース4つのインデントが必要です。インデントを入れないとエラーになります。
  • 関数を実行するには関数()といったように書きます(例 : calculate_total())。
  • 数値や文字列のグローバル変数は関数内では直接参照できません。globalを使って対象の変数がグローバル変数だと明示する必要があります。
  • 辞書やリストなどはglobalの指定をしなくても関数内で参照できます。
  • 引数を関数に設定することで関数に様々なパラメーターを渡すことができます。
  • 省略可能な引数を設定したい場合にはデフォルト値を使います。
  • キーワード引数を使うことで引数名と値を両方指定して引数の値を指定することができます。引数が多い場合などに便利です。
  • 引数に数値や文字列などを指定した場合は、その値はコピーされ関数内のローカル変数として扱われます(値渡し)。
  • 引数にリストや辞書などを指定した場合にはその値はコピーされません(参照の値渡し)。
  • 半角のアスタリスクを引数部分に利用することで、リストや辞書を引数に展開したり、任意の個数の引数や任意の引数名のキーワード引数を受け付ける関数を作ることができます。

他にもPythonなどを中心に色々記事を書いています。そちらもどうぞ!
今までに投稿した主な記事たち

参考文献・参考サイト

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
4