5. 関数定義とその動作#

5.1. 背景#

これまでにPython概観比較演算子を通して基本的な型とそれらに対する演算子を眺めてきた。これである程度のプログラムを書くことができるようになったわけだが、実行したいコードを直接記述形では再利用性が低い。ここでいう再利用性とは「一度書いたコードを自由なタイミングで利用できる度合い」のことを指す。例えばある対戦ゲームでステージを用意するシーンを想像してみよう。ステージを用意しようとする度に同じプログラムを書くのはナンセンスに感じないだろうか。これに対する解決策の一つが 関数(function) だ。

これまでに用いた関数にはprint関数やtype関数があった。これらに共通している点は 関数名(引数) という書式で実行することと、引数に応じて動作が異なることだ。即ち関数とは 指定した引数に応じて、予め指定された処理を行う。この関数を独自に記述できるようになれば前述の再利用性を高めることができる。それと同時に指定された処理を関数として定義することで可読性が高まり、関数を利用する側のプログラムと定義する側のプログラムを分類することで修正等を行いやすくなる。


5.2. 関数定義#

_images/function1.svg

Fig. 5.1 関数定義#

関数定義はdef文を用いて def 関数名(引数): のように書き始める。続けて、ブロック(block) により関数の中身を記述するという形式で記述する必要がある。ブロックとは空白を用いた インデント(indent) によりコードの冒頭を揃えて指する。すなわち次のように記述する。

def 関数名(引数):
    命令1
    命令2
    命令3
    ...

次に具体例を眺めてみよう。

# 関数定義の例1
def multiply(x, y):
    answer = x * y
    return answer

# 以下は関数定義した関数multipyを呼び出して実行している例。
result = multiply(2, 3)
print(result)

# 引数の名称を使って書くこともできる。
result = multiply(x=2, y=3)
print(result)

def文から始まる3行が 関数定義(function definition) の例である。それ以降はブロックが異なるため、通常通り逐次処理されるコードである。result = multiply(2, 3) は、定義した関数を呼び出して実行している例だ。関数定義では以下のことを行っている。

  • def multiply(x, y): により、multiplyという名前の関数を定義している。

    • この関数は2つの引数を要求しており、第1引数をx、第2引数をyと呼ぶことにしている。この変数名は関数内部でのみ使われるものであり、名称は自由に変更して構わない。

  • 2行目と3行目には謎の空白を含むコードが書かれている。この空白が インデント(indent) であり、半角スペース4つ をコードの前に挿入している。このインデントが揃っている部分がブロックである。上記の例では result = multiply(2, 3) はその前のコードとインデントが揃っていないため、異なるブロックである。このように関数定義ではインデントでブロックを記述することで「どこからどこまでが関数のコードなのか」を明示している。

    • 2行目は、これまでに習ったことを記述しているが、関数定義の時点では変数x, yの中身はどこにも存在していないことに注意しよう。関数定義の時点では「指定した引数を受け取ったら、こういうふうに処理させたい」というコードを記述していくことになる。

    • 3行目は、この関数を実行した後で呼び出し元に変数areaの中身を返している。

      • 呼び出し元とは、例えばその下に書いている result = multiply(2, 3) のことだ。このコードでは変数resultに関数multiplyの実行結果を保存しようとしている。関数の実行結果を利用するためには、関数ブロック内で return文 を記述し、呼び出し元に返したい値を返してやる必要がある。

Note

関数定義とは独自に関数を定義することを指す。あくまでも定義しているだけで実行していない点に注意しよう。すなわち関数定義せずに hoge = 1 のように書いたコードはその場で実行されるが、def文により関数定義を始めた部分はその時点では「関数を定義する」ということだけを実行する。関数の実行は、print関数のように「関数が呼び出された時点」で実行されることになる。定義と実行のタイミングは独立している。

Note

関数ブロックを実行中に return文 に到達すると、その時点でブロックの実行を終了して呼び出し元に戻る。下記コードを実行して確認してみよう。

def add(a, b):
    return a + b
    return a * b

print(add(2, 3))

Note

return文 は、関数呼び出し元に値を返したいときだけ記述する。不要であれば書かなくとも良い。例えばprint文を実行したいだけの関数ならば以下のように定義して良い。また関数定義した後ならば好きなタイミングで何度でも呼び出して実行することができる。再利用性が向上していることが分かるだろう。

