1.11. Function(関数)

 数学や物理でfunction(関数)といえば、$\sin$とか$\exp$のような初等関数をイメージすると思うが、情報処理の分野では、「ある入力に対して、何か出力してくれる手続きのまとまり」という意味でも使われる。この”function”という概念は、どのプログラミング言語でも極めて重要な概念である。どの言語でも基本的なfunctionは組み込まれているし(これをbuilt-in function、組み込み関数という)、module内で誰かが作ったfunctionを使うこともできる。同様に、自分で新たなfunctionを定義することもできる。なお、情報処理の分野では入力のことを一般に引数(ひきすう, argument)と呼び、出力のこと戻り値(return)と呼ぶ。

 

 例として、$\sin ^2 x$を出力するfunction sin2を作る。

In [1]:
import numpy as np
import matplotlib.pylab as plt
 

 pythonでは、def 関数名(引数):のように関数名を定義して、文末にコロン(:)をつける。forループ、if文などと同じように、functionの中身の部分もインデントして書かなければいけない。

In [2]:
def sin2(x):
    s = np.sin(x) #sはあとで使う
    y = s**2
    return y
 

 まだ関数を作っただけで中身がない。定義域$~0 \leq x \lt 2 \pi~$の範囲で、$\sin x$と$\sin^2 x$をプロットして比較してみよう。

In [3]:
x = np.arange(0, 2*np.pi, 0.1)
plt.plot(x, np.sin(x))
plt.plot(x, sin2(x));
 
 

$\sin^2x$は周波数が2倍で正の値のみを取ることが分かる。

 

 

名前空間、スコープ、隠ぺい、カプセル化

 functionを使うとき注意したい点がいくつかある。まず、functionはinputからoutputを出力する装置なので、funtion内で使われている変数、たとえば、2行目のsはfunctionの外で読み出すことができない。したがって、

In [4]:
print(s)
 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-4-0ff1b7208845> in <module>
----> 1 print(s)

NameError: name 's' is not defined
 

とすると、sは定義されていないとpythonに怒られる。なぜこのような仕様になっているかというと、functionというものを、inputを入れたらoutputを返す単なるブラックボックスとして扱いたいからで、function中で使われている変数は外部に漏らしたくないからだ。このことをカプセル化や隠ぺいと呼んだりする。もしfunction内で使われている変数がfunctionの外で無制限に読み出せたとすると、他の部分で使われている変数名とfunction内で使われている変数がたまたま同じ名前だった場合、上書きされてしまう。だから上書きされないように、function内で使われている変数(これをローカル変数という)はreturnで返す以外は外に出られない仕様になっている。「じゃあ変数名が被らないように注意して名前を付ければいいじゃん」と思うかもしれないが、コード内の変数がすべて被らないようにしようとすると、たとえば巨大なコードを大人数で作ろうとした場合、プログラマー全員がすべての変数名のリストを持っていて、名前が被っていないかどうかいちいちチェックしないといけなくなり、作業量が膨大になる。一方、functionをカプセル化して変数を読み出せないようにしておけば、大人数で大きなコードを書く場合に、functionの中でどんな変数名が使われていようと名前が被る心配はない。そのfunctionを作る担当者以外はfunctionの中身を知らなくても問題ないので、作業効率が格段に上がる。また、変数の値を取り出せない仕様にしているので、functionを使い終わったら、物理メモリから値を消去することができ、メモリの消費を抑える効果もある。なお、コード全体で使いたい変数はグローバル変数と呼び、変数の有効範囲のことを名前空間(namespace)やスコープという。

 

 下の例では、メインの部分で定義されている変数achangeというfunctionで書き換えようとして、function内では書き換わっているものの、メインの部分では書き換わっていないことを示している。

In [1]:
a = 2 #グローバル変数
def change():
    a = 3 #ローカル変数
    print("a in function is" , a)
change()
print("a in main is" , a)
 
