Programmierkurs
für Naturwissenschaftler/innen

関数

関数とは何か

プログラミングの文脈での関数とは通常、ある機能の単位を指す。 と言っても分かりにくいであろうから、具体的な例を見てみよう。

プログラミングをしていると、似たような作業を行う場合が良くある。 それを毎回書くと、どこかで間違ってしまうかもしれないし、プログラム全体の見通しも悪くなる。

簡単な例で見てみよう。

summation = 1 + 2 + 3
print("Sum of 1 ... 3 is", summation)

もしこの計算が一回だけならこれで問題ないが、何回も出てくる場合はどうだろうか。

summation = 1 + 2 + 3
print("Sum of 3 numbers from 1 is", summation)

summation = 5 + 6 + 7
print("Sum of 3 numbers from 5 is", summation)

summation = 10012 + 10103 + 10014
print("Sum of 3 numbers from 10012 is", summation)

数字は違うものの、ほとんど同じことを三回書いている。

問題は、

  • 単純に面倒くさい

  • どこかに間違いがあったら、三箇所直さなくてはならない

    • この例では全体が見えるので見落しにくいが、この処理がプログラムのあちこちに散らばっていたら、全てをもれなく修正するのは結構難しい

  • 2 番目とほぼ同じだが、後でより良い方法で書き直したくなったときに、全部直すのは面倒

ところで、上のプログラムに間違いがあることに気付いただろうか? もういちど見て、探してみよう。 答は最後に書く。

このような「動くけど結果が正しくない (しかもそれっぽい結果が出ている)」バグは発見が大変難しい。 こうした問題を避けるためにも、似たような処理は一箇所でやらせたい。

そこで、この処理を一つの機能として実装する。

def sum3(num_from):
    summation = num_from + (num_from + 1) + (num_from + 2)
    print("Sum of 3 numbers from", num_from, "is", summation)


sum3(1)
sum3(5)
sum3(10012)

ここでは sum3 という関数を定義している。 この関数は値を一つ受け取る (num_from)。 これを「引数(ひきすう)」という。 この関数は num_from + (num_from + 1) + (num_from + 2) を計算し、それを表示する。

このように書くと、間違いを起こしにくくなる。 また間違いがあってもこの関数を使っている全ての箇所で間違いが発生するはずなので、発見の可能性が高まる。 さらに、例えばメッセージの部分を変えたいと思ったとき、元のプログラムでは 三箇所も直さなくてはならないが、新しい方では一箇所直せばすむ。

複数回、似たような処理をしているときは、関数としてまとめることを検討すべきである

細かいことはこの後で説明するが、まずは関数を使ってみよう。

上のプログラムを入力し、実行してみよ。 大雑把にそれぞれの行でなにをやっているか想像してみよ。

実行結果

補足
ここでは説明のために上のように書いたが、実際には (状況にもよるが) sum3 関数には計算のみに専念させた方が良い。 この例では sum3 関数の中で結果表示までしてしまっており、 sum3 関数に複数の役割を持たせてしまっている。 原則として (例外はたくさんあるものの) 一つの関数は一つの仕事に専念させることで、修正や改良がやりやすくなることが多い。

なお、 sin 等の本来の(数学的な)関数は、プログラミングの文脈では「数学関数」と呼ぶことが多い。 これについてはこのページの後半で述べる。

関数を作る

では、改めて関数の書きかたについて学ぼう。 すでにおおよそ予想がついているとは思うが、以下のように書く。

定義

Python で functionname という名前の関数を定義するには、以下のように書く。

def functionname(arg1, arg2, ...):
    # ここに処理を書く

最初の def はそのように書く。

functionname は関数の名前である。 命名規則は変数と同様である。 好きなように付けてよいが、原則として、長くても分かりやすい名前を付けるのが良い。 既に使っている変数と同じ名前を使うことはできない1 (上書きされるので、共存できない)。

arg1, arg2, … は引数と呼ばれる変数である。 これも好きに名前を付けて良い。 この変数は この関数の中でのみ使える ものである。 これについてはスコープで詳しく見る。 引数の数は任意である。 引数が必要なければ、括弧 () のみ書けばよい。 例えば、

def some_function_without_arguments():
    # 引数を取らない関数


def some_function_with_one_argument(arg):
    # 引数を一つ取る関数


def some_function_with_two_arguments(arg1, arg2):
    # 引数を二つ取る関数

などのようになる。

そして def の行の最後に : を付ける。

関数が呼ばれた時に実行する内容を def の次の行以降に書く。 このとき、先頭に空白を付ける。 これを インデント (字下げ) という。

すでに見たように、例えば、

def sum3(num_from):
    summation = num_from + (num_from + 1) + (num_from + 2)
    print("Sum of 3 numbers from", num_from, "is", summation)


sum3(1)
sum3(5)
sum3(10012)

の場合は、 def の次の二行が関数 sum3 の内容 (定義) である。 最後の三行は sum3 の定義の外側にある部分であり、ここでこの関数を実際に使っている。

