HOME Python download 書き込む

Python の ”新しい”クラス


1. 初めに

Python 2.2 から、タプル、リスト、辞書などの組み込みオブジェクトを親クラスとするクラスを定義することが 可能になりました。 使い方は、普通のクラスとほとんど変わりません。 便利になった点は、組み込みオブジェクトの属性を再定義しなくてすむという点です。 例えば、Python 早めぐり で述べた Vector クラスは、 リスト型内部変数 'v' を定義して、Vector の次元などは、v の次元になるように __len__ などを定義する必要がありました。 組み込みオブジェクトを親にすればこのような二度手間がはぶけます。

ここではタプルのサブクラスとして Vector クラスを定義してみます。 Python 早めぐりで定義したベクトルは説明用の不完全なものでしたが、 ここでは使えるものを作ってみました。 算術演算を定義する特殊メソッドの使い方を参考にして下さい。 また、Vector クラスを使って3次元のフラクタル木を作ってみました。

2. タプルを使った Vector の定義

[code 1] にタプルを使った Vector クラスの定義を示します。 [code 1] を見るとわかるように、配列としての属性は定義する必要がありません。 __init__ もタプルのを使うので定義不要です。 組み込みオブジェクトを親クラスにすると、初期化も組み込みクラスと同じように行われます。 つまり、Vector を作るときにはリスト(またはタプル)を引数に与えなければなりません。 これは若干不便なので、Vector を作る関数 make_vector も作っておきました。

さて、Vector クラスは、ベクトルらしく振舞うように代数演算 (+, -, *, +=, -=) を定義します。

[code 1] (vector.py)

 01:     #! /usr/bin/env python
 02:     #  coding: shift_jis 
 03:     
 04:     import math, copy
 05:     
 06:     
 07:     ## functions ------------------------------------------------------------
 08:     def isnum(n):
 09:         """ check if n is a number """
 10:         return type(n) in (int, float, long, complex)
 11:     
 12:     def make_vector(*ele, **key):
 13:         """ making an instance of Vector"""
 14:         if ele:
 15:             return Vector(ele)
 16:         elif 'value' in key and 'dimension' in key:
 17:             return Vector([key['value'] for x in range(key['dimension'])])
 18:         else:
 19:             raise KeyError, 'Key should be \'dimension\' and \'value\'.'
 20:     
 21:     
 22:     ## ------------------------------------------------------------------------
 23:     class Vector(tuple):
 24:         """
 25:         Vector is a class for vector operation, which is a sub-class of tuple.
 26:         """
 27:     
 28:         def __neg__(self):
 29:             return Vector([-x for x in self])
 30:         
 31:         def __pos__(self):
 32:             return copy.copy(self)
 33:             
 34:         def __abs__(self):
 35:             """it returns the magnitude of vector."""
 36:             return math.sqrt(sum([ x * x for x in self]))
 37:     
 38:         def norm(self):
 39:             """it returns the normarized vector."""
 40:             a = self.__abs__()
 41:             return Vector([x/a for x in self])
 42:             
 43:         def __add__(self, a):
 44:             """it adds vector and vector."""
 45:             if isinstance(a, Vector):
 46:                 if len(self) == len(a):
 47:                     return Vector([ x + y for x, y in zip(self, a)])
 48:                 else:
 49:                     raise ValueError, "Same dimension is required."
 50:             else:
 51:                 raise TypeError, "Vector is required."
 52:     
 53:         __iadd__ = __add__
 54:      
 55:         def __sub__(self, a):
 56:             return self.__add__(-a)
 57:     
 58:         __isub__ = __sub__
 59:                 
 60:         def __mul__(self, m):
 61:             """if m is a vector, it calculate the inner product,
 62:                else if m is a number, it multiplies each element of vector by m."""
 63:                
 64:             if isinstance(m, Vector):
 65:                 if len(self) == len(m):
 66:                     return sum([ x * y for x, y in zip(self, m)])
 67:                 else:
 68:                     raise ValueError, "Same dimension is required."
 69:             elif isnum(m):
 70:                 return Vector([ x * m for x in self])
 71:             else:
 72:                 raise TypeError, "Vector or number is required."
 73:     
 74:         __rmul__ = __mul__
 75:     
 76:         def __str__(self):
 77:             """add#V in front of the representation of tuple."""
 78:             return '#V' + tuple.__str__(self)
 79:     
 80:     ##-----------------------------------------------------------------------------
 81:     __all__ = (make_vector, Vector)
 82:     
 83:     if __name__ == '__main__':
 84:         v1 = Vector([1,2,3])
 85:         v2 = Vector([2,3,4])
 86:         v3 = Vector([1+2j, 2+3j, 4+5j])
 87:         print 'Vector([1,2,3])  (v1, here in after) = ', v1
 88:         print 'Vector([2,3,4])  (v2, here in after) = ', v2
 89:         print 'Vector([1+2j, 2+3j, 4+5j])     (v3, here in after) = ', v3
 90:         print '-v1 = ', -v1
 91:         print 'v1==v2', v1==v2
 92:         print 'v1[0] =', v1[0]
 93:         print 'v1*v2 = ', v1*v2
 94:         print 'v2-v1 = ', v2 - v1
 95:         print '3*v1 = ', 3*v1
 96:         print 'v1*3 = ', v1*3
 97:         print 'abs(v1) = ', abs(v1)
 98:         print 'v1.norm() = ', v1.norm()
 99:         for i, x  in enumerate(v1):