def welcome(name):
    print('{}さん、ようこそ'.format(name))
    print('これから授業を始めます')

welcome('naltoma')
welcome('牧瀬紅莉栖')

検討

変数xを入力したら \(2x^2 + 1\) の算出結果を返す関数を実装してみよう。関数名を equation とする。

回答例
def equation(x):
    return 2 * x**2 + 1

#動作確認
print(equation(0))
print(equation(1))
print(equation(2))
print(equation(3))

5.3. 関数を実行する様子#

_images/function2.svg

Fig. 5.2 関数を実行する様子#

実行した関数を実行する様子を図示した。

  • def文を書いた時点では関数定義しただけである。まだ実行していない。

  • 関数定義ブロックを抜けると定義終了となる。

  • width = 2 以下は実際に実行するコードである。変数widthに2を保存し、変数heightに5を保存する。

  • area = multiply(width, height) により、関数multiplyの実行結果を変数areに保存しようとしている。

    • = は、右辺の評価結果を左辺に保存するという動作を思い出そう。ここでは multiply 関数を実行し、その戻り値を変数areaに保存することになる。

    • このように関数呼び出しが行われたタイミングで、その関数定義のコードに移動する。すなわち、後でこの場所に戻ってこれるように「この場所」をメモとして残しておきつつ、実行箇所を def multiply(x, y): に移動する。関数multiplyでは、呼び出し元で指定されていた第1引数widthを関数内では変数xと名付け直し、呼び出し元で指定されていた第2引数heightを関数内では変数yと名付け直す。

    • 名付け直した後は関数ブロック内のコードを実行する。answer = x * y により変数answerに10が保存される。

    • return answer により変数answerの中身である10が、呼び出し元に返される。この返すタイミングで関数実行を終了する。

    • area = multiply(width, height) の右辺の実行結果として10が返ってくるので、変数areaに10が保存される。

  • print(area) により10が出力される。

  • print(answer) は変数answerを出力しようとしているが、そのような変数はないため NameError が返ってくる。

    • 先程関数multiplyの関数ブロック内では変数answerを利用した。このブロック内であればasnwerは利用できるが、関数外に移動した時点で利用できなくなってしまう。このように関数内部でのみ利用できる変数のことを 局所変数やローカル変数(local variables) と呼ぶ。

Note

NameError とは、未定義の変数や未定義の関数を利用しようとした際に出力されるエラーである。多くの場合はタイプミスか、局所変数を参照しようとした際に起こる。


5.4. ローカル変数とスコープ#

_images/function3.svg

Fig. 5.3 ローカル変数とスコープ#

関数内でのみ利用できる変数をローカル変数と呼ぶことを述べた。このような「利用範囲」のことを スコープ(scope) と呼ぶ。図に示す通り関数 multiply の内部で用いている変数 x, y, answer は全てローカル変数であり、このブロック内でのみ利用することができる。一番最後の print(answer) はブロック外にある、すなわち スコープが異なる ため、参照することができない。


5.5. 複数の戻り値#

return文で 複数の値をカンマ区切りで列挙 することで複数の値を呼び出し元に返すことができる。次のコードは2つのベクトル \(vect1 = (vect1\_1, vect1\_2), vect2 = (vect2\_1, vect2\_2)\) の足し算を求め、返す例だ。入力時には2つのベクトルの個々の要素4つの値を入力している。ベクトルの和では要素ごとに足し算をすることになるため分けて足し算し、要素ごとの値を返している。戻り値が2つあるため、関数呼び出し元でも カンマ区切りで変数を列挙 することで2つの値を受け取っている。

def add_vector(vect1_1, vect1_2, vect2_1, vect2_2):
    result1 = vect1_1 + vect2_1
    result2 = vect1_2 + vect2_2
    return result1, result2

result1_1, result1_2 = add_vector(1, 2, 3, 4)
print(result1_1, result1_2)

Tip

ここではまだ基本的な型・四則演算ぐらいしか触れていないため、それらを用いる方法で記述してみた。現実的にはベクトル演算はNumpyと呼ばれるライブラリを利用すると簡単に処理することができる。