Pythonの__sub__メソッドを徹底解説! オブジェクト同士の「引き算」を定義しよう


 

Pythonで数値や日付を-演算子で引き算するのは一般的ですよね。でも、自分で作ったクラスのオブジェクト同士を足し算のように引き算したいと思ったことはありませんか? 例えば、2つのベクトルオブジェクトの差を計算したり、カスタムデータ構造から要素を「引き去る」ような操作を直感的に行いたい場合などです。

そんな時にPythonでその「引き算」の挙動をカスタマイズできるのが、特殊メソッド(マジックメソッド)の1つである**__sub__メソッド**です。

この記事では、__sub__メソッドの基本的な使い方から、その役割、そしてカスタムオブジェクトに独自の引き算を実装する方法まで、初心者の方にも分かりやすく徹底的に解説します。__sub__をマスターすれば、あなたのPythonコードはもっと直感的で、オブジェクト指向プログラミングの幅がさらに広がるでしょう!


 

__sub__メソッドとは? なぜ使うのか?

 

__sub__は、Pythonの特殊メソッド(Special Method)、あるいは**マジックメソッド(Magic Method)**と呼ばれるメソッドの一つです。これらのメソッドは、__(アンダースコア2つ)で始まり、__で終わる名前を持ち、特定のPythonの構文や組み込み関数が呼び出されたときに自動的に実行されます。

__sub__メソッドは、オブジェクトに対して**-演算子**が使用されたときに呼び出されるメソッドです。

なぜ__sub__を使うのでしょうか?

  • 演算子のオーバーロード(Operator Overloading): 独自のクラスで-演算子が使われたときの動作を、自由に定義できるようになります。これにより、カスタムオブジェクトを数値のように自然に操作できます。

  • コードの可読性向上: 複雑な処理を関数呼び出しではなく、直感的な-演算子で表現できるため、コードが読みやすくなります。

  • ドメイン固有の操作: 扱うデータや概念に合わせて、意味のある「引き算」を実装できます。例えば、ベクトルの減算、日付間の日数計算、リソースの消費などです。


 

__sub__メソッドの基本的な使い方

 

__sub__メソッドは、クラスの内部で定義します。通常、2つの引数を受け取ります。

 

構文

 

Python
 
class MyClass:
    def __sub__(self, other):
        # self: -演算子の左側のオブジェクト (MyClassのインスタンス)
        # other: -演算子の右側のオブジェクト (任意の型)
        # ここで引き算のロジックを実装し、結果を返す
        pass

 

戻り値

 

__sub__メソッドは、引き算の結果となる新しいオブジェクトを返す必要があります。元のオブジェクト(self)を変更するべきではありません。これは、数値の引き算が元の数値を変更せず、新しい数値を返すのと同じ原則です。

 

具体例1:二次元ベクトルの減算

 

最も分かりやすい例として、二次元ベクトルを表現するクラスで__sub__を実装してみましょう。

Python
 
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self): # オブジェクト表示用(開発者向け)
        return f"Vector({self.x}, {self.y})"

    def __sub__(self, other):
        # other が Vector型かチェック
        if isinstance(other, Vector):
            # 新しい Vector オブジェクトを返す
            return Vector(self.x - other.x, self.y - other.y)
        else:
            # Vector型でない場合は NotImplemented を返す
            return NotImplemented

v1 = Vector(5, 7)
v2 = Vector(2, 3)

v3 = v1 - v2 # __sub__ メソッドが呼び出される
print(v3)    # 出力: Vector(3, 4)

# 数値との減算はエラーになる (NotImplementedが返されるため)
# print(v1 - 2) # TypeError: unsupported operand type(s) for -: 'Vector' and 'int'

この例では、Vectorオブジェクト同士を-で引き算すると、それぞれのxy成分が減算された新しいVectorオブジェクトが生成されます。


 

__sub__の動作と注意点

 

 

1. 戻り値は新しいオブジェクト

 

先述の通り、__sub__新しいオブジェクトを返すべきです。元のオブジェクトをインプレースで変更したい場合は、__isub__-=演算子用)を使用します。

 

2. other引数の型チェック

 

__sub__メソッドは、otherとして任意の型のオブジェクトを受け取る可能性があります。したがって、otherの型をチェックし、適切に処理を分岐させることが重要です。

  • isinstance(other, MyClass): otherが特定のクラスのインスタンスであるかを確認します。

  • NotImplementedを返す: もしotherselfのクラスとの引き算をサポートしない型である場合、NotImplementedを返すべきです。これにより、Pythonはother側の__rsub__メソッド(後述)を試したり、最終的にTypeErrorを発生させたりします。TypeErrorを直接発生させるよりも推奨されます。

 

3. __rsub__との関係(Reverse Subtract)

 

-演算子で左右のオブジェクトの型が異なる場合、Pythonは特殊なルールでどちらの__sub__メソッドを呼び出すかを決定します。

例えば obj1 - obj2 という式があったとき:

  1. obj1.__sub__(obj2) が試されます。

  2. もし obj1.__sub__(obj2)NotImplementedを返した場合、Pythonは obj2.__rsub__(obj1) (リバース減算)を試します。

  3. どちらもNotImplementedを返すか、対応するメソッドが存在しない場合、TypeErrorが発生します。

これにより、例えばMyClassのオブジェクトと組み込み型(例: int)を引き合わせる場合でも、適切に処理を定義できます。

Python
 
class MyCounter:
    def __init__(self, value):
        self.value = value
    def __repr__(self): return f"MyCounter({self.value})"

    def __sub__(self, other):
        if isinstance(other, MyCounter):
            return MyCounter(self.value - other.value)
        elif isinstance(other, int):
            return MyCounter(self.value - other)
        return NotImplemented # これが重要

    def __rsub__(self, other): # other - self が呼び出された時
        if isinstance(other, int):
            return MyCounter(other - self.value)
        return NotImplemented

counter = MyCounter(10)
print(counter - MyCounter(3)) # MyCounter(7) (MyCounter.__sub__ が呼び出される)
print(counter - 2)           # MyCounter(8) (MyCounter.__sub__ が呼び出される)
print(50 - counter)          # MyCounter(40) (int.__sub__ が NotImplemented を返し、MyCounter.__rsub__ が呼び出される)

__rsub__は、左側のオブジェクトが右側のオブジェクトを処理できない場合に、右側のオブジェクトのメソッドが呼び出されるという「フォールバック」の役割を果たします。


 

まとめ

 

__sub__メソッドは、Pythonのオブジェクト指向プログラミングにおいて、カスタムクラスに直感的で自然な「引き算」の挙動を与えるための強力なツールです。

  • -演算子の動作をカスタマイズするための特殊メソッド

  • 常に新しいオブジェクトを返すべきで、元のオブジェクトを直接変更しない。

  • other引数の型を適切にチェックし、サポートしない型の場合は**NotImplementedを返す**。

  • __rsub__メソッドと連携して、異なる型のオブジェクト間の減算も処理できる。

__sub__を使いこなすことで、あなたのカスタムクラスはよりPythonicで、まるで組み込み型のように自然に振る舞うようになります。ぜひ今日学んだことを、あなたのコーディングに活かしてみてくださいね!


 

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座