100:             print 'v1[', i, '] = ', x
101:         v1 += v2
102:         print 'v1 += v2 -> v1', v1
103:         v1 -= v2
104:         print 'v1 -= v2 -> v1', v1
105:         print 'make_vector(1,2,3,4) =', make_vector(1,2,3,4)
106:         print 'make_vector(dimension=5, value=0) =', make_vector(dimension=5, value=0)
107:         print 'v3 + make_vector(dimension=len(v3), value= 1+2j) = ', v3 + make_vector(dimension=len(v3), value= 1+2j)
108:         print 'repr(v1) = ', repr(v1)
109:         print 'str(v1) = ', str(v1)

説明

81 行までがモジュールの定義で、それ以降はテストコードです。

1. 関数

2つの関数を定義します。
isnum (n)
n が、整数、実数、複素数かどうか調べます。
make_vector(*ele, **key)
各要素 (ele) が与えられたらその要素を持つベクトルを返します。また、 キーワード 'dimension' と 'value' が与えられたら、 value を値とする dimension 次元の ベクトルを返します。

2. class Vector(tuple)

tuple をスーパークラスとして class Vector を定義します。 初期化および配列用のメソッドは tuple のをそのまま使えばよいので定義しません。 ベクトルらしく振舞うよう代数演算子を定義します。
__neg__
単項演算子 '-' を定義します。符号の反転を表します。
__pos__
単項演算子 '+' を定義します。ここではコピーを作って返すだけです。
__abs__
関数 abs が返す値を定義します。ベクトルの長さを返します。
norm
単位ベクトルを返します。
__add__
二項演算子 '+' を定義します。ベクトル同士の足し算です。
__iadd__
二項演算子 '+=' を定義します。
__sub__
二項演算子 '-' を定義します。ベクトル同士の引き算です。
__isub__
二項演算子 '-=' を定義します。
__mul__
二項演算子 '*' で、左側がベクトルの場合を定義します。 右側もベクトルなら内積を計算します。数なら、ベクトルの各要素にその数をかけたベクトルを返します。
__rmul__
二項演算子 '*' で、右側がベクトルの場合を定義します。__mul__ と同じです。
__str__
関数 str が返す値および print 文で印字される文字列を定義します。

数値型をエミュレートするメソッドについての詳しいことは Python リファレンスマニュアル 3.3.7 を参照してください。

