Webページでは数式を表すためにLaTeX表記が使えるMathJaxを利用しています。
WebブラウザにはSafari/Chrome/Firefoxを使って下さい(IEでは表示できないようです。)
オブジェクト指向--有理数計算を例にして
Pythonはオブジェクト指向プログラミング言語(OOP: Object-Oriented Programming language)の1つで、標準的に提供されている豊富なクラス(データ構造とそれができること:メソッド)以外に、問題解決のためにプログラマーが新しいクラスを定義することができる。
悩ましい浮動小数計算
Pythonを含む大抵のプログラミング言語では $\frac{22}{3}$を 22 / 3 で計算すると、整数商 7 を返す((Python2 では 22 /3 、Python3では 22 // 3 で計算する)。 有理数 22 / 3 を値としての計算をするためには浮動小数点数(floating point numbers)を使って 22.0 / 3 とするのであるが、Python では次のように結果する。
>>> 22.0 / 3 7.333333333333333浮動小数点数は、計算機ハードウェアの中では基数を 2 とする (2進法の) 分数として表現されている。 たとえば、少数 0.125 は基数10 と 2 を使うと次のように表される。 \begin{align*} 0.125 &= [0.125]_{10}=\frac{1}{10}+\frac{2}{10^2}+\frac{5}{10^3}\\ &=[0.001]_2=\frac{0}{2}+\frac{0}{2^2}+\frac{1}{2^3} \end{align*} しかし、一般に 2 を基数とした表現で何桁使おうとも、10 を基数をした数を正確に表現することはできない。 たとえば、数 0.1 = 1 / 10 は基数 2 では循環小数 (repeating fraction) となる(0011が繰り返される)。 \[ 0.1 = [0.0\dot{0}\dot{0}\dot{1}\dot{1}]_2= 0.0001100110011001100110011001100110011001100110011\dots \] Pythonでは、大抵の処理系では浮動小数点をfloat 型として 53bit の精度で保持する。 10進数で 0.1 と書いたときに内部では次のような2進の小数が格納される。 \[ [0.00011001100110011001100110011001100110011001100110011010]_2 \] これは 1/10 に近いが、厳密には同じ値とはならない。 事実、次のような数である。 \[ [0.1000000000000000055511151231257827021181583404541015625]_{10} \not =\frac{1}{10} \]
次の結果は、浮動小数計算を内部で実現するハードウエアの宿命である(Pythonのバグではない)。
>>> 0.1 + 0.2 0.30000000000000004多くのプログラミング言語では、そのままの形では \[ \frac{1}{10}+\frac{2}{10} \] を正しく取り扱うことができないということだ。 それでも、Pythonが頑張って次のように計算する。
>>> 22.0 / 3 + 2.0 /3 8.0しかし、この結果は \[ \frac{22}{3} + \frac{2}{3} = \frac{24}{3} = 8 \] を正しく計算していると信じる根拠とはならない。
有理数計算モジュール fraction
Pythonでは fractions ('s'が付いている)モジュールをインポートすると有理数計算を行うことができる(Python2: 数値と数学モジュール fractions - 有理数)。 有理数(rational numbers)とは、整数である分母(denominator)と分子(numerator)からなる分数の形(ただし分母はゼロでない)の数 \[ \frac{p}{q},\qquad p,q\in\mathbb{Z}, q\not= 0 \] である。
有理数 $\frac{p}{q}$ を Fraction(p, q) であらわして、以下のような有理計算 $\frac{22}{3}+ \frac{2}{3} = \frac{8}{1}$ を正しく実行することができる。 計算結果も常に Fraction(p', q') となって有理数 $\frac{p'}{q'}$ の形を保持する。>>> import fractions >>> fractions.Fraction(22,3) + fractions.Fraction(2,3) Fraction(8, 1)
また、 $\frac{2}{3}+ \frac{1}{5} + \frac{3}{7} = \frac{136}{105}$ も
>>> fractions.Fraction(2,3) + fractions.Fraction(1,5) + fractions.Fraction(3, 7) Fraction(136, 105)と正しい結果を返す。 このとき、Fraction(a, b) は既約分数(irreducible fraction)を返すことにも注意しよう。
>>> fractions.Fraction(24,128) Fraction(3, 16)
Pythonの数学計算モジュール math を使うと数学計算が可能になる(複素数版 cmath もある)。 $\sin, \cos, \tan$などの三角関数群、指数関数 exp(x) や対数関数 log(x), 平方根 sqrt(x) や 床関数 $\lfloor x\rfloor =$ floor(x)、天井関数 $\lceil y\rceil =$ ceil(y) などに加えて、定数として、円周率 $\pi=$ pi や $\mathrm{e}=$ e が使える。
math モジュールを使うと、次のように Fraction に浮動小数 $\pi$ を与えて有里数表示も得ることができる。
>>> import math >>> fractions.Fraction(math.pi) Fraction(884279719003555, 281474976710656)
>>> 884279719003555.0/281474976710656 3.141592653589793
スクリプトを書いて実行しながら、不足分のクラスメソッド定義を追加していく
Python で新しいクラスを定義するには、クラス名とクラスに属するオブジェクトを操作するメソッドを用意して次のように書く。 構造単位の字下げにも注意を払う。
class クラス名: コンストラクタや メソッドを定義する ...... ......
以下の例で明らかなようにオブジェクト指向プログラミングを修得するためには、新しいクラス定義を記して。それの動作を検証するテストスクリプトを実行しながら、クラス設定と必要なメソッドを考えていくというサイクルがたいへん重要だ。 「教科書」で説明しているソースを眺めるだけでは、「体得」に到達することは難しい。
rationalクラスを構築する
オブジェクト指向プログラミングにおけるクラスの概念を理解するには大変有効なケーススタディとして、有理数クラス rational を定義してみよう。 上で観たように既にPythonにには有理数計算を実行できるモジュール fractions が備わっているのであるが、新しいクラスの設計と実装の理解のために、自力で有理計算クラスを定義してみる(数の抽象クラス numbers があり、numbers.Rationalは定義されているが、ここで定義する rational クラスがバッティングする心配はない)。
rational クラスを定義するためには、まずコンストラクタメソッド __init__(2文字のアンダースコア(_)で前後を挟まれている!!)を定義する。 コンストラクタは、クラスのインスタンス(instance)生成時に常に実行される初期化関数のようなものである。
__init__() の定義において、最初の特別な引数 self は省略できずに必須、クラスオブジェクト自身の参照に必要だ。 インスタンス生成に必要とするのは有理数 top / bottom に必要な2つの仮引数(整数) top と bottom を与えると、rational クラス自身の局所変数 num と den (これを self.num, self.den と参照するしている)。 ただし、ここでは bottom を省力した場合には bottom に 1 を代入するようにデフォルト値を設定するように書いている。 rational(4) とだけしたときは rational(4,1) の動作となる。
class rational: """class rational represents a rational number. """ def __init__(self, top, bottom = 1): """constructor for rational numbers """ self.num = top self.den = bottom
2重引用符3回 """ と """ で囲まれた文字列はPython Documentとして pydoc などで閲覧できる。 コメント代わりに、丁寧にクラスの意味やメソッドの定義について説明を加えるとよい。
$ pydoc rational_test
動作を検証するスクリプト を rational_test.py に追加保存し、それを実行することを前提に必要なメソッドを考えていこう。 ここまでの段階で次のようなスクリプト rational_test.py を書いて実行することができる。
#!/usr/bin/env python # -*- coding: utf-8 -*- class rational: """class rational represents a rational number. """ def __init__(self, top, bottom = 1): """constructor, initailly involed when creating rational instances """ self.num = top self.den = bottom ### end of class ration r1 = rational(3,5)
16行目で $\frac35$ を意図したrationalクラスのインスタンス rational(3,5) を生成して変数 r1 に代入している。
コンストラクタだけの定義では、クラス自身の局所変数 self.num と self.den に値が渡っただけで、それをどのように操作するのがクラス自身はまだ知らない。 そこで、rational クラスに次のメソッドを追加する。 メソッド定義の引数に self をセットしていることに注意。 実際には、rational インスタンス r にメソッド show を作用させるには r.show() と引数はとらない(コンストラクタもそうであった)。
def show(self): """ show rational number in q/p """ print self.num, '/', self.den
ここまでで、次のスクリプトが得られる。
#!/usr/bin/env python # -*- coding: utf-8 -*- class rational: """class rational represents a rational number. """ def __init__(self, top, bottom = 1): """constructor, initailly involed when creating rational instances """ self.num = top self.den = bottom def show(self): """ show rational number in q/p """ print self.num, '/', self.den ### end of class ration r1 = rational(3,5) r1.show() print r1
print はプリントすべきオブジェクトを出力可能な文字列に 変換され ているときに上手く動作する。 今のままでは、変数に格納されているインスタンスの格納アドレスを算法してしまうので上手く働かないためにエラーが生じた。
printはrationalクラスでも上手く働くようにするには、オブジェクトを文字列に変換する元から備わっていたメソッド __str__ を今の場合でも働くように実装を拡張すればよい。 これをオーバーライド(override)という。 クラス rational ないで次のようにオーバーライドする、
def __str__(self): '''Display self as a string. override __str__ method ''' return str(self.num) + '/' + str(self.den)
こうして、次のスクリプトが得られる。 少し長くなるがすべてを書きだそう。 クラス定義の後に、インスタンス内容を2つのやり方で表示するスクリプトを書いている
#!/usr/bin/env python # -*- coding: utf-8 -*- class rational: """class rational represents a rational number. """ def __init__(self, top, bottom = 1): """constructor, initailly involed when creating rational instances """ self.num = top self.den = bottom def __str__(self): '''Display self as a string. override __str__ method ''' return str(self.num) + '/' + str(self.den) def show(self): """ show rational number in q/p """ print self.num, '/', self.den ### end of class ration r1 = rational(1,4) r1.show() print r1 r2 = rational(4,3) print r1, r2 print r1 + r2
既約分数で rational クラスを定義する
しかしこの段階においても、たとえば インスタンス r1 = rational(18,24) として、これを表示すると 18 / 24 と当たり前に表示される。 しかし、18 / 24, 6 / 8, 24 / 32 などは全て同じ有理だと見なし、その代表として 6/8 と見るのである。 つまり、有理数は既約分数(irreducible fraction)としてインスタンス化され、計算結果も既約化されるようにクラス定義をすべきである。
\[ \frac{q}{p} \Rightarrow \frac{q}{\mathrm{gcd}(q,p)} \Big/ \frac{p}{\mathrm{gcd}(q,p)} \]$\mathrm{gcd}(q,p)$ は 整数 $q, p$ の最大公約数(GCD: Greatest Common Divisor)である。 したがって、rational クラスが既約分数として取り扱えるようにするためには、クラス定義の外側で、最大公約数を計算する次のような関数 gcd を定義して、rationalクラスのコンストラクタを書き換えおく必要がある。
def gcd(m, n): """GCD for lowest terms representation of rational numbers """ while m % n != 0:# Euclidean Algorithm old_m = m old_n = n m = old_n n = old_m % old_n return n
rationalクラスに加減乗除メソッドを追加する
rational クラスでは次の加減乗除からなる四則演算をメソッド化して実装する必要がある。 いずれも分母がゼロでない有理数でのみ演算が定義され、その結果も分母がゼロでない場合に意味を持つ。
\begin{align*} \frac{n_1}{d_1}+\frac{n_2}{d_2} &= \frac{n_1\times d_2 + n_2\times d_1}{d_1\times d_2}\\ \frac{n_1}{d_1}-\frac{n_2}{d_2} &= \frac{n_1\times d_2 - n_2\times d_1}{d_1\times d_2}\\ \frac{n_1}{d_1}\times \frac{n_2}{d_2} &= \frac{n_1\times n_2}{d_1\times d_2}\\ \frac{n_1}{d_1}\div \frac{n_2}{d_2} &= \frac{n_1\times d_2}{n_2\times d_1} \end{align*}これらを既に定義されている __add__(記方法 -), __sub__(記方法 -), __mul__(記方法 *), __div__(記方法 / )としてメソッドのオーバーライドとして定義する。 たとえば、掛け算のメソッド __mul__ は次のように rational クラス内でオーバーライドして定義する。
def __mul__(self, other): """Override: Multiplication of two rational numbers , allowing notation self * other """ return rational(self.num * other.num, self.den * other.den)
こうすると、上の演習のように既約分数が扱えるようにコンストラクタを修正しておくと、 \[ \frac{3}{5} \times \frac{4}{6} = \frac{3\times 4}{5\times 6}=\frac{2}{5} \] と同じく、直接 fraction(3,5) * fraction(6, 4) をprintしてみると 2/5 を表示する。
rationalクラスの(とりあえずの最後のメソッドとして)、数の浮動小数への変換 float メソッドをオーバーライドしておこう。
def __float__ (self): """Override: float value of self. """ return float (self.num ) / float (self.den)
このメソッドは、rationalクラスのインスタンス rational(1,3) に対して、float(rational(1,3)) が浮動小数であるように扱うことを可能にする。