6.1 マジックメソッド
Pythonにおけるオペレーターのオーバーロードは、組み込みオペレーター(例えば、+、-、*、/)の振る舞いをカスタムクラスで定義または変更することを可能にするんだよ。 これは特別なメソッド、つまりマジックメソッドを使って行うんだ。
例えば、自分のクラスで比較オペレーターをオーバーロードすることができるよ:
| オペレーター | アンダースコアなしメソッド | メソッドシグネチャ |
|---|---|---|
| == | eq() |
__eq__(self, other) |
| != | ne() |
__ne__(self, other) |
| < | lt() |
__lt__(self, other) |
| <= | le() |
__le__(self, other) |
| > | gt() |
__gt__(self, other) |
| >= | ge() |
__ge__(self, other) |
例えば、自分のクラスを書いて、クラスのオブジェクトが比較されたときに自分のやり方で動作してほしいとするよね。その場合、クラスで«__eq__»メソッドを実装すれば、Pythonはコードでクラスのオブジェクトが比較されるたびにこのメソッドを呼び出してくれるよ。
例:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# 使用方法
v1 = Vector(2, 3)
v2 = Vector(2, 3)
v3 = Vector(4, 5)
print(v1 == v2) # 出力: True
print(v1 == v3) # 出力: False
毎回、2つのオブジェクトを比較すると、Pythonは«__eq__»関数が実装されているか確認するんだ。それがあれば、Pythonはそれを呼び出すよ。そして、実装されていない場合は、オブジェクトへの参照を比較するだけになるんだ。
実際には上記の例では(メソッドの存在を確認する必要があるだけで)次のように書かれているんだ:
# 使用方法
v1 = Vector(2, 3)
v2 = Vector(2, 3)
v3 = Vector(4, 5)
print(v1.__eq__(v2)) # 出力: True
print(v1.__eq__(v3)) # 出力: False
6.2 すべてのオペレーターの一覧
オーバーロード可能なオペレーターのグループは全部で6つあるよ。
算術オペレーター:
| オペレーター | アンダースコアなしメソッド | メソッドシグネチャ |
|---|---|---|
| + | add |
__add__(self, other) |
| - | sub |
__sub__(self, other) |
| * | mul |
__mul__(self, other) |
| / | truediv |
__truediv__(self, other) |
| // | floordiv |
__floordiv__(self, other) |
| % | mod |
__mod__(self, other) |
| ** | pow |
__pow__(self, other) |
比較オペレーター:
| オペレーター | アンダースコアなしメソッド | メソッドシグネチャ |
|---|---|---|
| == | eq |
__eq__(self, other) |
| != | ne |
__ne__(self, other) |
| < | lt |
__lt__(self, other) |
| <= | le |
__le__(self, other) |
| > | gt |
__gt__(self, other) |
| >= | ge |
__ge__(self, other) |
論理オペレーター:
| オペレーター | アンダースコアなしメソッド | メソッドシグネチャ |
|---|---|---|
| & | and |
__and__(self, other) |
| | | or |
__or__(self, other) |
| ^ | xor |
__xor__(self, other) |
| ~ | invert |
__invert__(self) |
インデックスとスライスオペレーター:
| オペレーター | メソッド |
|---|---|
| obj[key] | __getitem__(self, key) |
| obj[key] = value | __setitem__(self, key, value) |
| del obj[key] | __delitem__(self, key) |
単項オペレーター:
| オペレーター | メソッド |
|---|---|
| - | __neg__(self) |
| + | __pos__(self) |
| abs() | __abs__(self) |
| ~ | __invert__(self) |
代入オペレーター:
| オペレーター | メソッド |
|---|---|
| += | __iadd__(self, other) |
| -= | __isub__(self, other) |
| *= | __imul__(self, other) |
| /= | __itruediv__(self, other) |
| //= | __ifloordiv__(self, other) |
| %= | __imod__(self, other) |
| **= | __ipow__(self, other) |
こうしたことから、Pythonが遅いのはこのせいかもね。オペレーターが実行されるたびに、クラスや親クラスの同等の関数を探しに行くんだ。それでも、世界で最もコンパクトなコードを書くことができるんだよ :)
6.3 インデックスオペレーター
オブジェクトを比較したり集合を引いたりできるのは、ある意味当然のことだよね。そして、ロジックや数学的な操作が想定されるクラスを書くときにそれに気づくはず。
こんな面白い例をやってみたいんだけど、インデックスオペレーターについてです。さっそくコードから始めましょう:
class CustomList:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
self.data[index] = value
def __delitem__(self, index):
del self.data[index]
def __repr__(self):
return repr(self.data)
# 使用方法
c_list = CustomList([1, 2, 3, 4, 5])
print(c_list[1]) # 出力: 2
c_list[1] = 10
print(c_list) # 出力: [1, 10, 3, 4, 5]
del c_list[1]
print(c_list) # 出力: [1, 3, 4, 5]
ここで、3つの操作の例が見られるよ:
インデックスを使ったデータの読み込みインデックスを使ったデータの書き込みインデックスを使ったデータの削除さえも。
データがリストとして保存されているとは限らないんだよ。また、インデックスが数値である必要もないんだ。例えば、クラスdictionary(辞書)はまさにそのように実装されているんだ。
SuperListを作成しよう
listクラスを覚えている?それに要素を代入できるけど、すでに存在するインデックスに対してだけだよね。では、独自のクラスを作って、SuperListと名付けよう。その要素にはどんなインデックスでもアクセスできるようにしよう:
インデックスが< 0の場合、要素を先頭に挿入するインデックスが>= lenの場合、要素を末尾に追加するそれ以外の場合は単に要素を返す
例:
class SuperList(list):
def __init__(self, value):
super().__init__(value)
def __setitem__(self, index, value):
if index >= len(self):
super().append(value)
elif index < 0:
super().insert(0, value)
else:
super().__setitem__(index, value)
lst = SuperList([1, 2, 3])
lst[200] = 100
lst[-200] = 99
print(lst) # [99, 1, 2, 3, 100]
インデックスのオーバーロードは素晴らしい可能性を秘めているので、ぜひ実際に使ってみてね。今日はここまでです。
GO TO FULL VERSION