本当に誰でも読める!! Pythonソースコードの読み方講座(第5回) 関数にまつわるコードの読み方

  • HOME
  • 本当に誰でも読める!! Pythonソースコードの読み方講座(第5回) 関数にまつわるコードの読み方

本記事は当社が発行しているシミュレーションメールマガジンVol.5の記事です。 シミュレーションメールマガジンの詳細・購読申込はこちら のサポートページから

プログラマにもノン・プログラマにも読んでいただきたい「Pythonコードの読み方」講座の第5回です。 前回まではPythonのキーワードを集中的に扱っていましたが、今回は一度離れて、プログラミングで重要な要素である「関数」について扱いたいと思います。

例文1

この講座は「コードの読み方」を重視しますので、「関数とは何か?」という点は一旦後回しにしましょう。 早速例文になります。次のようなコードを目にしたとき、どのように読めばよいでしょうか?:

def speed(d, v0, vmax=None):
    """
    エッジ人口密度dとエージェントの最大移動速度v0, エッジの最大速度から移動速度vを求める
    ユーザーが編集できる
    numpyの配列演算で記述する
    """
    V = np.zeros(v0.shape, dtype=float)
    V[d < 1.5] = -0.204 * d[d < 1.5] + 1.48
    V[d >= 1.5] = 1.32 * np.log(9.16 / d[d >= 1.5]) / np.log(10)
    V = v0 * (V / 1.48)
    # K > 9.16 の場合、V < 0 になってしまう。
    V.clip(0.1, np.inf, out = V)
    return V

まず冒頭の "def" ですが、これはPythonのキーワード(予約語)になります。「これから関数を用意します」と宣言しているわけです。前回までに見た for や if などの構文を表すキーワードの仲間になり、これらと同様に

  • 行末にはコロン ":" を付ける
  • 改行後、段落下げする

という形式になっていることがわかります。

次にきている "speed" は、定義する関数の名前です。ここは(Pythonの変数名として妥当なものであれば)任意の文字列を書くことができます。

次にカッコの中を見ていきましょう。ここでは3つの名前 "d, v0" のあとに、 "vmax=None" というものが並んでいます。 これは以下のように読むことができます:

  • この関数 "speed" は3つのモノを与えて動かすことができる
    • それぞれに "d", "v0", "vmax" という名前を付ける
  • "vmax" だけは与えなくても良い。つまり、2つのモノを与えるだけでも動かすことができる。その場合には "vmax" の値は None になる [1]

イコール"="が使われている場合だけ特殊ですが、基本的には与えられたものに順番に名前を付けていると思えば良いでしょう。

また、2行目以降は以下のように読むことができます:

  • 「""" """」で囲われた部分: ソースコードの説明のためのコメントで、実行には関係ありません。この部分に関数の説明を書くのが一般的です。
  • それ以降: 関数の処理内容を示すコードを記載しています。 実際に "d", "v0" といった名前で与えられたモノを使っている様子が確認できます(ゴツいので何をやってるかは気にしなくて問題ありません)。
  • "return (何か)": これもPythonのキーワード引数で、「呼び出した先に(何か)を返す」のように読みます。後述するように関数はどこかから呼び出される(使われる)ので、呼び出したところに結果を返してあげる必要があります。 [2]

簡単にまとめると、以下のように認識すればOKです:

  • Python コードで "def" の行は「関数(一連の処理をまとめたもの)」を用意している
  • "def" の後ろに関数名、その後ろのカッコ内に「与えるモノの名前」がつづく
    • "=(何か)" が続いている場合はそのモノは与えなくても動く ((何か)が採用される)
  • "return (何か)" がある場合、呼び出した先に(何か)が返される