3. 簡単な使用例

Vector クラスを利用して、3次元の木を描いてみましょう。 木は、枝を生やすとき、新しい枝の長さをもとの枝の長さを一定の比率で縮小し、 もとの枝に対して一定角度で曲げることによって描かれます。 グローバル変数 THETA, BR の値を変えると木の形がいろいろと変わります。 遊んでみてください。 コードを [code 2] に示します。

[code 2] (tree.py)

01:     #! /usr/bin/env python
02:     
03:     from vector import make_vector
04:     from math import sin, cos, pi, atan, sqrt
05:     from random import randint
06:     
07:     
08:     REP = 9
09:     THETA = pi * 0.15
10:     BR = 0.6
11:     
12:     def make_branch(br, ratio, theta, phi):
13:         """
14:         bending the vector v (br.v * ratio) by theta,
15:         and turn it around the plane which is perpendicular to v by phi.
16:         
17:         A unit vector (x, y, z) on the perpendicular plane is represented by:
18:         ax + by + cz = 0,          (1)
19:         x^2 + y^2 + z^2 =0         (2)
20:         where a=v[0], b=v[1], c=v[2]
21:     
22:         from eq (2),  x, y, z  are converted to alpha, phi by
23:         x = sin(alpha)cos(phi)
24:         y = sin(alpha)sin(phi)
25:         z = cos(alpha)              (3)
26:     
27:         where 0<= alpha <=pi.
28:         
29:         by combining eqs (1) and (3), eq (4) is obtained
30:     
31:         tan(alpha) = -c / (a cos(phi) + b sin(phi))   (4)
32:         
33:         by calculating x,y, and z using eqs (3) and (4),
34:         a unit vector(v1) perpendicular to v is obtained.
35:     
36:         The second step is making two unit vectors say v2, and v3,
37:         which satisfy
38:         v * v2(or v3) = 0
39:         v1 *v2(or v3) = -0.5
40:     
41:         Then make a list of Branch from v1, v2, v3, 
42:         """
43:     
44:         v0 = br.v * ratio
45:         p = (v0[0] * cos(phi) + v0[1] * sin(phi))
46:         alpha = p==0 and 0.5*pi or atan(-v0[2]/p)
47:         if alpha<0:  alpha += pi
48:         v1 = make_vector(sin(alpha)*cos(phi), sin(alpha)*sin(phi), cos(alpha)) # first unit vector perpendicular to v0
49:     
50:         # calculating 2nd and 3rd unit vectors perpendicular to v0
51:         t = v0[1]*v1[2] - v0[2]*v1[1]
52:         a = 0.5*v0[2]/t
53:         b = (v0[0]*v1[2]-v0[2]*v1[0])/t
54:         c = -0.5*v0[1]/t
55:         d = -1*(v0[0]*v1[1]-v0[1]*v1[0])/t
56:         a1 = 1+b*b+d*d
57:         b1 = a*b + c*d
58:         c1 = a*a + c*c -1
59:         d1 = b1*b1 - a1*c1
60:         if d1 <= 0: raise ValueError, "no real value"
61:         d1 = sqrt(d1)
62:         x1 = (b1 + d1)/a1
63:         x2 = (b1 - d1)/a1
64:         x_y = lambda x: a-b*x
65:         x_z = lambda x: c-d*x
66:         v0cos = v0*cos(theta)
67:         v0sin = abs(v0) * sin(theta)
68:         r = br.r+br.v
69:         return [Branch(r, v0cos+v0sin*vp)            \
70:                 for vp in [v1, make_vector(x1, x_y(x1), x_z(x1)), make_vector(x2, x_y(x2), x_z(x2))]]
71:     
72:     def out_data(i, ls):
73:         f = file('tree%d.dat' % i, 'w')
74:         f.write( '#x y z\n#rep: %d\n' % i)
75:         f.write('\n\n'.join(['%s\n%s\n' % (repr(b.r)[1:-1], repr(b.r + b.v)[1:-1]) for b in ls]))
76:         f.close()
77:         
78:     
79:     class Branch:
80:         def __init__(self, r, v):
81:             self.r = r
82:             self.v = v
83:     
84:     if __name__=='__main__':
85:         ls0 = [Branch(make_vector(0,0,0), make_vector(0,0,1))]
86:         ls1 = []
87:         out_data(0, ls0)
88:         for rep in range(REP):
89:             for b in ls0:              # make branches from those produced at the last turn
90:                 ls1.extend(make_branch(b, BR, THETA, randint(0,6283) * 0.001))
91:             out_data(rep+1, ls1)
92:             ls0, ls1 =ls1, []
tree0.dat -- tree7.dat が生成するので、それを gnuplot で描かせると図1のようになります。 枝をだんだん細くし、最後 (tree8.dat) を緑色で描くと本物らしくなります。 gnuplot に与えるコマンドファイル tree.plt はこんな感じです。