a in function is 3
a in main is 2
 

 ここで一つ注意したいことがある。上で定義したchangeというfunctionには引数がない。そこで、change()ではなくchangeとしてしまう間違いが入門者に非常に多い。正しい書き方であるchange()とすると、

In [6]:
change()
 
a in function is 3
 

 と正しく表示されるが、()を書き忘れて、changeとすると、

In [7]:
change
Out[7]:
<function __main__.change()>
 

 となってなんだか変な結果が返ってくる(この文の意味はclassと属性の構造が分かると理解できるようになる)。引数のないfunctionは、引数がないことを明示するために()を省略してはいけないのだが、そのことを忘れるとコードを書くときハマってしまうので気を付けてほしい。imshowでカラーバーを表示させたいとき、colorbar()のように()を付けなければいけなかったのは、colorbarが引数を書かなくてもよいfunctionであるためで、()を省略してはいけないからだ。

 

 

練習問題

(1.11.1.) 練習問題(1.9.2)で紹介したフィボナッチ数列を考える。桁数を入力したら、その桁数のフィボナッチ数列をすべて生成するfunction fibonacciを作れ。また、このfibonacciを使って、8桁までのフィボナッチ数列を求めよ。

 

(1.11.2.) 前問のfunction fibonacciを使って、ある整数を入力したとき、その整数に最も近いフィボナッチ数を与えるfunction find_fibonacciを作れ。

 

 

解答例

(1.11.1.) (1.9.2.)の解答をほとんどそのままfunctionに書き直す。ただし、桁数をnとしてfunction fibonacciの引数にする。

In [8]:
import numpy as np 
def fibonacci(n):
    F = np.array([0, 1])
    i = 1
    Fi = np.array([1])
    while np.log10(Fi) < n:
        F = np.concatenate([F, Fi])
        i += 1
        Fi = np.array([F[i-1] + F[i]])
    return F
 

 8桁までなので、引数に8と書けば答えがでる。

In [9]:
fibonacci(8)
Out[9]:
array([       0,        1,        1,        2,        3,        5,
              8,       13,       21,       34,       55,       89,
            144,      233,      377,      610,      987,     1597,
           2584,     4181,     6765,    10946,    17711,    28657,
          46368,    75025,   121393,   196418,   317811,   514229,
         832040,  1346269,  2178309,  3524578,  5702887,  9227465,
       14930352, 24157817, 39088169, 63245986])
 

 確認のため桁も調べておく。

In [10]:
print(np.log10(np.max(fibonacci(8))))
 
7.801032967581161
 

 

(1.11.2.) function fibonacciは別のfunctionの内部でも使用することができるので、find_fibonacci内でもこれを利用する。

In [11]:
def find_fibonacci(x):
    d = np.ceil(np.log10(x))
    F = fibonacci(d)
    index = np.abs(F-x).argmin()
    return F[index]
 

 比較したいある整数をxとして引数にする。fibonacciは桁数を引数にとっているので、xの桁数を求め、np.ceilを使った切り上げにより、それより一桁大きいすべてのフィボナッチ数列Fを求める。次にFの要素のうち、xに最も近い要素を差の絶対値が最小になることを利用して、np.abs.argminを使って求めるという流れ。.argminはその配列の中で最小の値を持つ要素の要素番号を与える属性である。例として、あるい整数xは適当に$22333$としてみた。

In [12]:
x = 22333
a = find_fibonacci(x)
print(a)
 
17711
 

 なお確認だが、find_fibonacciではフィボナッチ数列Fそのものではなく一番近いフィボナッチ数F[index]をreturnしているので、F自体は以下のように隠ぺいされていて、取り出すことはできない。

In [13]:
print(F)
 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-13-e87a1bc872bc> in <module>
----> 1 print(F)

NameError: name 'F' is not defined
 


当サイトのテキスト・画像の無断転載・複製を固く禁じます。
Unauthorized copying and replication of the contents of this site, text and images are strictly prohibited.
© 2019 Go Yusa