この構造を視覚的に表すと、次のようになっている。 どこが関数本体で、どこがそれ以外かをしっかり把握して欲しい。

def sum3(num_from):
summation = num_from + (num_from + 1) + (num_from + 2)
print("Sum of 3 numbers from", num_from, "is", summation)


sum3(1)
sum3(5)
sum3(10012)

インデントに使う空白の数はいくつでも良いが、Python では普通は 4 つである。 空白の数は定義の途中で変更できない (但し空行は入ってもよい)。

以下にいくつか例を挙げる。

# 正しくない書きかた
# (途中でインデント幅が変わっている)
def sum3(num_from):
    summation = num_from + (num_from + 1) + (num_from + 2)
      print("Sum of 3 numbers from", num_from, "is", summation)
# 正しくない書きかた
# (インデントがない)
def sum3(num_from):
summation = num_from + (num_from + 1) + (num_from + 2)
print("Sum of 3 numbers from", num_from, "is", summation)
# 正しいが、あまり望ましくない
# (インデント幅が 4 ではない)
def sum3(num_from):
  summation = num_from + (num_from + 1) + (num_from + 2)
  print("Sum of 3 numbers from", num_from, "is", summation)
# 正しいが、あまり望ましくない
# (インデント幅が 4 ではない)
def sum3(num_from):
        summation = num_from + (num_from + 1) + (num_from + 2)
        print("Sum of 3 numbers from", num_from, "is", summation)
# 正しい
# (途中に空行が入ってもよい)
def sum3(num_from):
    summation = num_from + (num_from + 1) + (num_from + 2)

    print("Sum of 3 numbers from", num_from, "is", summation)

返り値

返り値を返したい場合は、 return を使う。

例えば、 num_from から 3 つの数字を足して (答えを表示せずに) 結果を返す関数は、

def calc_sum3(num_from):
    summation = num_from + (num_from + 1) + (num_from + 2)
    return summation

と書ける。 この場合は、

def calc_sum3(num_from):
    return num_from + (num_from + 1) + (num_from + 2)

と書いてもよい。

返り値が不要な場合は、処理だけ書けば良い。

def say_hello():
    print("Hello, world")

関数内の変数

関数内で定義した変数は、その関数内でのみつかえる (これをローカル変数と呼ぶ)。

例えば、

def sum3(num_from):
    summation = num_from + (num_from + 1) + (num_from + 2)
    print("Sum of 3 numbers from", num_from, "is", summation)

と書いたとき、この summation はこの関数内でのみ使用可能な変数である。 これについてもスコープで詳しく見る。

小まとめ

def sum3(num_from):
    summation = num_from + (num_from + 1) + (num_from + 2)
    print("Sum of 3 numbers from", num_from, "is", summation)

において、

  • num_from は引数と呼ばれる変数であり、

  • summation は関数内で定義された変数 (ローカル変数) である

これらはいずれもこの関数内でのみ使える変数である。

関数を使う

関数の定義自体は、そのままでは何も実行しない (a = 10 としても見た目には何も起こらないのと同様である)。 そのためどこかでこの関数を使う (関数を呼ぶ、という言い方をすることが多い) 必要がある。

が、これもほぼ自明であろう。 上で定義した say_hello() を使いたければ、

say_hello()

と書けばよい。

返り値を使うには、それを直接使うか、一度変数に入れるかする。

sum3 = calc_sum3(10)
print(sum3)

この場合は、次のように書くこともできる。

print(calc_sum3(10))

完全なプログラムとして書くと、例えば次のようになる。

def calc_sum3(num_from):
    summation = num_from + (num_from + 1) + (num_from + 2)
    return summation


sum3 = calc_sum3(10)
print(sum3)

$x$ を引数に取り、$x^2 - 2$ を計算してその値を返す関数を書け。 また、この関数を使って、$x = 0, 1, 2$ のときのこの式の値を計算し、表示せよ。

実行結果

関数定義の場所

Python では関数定義は、最初に使われる (実行される) よりも前に書く必要がある。 すなわち、

def say_hello():
    print("Hello, world")


say_hello()

は問題ないが、

say_hello()    # エラー! Python は say_hello() が何かまだ知らない


def say_hello():
    print("Hello, world")

はエラーとなる。

補足

ここはやや発展的な内容なので、最初は読み飛ばしてよい。

少し分かりにくい例として、以下の場合がある。

# 正しく動くだろうか?

def say_hello_and_name(name):
    say_hello()    # まだ定義されていないけど...
    print("Hello,", name)

def say_hello():
    print("Hello, world")


say_hello_and_name("Einstein")

この場合、 say_hello_and_name()say_hello() が最初に「使われる」(「出現する」ではない) のは、 say_hello_and_name("Einstein") が実行される時である。 この文が実行されるまでに say_hello()say_hello_and_name() が定義されていれば良い。 従って、上の例は正しく動く。 すなわち、この例の場合は say_hello_and_name()say_hello() の定義の順番はどちらが先でも良い。