図1:フラクタル木

説明

図2の様に3本の枝に枝分かれさせます。枝の折れ曲がる角度を θ, 枝分かれする枝のうちの1本の x 軸となす角度を φ とします。新しい枝が 3 回対称 になるように枝分かれさせます。


図2:枝分かれのさせ方

インポートするモジュール
vector
make_vector だけをインポートすれば十分です。
math
三角関数、円周率および sqrt をインポートします。
random
枝の軸に対する角度をランダムに決めるため randint をインポートします
定数
REP
繰り返しの回数です。ここでは 9 回繰り返します。
THETA
枝の折れ曲がりの角度です。27 度折り曲げます。
BR
もとの枝と継ぎ足す枝の長さの比です。
make_branch(b, ratio, theta, phi)
b の終点に新しい枝を3本生やす関数です。新しい枝は、もとの枝に対して theta だけ曲げます。phi は 新しい枝の1本の x 軸となす角度です。 何をやったのか忘れないようにコメントが長いのですが、実質的なコードはそれほど長くありません。 もとの枝のベクトルに ratio をかけたベクトル (b.v * ratio)v0 とすると、 新しい枝は
v0*cos(theta) * abs(v0)*sin(theta)*vp
で表されます。 ここで、vpv0 に垂直な単位ベクトルです。 まず、vp のうちで、xy 平面上への射影と x 軸とのなす角度が phi のものを求めます(これを v1 とします)。 残りの vp
  v0*vp = 0
  v1*vp = -0.5
  abs(vp) = 1.0
から求まります。
最後に、vp のリストから新しい枝のリストを作って返します。
out_data(i, ls)
i 回目のターンで生成した枝のリスト (ls) の座標を gnuplot が読める形式で 'tree%d.dat' % (i+1) に 保存します。
class Branch
構造体として使っています。内部変数として 枝の始点 (self.r)、枝のベクトル (self.v) を保持します。
if __name__ == '__main__'
  1. 入力用と出力用の枝のリスト ls0, ls1 を作り、 ls0 の要素は z 軸上の長さ1の枝(幹)にします。(85,86)
  2. 最初の枝をファイルに保存します。(87)
  3. repREP より小さい間、新しい枝を作ります。(88)
    1. 前回生成した枝 (ls0) から新しい枝を生やして、それを ls1 に追加します。(89--90)
      枝の向きが自然になるように、 phi はランダムに振ります。
    2. 枝を作り終わったら、ls1 をに出力します。(91)
    3. ls0ls1 を、ls1 に [] を代入して枝作りを繰り返します。(92)

4. 終わりに

Python の新しいクラスで遊んでみました。使い方はもともとのクラスとほとんど変わりありません。 組み込みデータ型をスーパークラスにできるところが便利な点です。

vector.py と tree.py は付録につけておきましたので 暇なとき遊んでみてください。