2019年7月23日

演算子の話② < 演算子 が行うこと

前回、dict.__lt__() はビルトイン定数 NotImplemented を返すという話をしました。これがどのような意味があるのか、< 演算子の実処理を見ると明らかになります。

< 演算子の実処理は以下のようなものです。

def impl_lt(lhs, rhs):
    # first try with (lhs < rhs)
    ret = lhs.__lt__(rhs)
    if ret != NotImplemented:
        return ret

    # second try with (rhs > lhs)
    ret = rhs.__gt__(lhs)
    if ret != NotImplemented:
        return ret

    # give up operator <
    raise TypeError
最初に、最も素直な呼び出しである lhs.__lt__(rhs) を試します。
最初の呼び出しが NotImplemented を返すと、フォールバックとして、形は異なるが同じ意味であるはずの rhs.__gt__(lhs) を試します。
どちらも NotImplemented であれば、例外を投げて終了します。


フォールバックが機能する例を示します。
以下のような、自身と同じ型との __gt__() だけを備えたクラスを考えます。
class Stored(object):
    def __init__(self, value):
        self.value = value

    def __gt__(self, rhs):  # >
        if isinstance(rhs, Stored):
            print('Stored > Stored')
            return self.value > rhs.value
        return NotImplemented

# 素直に Stored.__gt__(Stored) が呼ばれる
>>> Stored(2) > Stored(1)
Stored > Stored
True

# Stored.__lt__(Stored) が定義されていないので、
# 代わりに Stored.__gt__(Stored) が呼ばれる
>>> Stored(1) < Stored(2)
Stored > Stored
True
Storedクラスは __gt__() しか持っていなくとも、< 演算子で結果を得ることができました。これがフォールバックが働いた例となります。


フォールバックが特に役立つのは、分数クラスのように int や float と比較可能なクラスを自作した場合です。
int.__lt__(Fraction) は実装できませんが、Fraction側で Fraction.__lt__(int) などを実装しておけば、フォールバックが働くことで、1 < Fraction(1, 2) のような演算子を呼び出し可能となります。

0 件のコメント:

コメントを投稿