関数として書くことのメリット

関数として書くことのメリットとして、関数単体での改良をしやすいという点がある。 例えば、 sum10() という関数を次のように書いたとしよう。 (行末の \ は次の行に続くという意味である)

def sum10(num_from):
    summation = num_from + (num_from + 1) + (num_from + 2) \
              + (num_from + 3) + (num_from + 4) + (num_from + 5) \
              + (num_from + 6) + (num_from + 7) + (num_from + 8) \
              + (num_from + 9)

    return summation


print(sum10(10))
print(sum10(100))

ところが良くみると、次のようにすっきり書くことができることに気付く。

def sum10(num_from):
    summation = 10 * num_from + 45
    return summation


print(sum10(10))
print(sum10(100))

ポイントは、 プログラムの 関数以外の部分を変更することなく sum10() を改良できた、という点である。 上の例は、いかにも例のために作ったものであるから実感がわきにくいかもしれないが、このような場面はよくある。

ただし、「関数以外を変更することなく」改良するためには、引数と返り値が変わらない場合に限る。 従って:

関数を書く前に、引数と返り値について良く検討すべきである
と言える。

とは言え、これはあくまで理想論である。 どのような引数・返り値にすべきかの決定は経験によるところも大きい。 関数を改良する場合に、引数も変更したくなることは良くある。 最初のうちはあまり堅苦しく考えず、どんどん気楽に変更して、少しずつコツをつかんでいってもらいたい。

慣例

(変数ではなく) 関数であることを明確にするために func() のように関数名 + () と書くことがある。 例えば「上の例では sum10 という関数を作成した」と言う変わりに「上の例では sum10() を作成した」と書くことがある。 この場合は引数を省略しているだけで、引数を取らない関数だという訳ではないので、その点だけ注意しよう。

数学関数

ここまでは、自分で新しい関数を作る方法について述べてきた。 ここでは、Python に最初から用意されている関数についていくつか紹介する。

これまで使ってきた print() もPython に最初から用意されている関数の一つである。 他にもたくさんあるのだが、ここでは数学関数についてのみ述べる。

Python ではさまざまな数学関数が用意されている。 但し数学定数のときと同様に、 import math をプログラムの最初に書く必要がある。 これを一度書いておけば、それ以降で以下の関数 (や数学定数で説明した定数) が使えるようになる。

e-関数であれば、 math.exp(x) のように使う。 math. を付けるのを忘れないこと。

import math

a = math.exp(0.5)
print(a)

とすると、$\mathrm{e}^{1/2}$ を計算して表示する。

同様に sin であれば math.sin(x) のように使う。 三角関数の引数はラジアン単位であるので注意。 すでに述べたように、円周率 $\pi$ は math.pi という名前で定数が定義されているので、これを使うことができる。

import math

a = math.sin(math.pi / 3)    # sin 60°
print(a)

平方根は math.sqrt(x) である。 また e を底とする対数は math.log(), 10 を底とする対数は math.log10() である。

絶対値は abs(x) (math. は書かない) または math.fabs(x) で得られる (f は float (浮動小数点数) の意味である)。 通常はどちらを使っても問題ない2

完全な数学関数のリストは、 math — Mathematical functions (Python Documentation) を参照せよ。

クイズの答え

summation = 10012 + 10103 + 10014

summation = 10012 + 10013 + 10014

となるべきである (二つ目の数字は 10103 ではなく 10013)。 もちろんどちらでも数学的には何の問題がないが、プログラマーの意図とは異なっている。

繰り返しになるが、このような「動いてそれっぽい結果を出すが正しくない」バグは発見が大変難しい。 こうしたミスをいかに減らすか (ミスを犯しにくい、ミスをしても発見しやすい書き方) が大変重要である。

まとめ

ここでは関数について学んだ。

  • 関数定義は次のように書く

    def functionname(arg1, arg2, ...):
        # ここに処理を書く
    • arg1, arg2, … は引数と呼ばれ、この関数の中でのみ使用できる (好きな名前を付けてよい)

    • 関数の内容は インデント (字下げ) して書く

    • 返り値を返すには return を使う

    • 関数内で定義した変数は、その関数内でのみ使える

  • Python にもともと用意されている関数もたくさんある

  • 関数を書く際に気を付けるべきことはいろいろあり、それについてもいろいろ書いたが、 最初のうちは気軽にいろいろ試してみるのが良い 。 少し慣れてきたらもう一度このページを読み直し、これらの注意点について考えてもらえると良い。


1

この表現はいくぶん不正確であるが、ここでは深入りしない。

2

abs(x) は引数が整数型なら結果も整数型で、引数が浮動小数点型なら結果も浮動小数点型で返すが、 math.fabs(x) は引数が整数型でも浮動小数点型でも、結果を浮動小数点型で返す、という違いがある。