13. クラス#
クラスは変数(メンバ変数)と関数(メソッド)をまとめて管理できるオブジェクトの型を定義する。
13.1. インスタンス化#
以下の例では2次元上の点を表すクラスPoint
を定義している。Point
クラスのインスタンスのメンバ変数としてx
とy
を定義し、それぞれ0.
に初期化している。
class Point:
def __init__(self):
self.x = 0.
self.y = 0.
クラスをインスタンス化してPoint
オブジェクトを作成する。このとき、作成されたオブジェクトを引数として__init__
関数が呼び出される。__init__
関数を定義するときに、最初の引数の変数名をself
にするのはお約束。そのオブジェクト(self
変数で参照される)に対して.
演算子を適用することで、メンバ変数x
とy
を0.
に初期化している。
p = Point()
p
の型はPoint
型である。
p
<__main__.Point at 0x7f9e7c2d3340>
type(p)
__main__.Point
インスタンスのメンバ変数には.
演算子でアクセスできる。
p.x
0.0
p.y
0.0
メンバ変数の値をクラスの外からでも変更できる(基本的にPythonのメンバ変数はpublicである)。
p.x = 2.
p.x
2.0
インスタンスのメンバ変数をクラスの外からでも宣言・代入できる。
p.a = 3
p.a
3
(当たり前ではあるが)Point
クラスを新しくインスタンス化して別のオブジェクトを作成しても、既に作成済みのオブジェクトに影響を与えることはない。
q = Point()
p.x
2.0
q.x
0.0
__init__
関数で引数を受け取り、メンバ変数を初期化することも可能。
class Point:
def __init__(self, x=0., y=0.):
self.x = x
self.y = y
p = Point(1, 2)
p.x
1
p.y
2
__init__
関数の引数にデフォルト値を設定しておいたので、引数を省略しインスタンスを作成しても構わない。
q = Point()
q.x
0.0
13.2. メソッド#
インスタンスのメンバ変数に対する操作などをメンバ関数(メソッド)として記述できる。メンバ関数の第1引数としてインスタンスが渡されるため、第1引数をself
という変数名にするのが慣例。
class Point:
def __init__(self, x=0., y=0.):
self.x = x
self.y = y
def set(self, x, y):
self.x = x
self.y = y
def transpose(self):
self.x, self.y = self.y, self.x
def hamming(self):
return self.x + self.y
def dot(self, other):
return self.x * other.x + self.y * other.y
Point
クラスをインスタンス化
p = Point()
p.x
0.0
p.y
0.0
Point
クラスのインスタンスp
のメンバ関数を呼び出す。
p.set(1, 2)
p.x
1
p.y
2
上のメンバ関数の呼び出しは、以下の呼び出しと等価(第1引数がself
となる理由が分かる)。
Point.set(p, 2, 3)
p.x
2
p.y
3
メンバ関数の中でメンバ変数の値を変更する例。
p.transpose()
p.x
3
p.y
2
メンバ関数が値を返す例。
p.hamming()
5
メンバ関数の引数として別のオブジェクトを渡す例。
p = Point(2, 3)
q = Point(5, 4)
p.dot(q)
22
あるオブジェクトがどのようなメンバ変数・関数を持っているのか確認するには、dir関数を用いる。
dir(p)
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'dot',
'hamming',
'set',
'transpose',
'x',
'y']
13.3. 継承#
あるクラスをベースとしてメンバ変数・関数を追加し、新しいクラスを定義することを継承と呼ぶ。2次元の点を表すクラスPoint
をベースに、3次元の点を表すクラスPoint3D
を作成する。このとき、Point
クラスは親クラスと呼ばれる。以下の例におけるsuper().__init__
は、Point3D
クラスから見て親クラスであるPoint
クラスの__init__
関数を呼び出している。
class Point3D(Point):
def __init__(self, x=0., y=0., z=0.):
super().__init__(x, y)
self.z = z
3次元の点を表すインスタンスを作成する。z軸の値を格納できるようになっている。
p = Point3D(3, 4, 2)
p.x
3
p.y
4
p.z
2
ところが、hammingメンバ関数を呼び出しても、期待する結果(9)が得られない。
p.hamming()
7
これは、Point3D
クラスではhammingメンバ関数が明示的には実装されていないため、Point
クラスのhammingメンバ関数が呼び出され、x軸とy軸の値しか使われていないためである。
p.hamming
<bound method Point.hamming of <__main__.Point3D object at 0x7f9e7c32ec80>>
z軸の値も考慮してハミング距離を求めるには、Point3D
クラスにおいてhamming
関数を上書きする必要がある。
class Point3D(Point):
def __init__(self, x=0., y=0., z=0.):
super().__init__(x, y)
self.z = z
def hamming(self):
return self.x + self.y + self.z
p = Point3D(3, 4, 2)
p.hamming()
9
以上の変更により、Point3D
クラスのhammingメンバ関数が呼び出されることが確認できる。
p.hamming
<bound method Point3D.hamming of <__main__.Point3D object at 0x7f9e7c32fd90>>
13.4. 特殊メソッド#
自分で定義したクラスのオブジェクトに対して、+
演算子の動作やstr
関数を呼び出したときの動作を特殊メソッドに記述できる。以下は2次元の点を表現するPoint
クラスの特殊メソッドを実装する例。
class Point:
""" Two-dimensional vector. """
def __init__(self, x=0., y=0.):
self.x = x
self.y = y
def __str__(self):
""" str(p) """
return f'({self.x}, {self.y})'
def __repr__(self):
""" repr(p) """
return f'Point({self.x}, {self.y})'
def __add__(self, other):
""" self + other """
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other):
""" self - other """
return Point(self.x - other.x, self.y - other.y)
def __mul__(self, other):
""" self * other """
return Point(self.x * other.x, self.y * other.y)
def __rmul__(self, other):
""" other * self """
return Point(self.x * other, self.y * other)
def __truediv__(self, other):
""" self / other """
return Point(self.x / other.x, self.y / other.y)
def __floordiv__(self, other):
""" self // other """
return Point(self.x // other.x, self.y // other.y)
def __eq__(self, other):
""" self == other """
return self.x == other.x and self.y == other.y
def __ne__(self, other):
""" self != other """
return self.x != other.x or self.y != other.y
def __lt__(self, other):
""" self < other """
return self.x < other.x and self.y < other.y
def __le__(self, other):
""" self <= other """
return self.x <= other.x and self.y <= other.y
def __gt__(self, other):
""" self > other """
return self.x > other.x and self.y > other.y
def __ge__(self, other):
""" self >= other """
return self.x >= other.x and self.y >= other.y
def set(self, x, y):
self.x = x
self.y = y
def transpose(self):
self.x, self.y = self.y, self.x
def hamming(self):
return self.x + self.y
def dot(self, other):
return self.x * other.x + self.y * other.y
Point
クラスのインスタンスp1
とp2
を作成。
p1 = Point(2, 3)
p2 = Point(5, 6)
+
, -
, *
, /
演算子を使うと、__add__
, __sub__
, __mul__
または__rmul__
, __truediv__
が呼び出される。
p1 + p2
Point(7, 9)
p2 - p1
Point(3, 3)
p1 * p2
Point(10, 18)
3 * p1
Point(6, 9)
p2 / p1
Point(2.5, 2.0)
==
, !=
, <
, <=
, >
, >=
演算子を使うと、__eq__
, __ne__
, __lt__
, __le__
, __gt__
, __ge__
が呼び出される。
p1 == p2
False
p1 != p2
True
p1 < p2
True
p1 <= p2
True
p1 > p2
False
p1 >= p2
False
str関数はオブジェクトを文字列として読みやすい形式で表現する(ことを想定しているため、そのようになるように__str__
関数を実装しておいた)。
str(p1)
'(2, 3)'
repr関数はオブジェクトをプログラムとして実行可能な表現に変換する(ことを想定しているため、そのようになるように__repr__
関数を実装しておいた)。
repr(p1)
'Point(2, 3)'
オブジェクトを単に評価させたときにはrepr関数が呼び出される。
p1
Point(2, 3)
repr関数が返す文字列はそのままPythonのプログラムとして評価できることを想定しているため、例えばeval関数でプログラムとして実行できるようにしておくとよい。
eval(repr(p1)) + p2
Point(7, 9)