pythonでの関数とクラスの呼び出し(関数の引数を関数にすることの有用性やクラスの継承など)

#関数の呼び出し
ものすごく当たり前のことですが、関数から**他の関数**を呼び出すことができます。
例えば、hello()という"hello"を１回する出力する関数を定義したうえで、hello_n(n)という"hello"をn回出力する関数を定義する際には、最初に定義したhello()という関数を特別な宣言をせずに利用できます。

~~~python
>>> def hello():
	print("Hello!")

	
>>> def hello_n(n):
	for i in range(n):
		hello()

		
>>> hello_n(3)
Hello!
Hello!
Hello!
~~~
また、**他の関数ではなく定義している関数そのもの**すら呼び出すことができます。(これを再帰的プログラムといいます）

~~~python
def hello_n(n):
	if n==0:
		pass
	else:
		hello_n(n-1)
		hello()

		
>>> hello_n(4)
Hello!
Hello!
Hello!
Hello!
~~~
さらに、関数はクラスの中からでも呼び出すことができます。また、クラスの中の関数も再帰的に利用することができます。次のような"Hellos"という名前のクラスを定義し、その中でさきほど定義した関数hello()を呼び出すことができます。

~~~python
>>> class Hellos:
	def hello_n(self,n):
		if n==0:
			pass
		else:
			self.hello_n(n-1)
			hello()

			
>>> h=Hellos()
>>> h.hello_n(4)
Hello!
Hello!
Hello!
Hello!
~~~
#関数を関数の引数にする
また、関数は関数を引数にすることができます。関数を関数の引数にというと関数解析や汎関数が思い浮かびますが(そんなのは数学か物理を専攻にした人ぐらい？？)、例えば関数の内積を明示的に定義できます。
ある関数f(x)とg(x)を区間[a,b]で積分するコードを書けば、(もっと精度のいい近似方法もありますが)次のようになります。([a,b]を[-∞，∞]とすればまさに関数の内積です)

~~~python
def  inner_product(f,g,a,b):
	dx=0.000001
	x=a
	s=0
	while x<=b:
		s+=f(x)*g(x)*dx
		x+=dx
	return(s)
~~~

f_functionとg_functionという関数を具体的に定義して実行してみると次のようになります。

~~~python
>>> import math as m
>>> def f_function(x):
	l=1.0
	return(m.sqrt(2/l)*m.sin(m.pi*x/l))

>>> def g_function(x):
	l=1.0
	return(m.sqrt(2/l)*m.cos(m.pi*x/l))

>>> inner_product(f_function,g_function,0,1.0)
1.3272813108025964e-11
>>> inner_product(f_function,f_function,0,1.0)
0.9999999999964505
>>> inner_product(g_function,g_function,0,1.0)
1.0000000000035023
~~~
(※)なお、正しい解は上から、0,1,0 ですので悪くない精度です。

このように、関数を引数にすることで１つのinner_productのコードで、任意のf(x)とg(x)を処理すること可能になります。これを前節の関数の呼び出しを利用して記述すると、f(x)とg(x)が変わるたびにinner_productを書き換える必要がありますので、「関数を引数にできる」というpythonの機能は一見奇妙に思えますが(私自身も最初はこんなややこしい機能は誰も使わないだろうと思ってました)、非常に有用なものです。

さて、次のある数列a_kを生成する関数a(k)(k=0,1,2,3,･･･)があり、その数列による多項式、

```math

f(x)= \sum_{k=0}^n a_kx^k

```
を生成する関数のコードを作ってみます。(a_kをテイラー展開の係数とすればテイラー展開の数式を求めています。)

~~~python
>>> def f_function(n,a):
	f=str(a(0))
	x="x"
	for k in range(n):
		if a(k+1)<0:
			f+="-"+str(abs(a(k+1)))+"x^"+str(k+1)
		else:
			f+="+"+str(a(k+1))+"x^"+str(k+1)
	return(f)
~~~
これは数列を生成する関数a(k)の機能をf_functionは**利用**して多項式を生成するとも言えます。具体的にa(k)を定義するとこんな感じに実行されます。

~~~python
>>> def a_function(n):
	if n==0:
		return(1)
	else:
		return(1/n/a_function(n-1))

	
>>> f_function(4,a_function)
'1+1.0x^1+0.5x^2+0.6666666666666666x^3+0.375x^4'
~~~
この例では、a(k)として指数関数exp(x)のテイラー展開の係数を生成する関数としたので、f_function(n,a)はa(k)の機能を**利用**してn次までの指数関数exp(x)のテイラー展開の数式を生成する関数として機能します。
また、代わりにa(k)として三角関数sin(x)のテイラー展開の係数を生成する関数を与えれば、f_function(n,a)はa(k)の機能を**利用**してn次までの三角関数sin(x)のテイラー展開の数式を生成する関数として機能します。
その他の任意の関数についても同様に機能します。
しかし、ここが後述する**クラス継承**との大きな違いですが、f_function(n,a)はa(k)自体の生成に修正を加えたり、機能を追加したりということはできません。例えば、exp(2x)のテイラー展開はx⇒2xに置換するだけでなので、f_function(n,a)の中を修正すれば簡単にコードが作成できますが(a(k+1)⇒a(k+1)＊2^(k+1))、f_function(n,a)については同じ形式を保ったままで、a(k)自体を変更し、exp(2x)を生成するように機能を変えたり、あるいはexp(x)とexp(2x)の両方を生成するように機能を追加したりといったことはできません。
これが可能になるのが、クラスがクラスを引数にするようなフォーマットで記述される**継承**です。

#クラス内での関数の呼び出し
関数が他の関数を呼び出すことができるように、クラスの内部で定義した関数はクラスの内部で呼び出すことができます。
今では当たり前のことですが、私が最初に購入したテキストが低レベルであったためか、クラスの内部で定義したある関数を同じクラスの中で定義した別の関数から呼び出すようなコードが掲載されておらず、クラスを知ってしばらくはできないものと思い込んでしまいました。そのせいで、クラスの中で定義した関数をクラスの中で引数にしてみたり試行錯誤しましたが、クラス内で定義した関数を同じクラス内の別の関数で利用する場合は、関数名の前に｢self.｣を付ければ利用できます。
当然ですが、関数hello()をclass Hellosの中で定義し、同じclass Hellosの中でhello_n(n)から呼び出すことができます。

~~~python
>>> class Hellos:
	def hello(self):
		print("Hello")
	def hello_n(self,n):
		if n==0:
			pass
		else:
			self.hello_n(n-1)
			self.hello()
			
>>> h=Hellos()
>>> h.hello_n(3)
Hello
Hello
Hello
~~~



#クラスから別のクラスの呼び出し
それでは関数と同じように、クラスから別のクラスを呼び出すことは可能でしょうか？関数との類似性を考えたらできるような気がします。
そこで今度は、"Hello"を１回出力するclass Helloと、classs Helloを呼び出して"Hello"をn回出力するclass Hellosを定義して実行してみます。


~~~python			
>>> class Hello:
	def hello(self):
		print("Hello")

>>> class Hellos:
	def hellos(self,n):
		if n==0:
			pass
		else:
			self.hellos(n-1)
			h=Hello()
			h.hello()

>>> h=Hellos()
>>> h.hellos(3)
Hello
Hello
Hello
~~~

できました。クラスから別のクラスを呼び出すことは可能です。

#クラスの継承
クラスを継承するには、次のようにクラスを定義する際に引数として継承するクラスを記入します。
ここでは、クラスFactrialで階乗を計算する関数を定義し、クラスPermutationで継承しています。
クラスPermutationは継承するだけで何も定義していませんが、これだけでクラスFactrialの機能を利用できることがわかります。なお、factorial(n)はn!(nの階乗)を計算します。

~~~python
>>> class Factrial:
	def factorial(self,n):
		if n==0:
			return(1)
		else:
			return(n*self.factorial(n-1))

		
>>> class Permutation(Factrial):
	pass

>>> c=Permutation()
>>> c.factorial(3)
6
~~~
次にこのクラスPermutationに｢順列｣の計算を行うpermutation(n,k)を定義し、その計算にこのfactorialを利用します。クラスの中で定義した関数を同じクラスの中で定義する別の関数で使用する場合には、｢self.｣を関数の前に付けてコードを記述しましたが、継承元のクラス(スーパークラス)の関数を継承受のクラス(サブクラス)で使用する場合には、｢super().｣をスーパークラスの関数の名前の前に付けます。
　なお、このpermutation(n,k)の計算方法は次の公式によっており、効率が悪いですが後ほど修正します。

```math
permutation(n,k)={}_{n} P_k=n!/(n-k)!
```

~~~python
>>> class Factrial:
	def factorial(self,n):
		if n==0:
			return(1)
		else:
			return(n*self.factorial(n-1))

		
>>> class Permutation(Factrial):
	def permutation(self,n,k):
		return(super().factorial(n)/super().factorial(n-k))

	
>>> c=Permutation()
>>> c.permutation(5,3)
60.0
~~~
さて、単にスーパークラスの関数を利用するだけでは継承といっても、関数を別の関数から呼び出すのと大差ありません。しかし、クラスの継承ではスーパークラスの内容をサブクラスで上書き(**オーバーライド**)して関数の内容を変更することができます。これを示すために、まずクラスPermutationの中に階乗を計算する関数factorialを記述し、今度はクラスCombinationでPermutationを継承し関数permutationの内容を次の公式によるものにオーバーライドします。
なお、オーバーライドはスーパークラスと同じ名前の関数を再定義することで可能になります。

```math
permutation(n,k)={}_{n} P_k=n･(n-1)･(n-2)･･･(n-k+1)
```

~~~python
>>> class Permutation:
	def factorial(self,n):
		if n==0:
			return(1)
		else:
			return(n*self.factorial(n-1))
	def permutation(self,n,k):
		return(self.factorial(n)/self.factorial(n-k))

	
>>> p=Permutation()
>>> p.factorial(3)
6
>>> p.permutation(4,2)
12.0
>>> class Combination(Permutation):
	def permutation(self,n,k):
		if n==k:
			return(1)
		else:
			return(n*self.permutation(n-1,k))	
>>> c=Combination()
>>> c.permutation(4,2)
12
~~~
p.permutation(4,2)と実行した場合は小数点以下第１位が表示されていますが、c.permutation(4,2)で実行すると小数点以下第１位が表示されないことに、計算方法の違いが表れています。
なお、オーバーライドされても、次のようにsuper().を使うとスーパークラスのpermutationを用いても計算することができます。従って、元の計算方法を利用したい場合はいつでもサブクラスから元の関数を利用することができます。

~~~python
>>> class Combination(Permutation):
	def permutation(self,n,k):
		if n==k:
			return(1)
		else:
			return(n*self.permutation(n-1,k))
	def s_permutation(self,n,k):
		return(super().permutation(n,k))

>>> c=Combination()
>>> c.permutation(6,3)
120
>>> c.s_permutation(6,3)
120.0
~~~
さて、クラスCombinationの中に今度は｢組み合わせ｣の計算をする機能を追加したいのですが、combination(n,k)は次のようにpermutation(n,k)から計算されるので、継承したPermutationを用いて簡単に記述することができます。

```math
combination(n,k)={}_{n} C_k={}_{n} P_k/k!

```


これにより、クラスCombinationは階乗の計算、順列の計算及び組み合わせの計算という３つ機能を有するようになりました。

~~~python
>>> class Combination(Permutation):
	def permutation(self,n,k):
		if n==k:
			return(1)
		else:
			return(n*self.permutation(n-1,k))
	def combination(self,n,k):
		return(super().permutation(n,k)/super().factorial(k))

	
>>> c=Combination()
>>> c.permutation(5,3)
20
~~~
また、次のようにクラスFactrialをスーパークラスとし、クラスPermutationが継承し、さらにクラスCombinationがクラスPermutationをさらに**再継承**することもできます。

~~~python
>>> class Factrial:
	def factorial(self,n):
		if n==0:
			return(1)
		else:
			return(n*self.factorial(n-1))

		
>>> class Permutation(Factrial):
	def permutation(self,n,k):
		return(super().factorial(n)/super().factorial(n-k))

	
>>> class Combination(Permutation):
	def combination(self,n,k):
		return(super().permutation(n,k)/super().factorial(k))

	
>>> c=Combination()
>>> c.factorial(3)
6
>>> c.permutation(6,4)
360.0
>>> c.combination(6,4)
15.0
~~~

さらに、クラスPermutationを独立したコード(クラスFactrialを承継しない）とし、クラスCombinationがクラスFactrialとクラスPermutationの両方を同時に継承する(**多重継承**)ことも可能です。

~~~python
>>> class Factrial:
	def factorial(self,n):
		if n==0:
			return(1)
		else:
			return(n*self.factorial(n-1))

		
>>> class Permutation:
	def permutation(self,n,k):
		if n==k:
			return(1)
		else:
			return(n*self.permutation(n-1,k))

		
>>> class Combination(Factrial,Permutation):
	def combination(self,n,k):
		return(super().permutation(n,k)/super().factorial(k))

	
>>> c=Combination()
>>> c.factorial(3)
6
>>> c.permutation(6,2)
360
>>> c.combination(6,2)
180.0
~~~