実際にコードを「読む」上では関数の下に書かれているコメントも重要な手がかりになるでしょう。そこに書かれている情報から、関数が何を受け取って何を行い、何を返すかが明らかになります。逆にコメントが書かれていない場合、処理を行っているコードからその関数が何をするかを読み解くことになるため、コードを理解することが困難になってしまいます。 この記事を読んでいる方が関数を「書く」ことがあれば、読む人のためにコメントを書くことを強く推奨します。

  • None も Python のキーワードの一つで、「何者でもない」ような唯一のモノを指しています. ここでは気にしなくてもOKです。
  • return は記述しなくても問題ありません。その場合、呼び出した先には "None" が返されるという約束になっています。

例文2

さて、例文1は関数を用意する部分でした。実際にこの関数を利用している例文も見ておきましょう。例文1のコードに続いて、以下のように書かれていたとします:

V = speed(dense[e2e[E]], maxVs)

まずはイコール "=" の右側に注目しましょう。実際に例文1で "def" に続けて書いた名前 "speed " を使って呼び出しています。カッコ内には(その実態はさておき)カンマで区切られた2つのモノが渡されています。このそれぞれが例文1の コードの "d", "v0" に対応します. この例では2つのモノだけ与えていますので、3番目の "vmax" は "None" が入ります。 また、左側の "V = " とあるのは、「speed の実行結果("return" したモノ) を "V" という名前に格納する」と読むことができます。今回はたまたま例文1で "return" した "V" がそのまま例文2でも "V" として格納されていますが、この名前は必ずしも同じである必要はありません。

仮に例文1と例文2をそのままつなげたコードがあったとしてそれを実行した場合、処理の順番は、以下のようになります:

def speed(d, v0, vmax=None):
    """
    エッジ人口密度dとエージェントの最大移動速度v0, エッジの最大速度から移動速度vを求める
    ユーザーが編集できる
    numpyの配列演算で記述する
    """
    V = np.zeros(v0.shape, dtype=float)
    V[d < 1.5] = -0.204 * d[d < 1.5] + 1.48
    V[d >= 1.5] = 1.32 * np.log(9.16 / d[d >= 1.5]) / np.log(10)
    V = v0 * (V / 1.48)
    # K > 9.16 の場合、V < 0 になってしまう。
    V.clip(0.1, np.inf, out = V)
    return V

V = speed(dense[e2e[E]], maxVs)
  1. 例文1の "def" 以下のが処理される。"speed" 関数が定義される
  2. 例文2の "speed" に渡すモノが準備される("dense[...]" などの部分の処理)
  3. 例文1の "speed" 関数の処理が走る. ただし、"d", "v0", "vmax" にはそれぞれ "dense[e2e[E]]", "maxVs", "None" として扱われる.
  4. 例文1の "return" 以下に書かれた "V" が例文2で "V" という名前で格納される

まとめ

「関数」に関連して、"def" や "return" といったキーワードを交えながらコードの読み方を紹介しました。 「関数」が何なのか、ということは結局説明しませんでしたが、翻って、今回のように処理の一部分を外に切り出したもの、だと思えばよいでしょう。 プログラムを書いていると、同じような処理を複数箇所で行いたくなることがあります。そのようなときにその処理を関数としてまとめておけば、1回記述するだけでその処理を2回目以降は簡単に実行できるようになります。

コードを「読む」うえでは、"def" を見たらそのような「処理のまとめ」を行っているのだと思い、引数(カッコ内)やコメント、"return" 文の行などから大まかにどのような処理なのかを把握することからはじめるのが良いでしょう。細かくコードを追うのはそれからでも遅くありません。

豊岡 祥 株式会社 NTTデータ数理システム シミュレーション&マイニング部所属。
S4 Simulation System の開発のほか、機械学習・シミュレーション・数理最適化を幅広く扱い、分野を横断した問題解決に取り組んでいる。
入社後に競技プログラミングをはじめ、PGBATTLE2023にて企業の部団体3位入賞。

著書: 「XAI(説明可能なAI)──そのとき人工知能はどう考えたのか?」リックテレコム
趣味: 合唱
「数理科学の基礎知識」e-book無料ダウンロードはこちら