2017/10/05更新

[Python] 独自クラスで比較演算ができるようにする

このエントリーをはてなブックマークに追加      

こんにちは、@yoheiMuneです。
今日はPythonの独自クラスで、比較演算子を実装する方法をブログに書きたいと思います。独自クラスに演算が定義できるなんて素敵ですね。
画像

目次




独自クラスは、デフォルトでは演算ができない

このブログでは、以下のクラスを例に話を進めます。
class Item(object):
    def __init__(self, price):
        self.price = price
自身のフィールドにpriceという価格を表現するフィールドを持つクラスです。そして今回は、この価格情報を用いて比較ができるように実装したいと思います。

上記の状態で、演算をしようとすると、以下のようにエラーとなってしまいます。
item1 = Item(100)
item2 = Item(101)
# 演算してみると、エラー
print(item1 < item2)

# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: '<' not supported between instances of 'Item' and 'Item'
エラーの内容は「<演算子はItemクラス同士の比較をサポートしていません」とのこと。これをできるようにしようというのが、今回のブログです。



独自クラスに比較演算を実装する

以下の特殊メソッドを実装することで、独自クラスに比較演算を定義することができます。
メソッド名 内容
__lt__ Less Than:未満
__le__ Less Than or Equal:以下
__eq__ Equal:同じ
__ne__ Not Equal:違う
__gt__ Greater Than:より大きい
__ge__ Greater Than or Equal:以上
上記の6つを実装することで、独自クラスでも比較演算ができるようになります(正確には__eq__を実装してあれば、__ne__は実装しなくても大丈夫です)。

具体的には以下のように実装します。
class Item(object):

    def __init__(self, price):
        self.price = price

    def __eq__(self, other):
        if not isinstance(other, Item):
            return NotImplemented
        return self.price == other.price    

    def __lt__(self, other):
        if not isinstance(other, Item):
            return NotImplemented
        return self.price < other.price

    def __ne__(self, other):
        return not self.__eq__(other)

    def __le__(self, other):
        return self.__lt__(other) or self.__eq__(other)

    def __gt__(self, other):
        return not self.__le__(other)

    def __ge__(self, other):
        return not self.__lt__(other)
途中で出てくるNotImplemented(ドキュメントはこちら)は、演算をサポートしていないことを意味します。ここでは「Priceクラス同士の演算以外はサポート対象しない」という実装になります。

上記を実装すると、以下のように演算ができるようになります。
item1 = Item(100)
item2 = Item(101)
print(item1 == item2)  # False
print(item1 == 101)    # False
print(item1 < item2)   # True
print(item1 != item2)  # True
print(item1 <= item2)  # True
print(item1 > item2)   # False
print(item1 >= item2)  # False
ということで、基本はここまでです。



total_orderingを利用して楽をする

上記を実装してみると、__eq____lt__の2つさえ実装してしまえば、それ以外はそれらの組み合わせで表現できることがわかります。

そしてPythonでは、その2つだけ実装すれば、それ以外を自動的に実現してくれる、functoolsパッケージのtotal_orderingというでコレーターが存在します(公式ドキュメントはこちら)。Pythonさんすごいですね。

具体的には以下のように実装します。
"""
    便利デコレーター(total_ordering)を使う場合
"""
from functools import total_ordering

# クラスにデコレーターをつける
@total_ordering
class Student(object):

    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname

    def __eq__(self, other):
        if not isinstance(other, Student):
            return NotImplemented
        return (self.firstname, self.lastname) == (other.firstname, other.lastname)

    def __lt__(self, other):
        if not isinstance(other, Student):
            return NotImplemented
        return (self.firstname, self.lastname) < (other.firstname, other.lastname)


student1 = Student("Hen", "Lee")
student2 = Student("Ken", "Koba")
student3 = Student("Ken", "Koba")  # student2と同じ内容
print(student1 == student2)  # False
print(student2 == student3)  # True
print(student1 != student2)  # True
print(student1 < student2)   # True
print(student1 <= student2)  # True
print(student1 > student2)   # False
print(student1 >= student2)  # False
上記のように、以前の実装に比べてスッキリと実装することができます。ただしドキュメントにも記載がありますが、実行速度は少し落ちるようです。多くの場合には問題にならないと思いますが、パフォーマンス問題になる場合には前章のように全部自分で実装しちゃった方が良いです。



参考資料

今回の実装を行うのに、以下のドキュメントを参照しました。ありがとうございます。

3. データモデル — Python 3.6.1 ドキュメント

3. 組み込み定数 — Python 3.6.3rc1 ドキュメント

10.2. functools — 高階関数と呼び出し可能オブジェクトの操作 — Python 3.6.1 ドキュメント



最後に

演算子のオーバーライドができる言語ってなかなか素敵ですね。使いすぎるとカオスになりそうですが、ほどほどに便利に使えたらなと思います。

最後になりますが本ブログでは、Python・Go言語・Linux・フロントエンド・Node.js・インフラ・開発環境・Swift・Java・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。