質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

6回答

2682閲覧

【python】特殊メソッド __lt__ , __gt__ はどのように使い分けるのか。

rkymjr1991

総合スコア0

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

1グッド

0クリップ

投稿2022/12/02 11:00

python __it__と__gt__の使い分けについて

classの特殊メソッド lt と gt の使い分けについて、それぞれが>演算子と<演算子の役割を示し、インスタンスの比較ができるようになることは理解したのですが、それぞれの使い分けについて理解ができません。

__gt__を使った比較演算

python

1class Comparison: 2 def __init__(self,x): 3 self.x = x 4 5 def __gt__(self,other): 6 if self.x < other.x: 7 return True 8 elif self.x == other.x: 9 return None 10 else: 11 return False 12 13y1 = Comparison(22) 14y2 = Comparison(21) 15 16print(y1 > y2) 17#>>False

疑問

__gt__でself.xとother.xを比較し、結果を「True , False , None」で返すようにしていますが、比較演算子”>”の向きを変えれば逆の結果が帰って来ますし、例外も発生しないため__lt__とどのように使い分けるのか疑問を持っています。

また私が今勉強に使っている参考書では、同じclass内に__gt__と__lt__を同時に定義しており、中身は">"の向きを変えただけの全く同じものだったので、両方定義する理由があるのであれば知りたいと思い質問させていただきました。
下記に記載させていただきます。

疑問に持った参考書内のコード抜粋

python

1 def __init__(self, v, s): 2 """suit + value are ints""" 3 self.value = v 4 self.suit = s 5 6 def __lt__(self, c2): 7 if self.value < c2.value: 8 return True 9 if self.value == c2.value: 10 if self.suit < c2.suit: 11 return True 12 else: 13 return False 14 return False 15 16 def __gt__(self, c2): 17 if self.value > c2.value: 18 return True 19 if self.value == c2.value: 20 if self.suit > c2.suit: 21 return True 22 else: 23 return False 24 return False 25 26#__lt__と__gt__が全く同じ役割を示しているように 27#見えるため、同時に定義する理由が不明です。 28 29 def __repr__(self): 30 v = self.values[self.value] +\ 31 " of " + \ 32 self.suits[self.suit] 33 return v
melian👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答6

0

両方定義する理由があるのであれば知りたいと思い質問させていただきました。

a > bb < aのどちらか片方でしか使えない、なんてクラスを作っても、知って使うとしてもうっかり逆向きで書いてしまって動かないトラブルを生む、ましてやライブラリとして他人に使ってもらうには不便で不合理なものとなってしまいます。

投稿2022/12/02 11:13

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2022/12/02 11:14 編集

> 中身は">"の向きを変えただけの全く同じものだったので むしろ、向きによってまったく動作の違うクラスを作るほうが混乱します。
rkymjr1991

2022/12/02 11:56

ありがとうございます。両方実装しなかった場合にトラブルが生まれる可能性があるのであれば、両方実装しておいてまず間違いはなさそうですね。 初歩的な質問に快くご回答いただきありがとうございました。
guest

0

回答ではありません

速度の話があったので測ってみました。

python

1import timeit 2 3class Hoge: 4 def __init__(self, v): 5 self.value = v 6 7class HogeWithLt(Hoge): 8 def __init__(self, v): 9 super().__init__(v) 10 def __lt__(self, right): 11 return self.value < right.value 12 13class HogeWithGt(Hoge): 14 def __init__(self, v): 15 super().__init__(v) 16 def __gt__(self, right): 17 return self.value > right.value 18 19class HogeWithLtAndGt(Hoge): 20 def __init__(self, v): 21 super().__init__(v) 22 def __lt__(self, right): 23 return self.value < right.value 24 def __gt__(self, right): 25 return self.value > right.value 26 27for cls in ('HogeWithLt', 'HogeWithGt', 'HogeWithLtAndGt'): 28 print(f'[{cls}]') 29 print(timeit.repeat(f'v1 < v2', f'v1={cls}(1);v2={cls}(2)', number=10000000, globals=globals())) 30 print(timeit.repeat(f'v2 > v1', f'v1={cls}(1);v2={cls}(2)', number=10000000, globals=globals()))

sh

1$ python3 --version 2Python 3.8.10 3$ python3 sample.py 4[HogeWithLt] 5[1.3253876060189214, 1.35883577002096, 1.4073622680152766, 1.36936645701644, 1.31191633301205] 6[2.016354927996872, 2.013986409001518, 2.0291321120166685, 2.0595013450074475, 2.0382606999774] 7[HogeWithGt] 8[2.0358829490141943, 2.075015729002189, 2.076373985997634, 1.9962386050028726, 2.053162529016845] 9[1.328342495020479, 1.3259293310111389, 1.3575235510070343, 1.3413178710034117, 1.316727269004332] 10[HogeWithLtAndGt] 11[1.3144145520054735, 1.316202103975229, 1.3855247679748572, 1.3094538759905845, 1.3577031239983626] 12[1.3365965629927814, 1.3250991759996396, 1.3101062320056371, 1.3281135029974394, 1.3213003540004138] 13$

私の環境だと両方定義した方が割と速いようですね。


ついでにpypyでも測ってみました。

sh

1$ pypy3 -V 2Python 3.6.9 (7.3.1+dfsg-4, Apr 22 2020, 05:15:29) 3[PyPy 7.3.1 with GCC 9.3.0] 4$ pypy3 sample.py 5[HogeWithLt] 6[0.013435224012937397, 0.01156763598555699, 0.011592883995035663, 0.011952064000070095, 0.011544334003701806, 0.01208959097857587, 0.01161127700470388] 7[0.013763035996817052, 0.011804156994912773, 0.011587033019168302, 0.012147278001066297, 0.011602851998759434, 0.011594195995712653, 0.011793879006290808] 8[HogeWithGt] 9[0.013169176992960274, 0.01172563000000082, 0.01144949599984102, 0.01157167399651371, 0.011531008000019938, 0.011383701988961548, 0.011427515011746436] 10[0.016236709023360163, 0.01355474698357284, 0.02249747299356386, 0.011509436997584999, 0.02142298500984907, 0.01737112400587648, 0.011405222991015762] 11[HogeWithLtAndGt] 12[0.01291838800534606, 0.011729608988389373, 0.011620064004091546, 0.011509778996696696, 0.011812743992777541, 0.01150153300841339, 0.01154042498092167] 13[0.01317515701521188, 0.012265279976418242, 0.011791975004598498, 0.01159220200497657, 0.011521228996571153, 0.011551906995009631, 0.011629011016339064] 14$

流石に有意な差はなさそうです。

投稿2022/12/02 16:16

編集2022/12/03 00:57
dameo

総合スコア943

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dameo

2022/12/08 08:34

回答じゃないと大きく書いてますよ。ただの補足です。
guest

0

まずサンプルのコードはサンプルであってそれだけを見て考えることに意味はありません。
包丁の説明書にトマトを切っている図しか載っていないからといってトマトしか切れないのか? と言っているようなものです。


(otnさんの回答への付記です)

質問の上のコード

python

1class Comparison: 2 def __init__(self,x): 3 self.x = x 4 5 def __gt__(self,other): 6 if self.x < other.x: 7 return True 8 elif self.x == other.x: 9 return None 10 else: 11 return False

で、

python

1y1 = Comparison(22) 2y2 = Comparison(21) 3 4print(y1 > y2) # => False 5print(y1 < y2) # => True

と、「__lt__が定義されていなくてもy1 < y2ができる」ことに気づいていましたか。

同じクラス間での比較を考えている限りはどちらか一方だけで十分です。(__lt____gt__がNotImplementedをraiseするクラスからの派生である限りにおいては)


__lt____gt__の両方を実装する必要が生じるのは、手を入れられない既存のクラスとの比較をしたい時です。

有理数のクラス(MyFractionとしましょう)を自前で作った時、intと比較したくなります。
__lt__だけだと、MyFraction(7, 3) < 3は動きますが3 < MyFraction(7, 3)は動きません。
前者はMyFraction(7, 3).__lt__(3)に解釈されて期待通り動きますが、後者は(3).__lt__(MyFraction(7, 3))と解釈されてNotImplementedがraiseされ、次にMyFraction(7, 3).__gt__(3)が試されてまたNotImplementedがraiseされるからです。

intの__lt__メソッドをいじることはできないので、ここでMyFractionに__gt__が必要になる理由が生じます。

投稿2022/12/02 12:42

quickquip

総合スコア11038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

他の方とちょっと違う意見なので。

pythonでは、書式として、中置記法の2項演算子(+とか×とか)が使えます。クラスで定義したオブジェクトに対してもそれらの演算子を使って処理をさせることができますが、それらの動作を定義するときに特殊なメソッド(特殊メソッド)で定義することにしています。「+」演算を定義するのは __add__メソッド、「×」を定義するのは__mul__メソッドのように前後に__がついたメソッドです。

ということで、「python __lt____gt__の使い分け」ということではなく、そもそも、クラスの定義でそのクラスのインスタンスで「<」や「>」の演算を実行できるようにするには両方定義する必要があるのです。
これが基本です。

ただ、pythonでは「<」と「>」は逆の機能を持つ演算なので、便利機能として、他の方が説明しているように片方が無ければ、一方で補完するようになっているのです。 これはあくまで便利機能なので、 本質ではありません。

投稿2022/12/02 12:35

TakaiY

総合スコア12747

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

比較演算子”>”の向きを変えれば逆の結果が帰って来ますし、例外も発生しないため__lt__とどのように使い分けるのか疑問を持っています。

__lt____gt__は、あらかじめNotImplementedを返すように定義されていますので、
y1 > y2で呼び出されるy1.__gt__(y2)Falseを返すのに対して、
y1 < y2で呼び出されるy1.__lt__(y2)NotImplementedを返します。

演算子でNotImplementedが返った場合、<>のように反射の関係にある演算子がある場合、右項のクラスの対のメソッドを左項を引数にして呼び出し、NotImplemented以外の値が返ったらその値を結果とします。
つまり、y1 < y2に対してはy1.__lt__(y2)の呼び出しの後、y2.__gt__(y1)が呼び出されてTrueを返すので、__lt__を定義しなくても期待通りの結果になります。

__lt__を適切に定義した方がわずかに速くなると思いますが、メンテを考えると片方で良い気がします。

参考:https://docs.python.org/ja/3/library/constants.html

二項演算の (あるいはインプレースの) メソッドが NotImplemented を返した場合、インタープリタはもう一方の型で定義された対の演算で代用を試みます (あるいは演算によっては他の代替手段も試みます)。試行された演算全てが NotImplemented を返した場合、インタープリタは適切な例外を送出します。

投稿2022/12/02 12:01

otn

総合スコア84507

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

object.__gt__(self, other)

There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); rather, lt() and gt() are each other’s reflection, __le__() and __ge__() are each other’s reflection, and __eq__() and __ne__() are their own reflection.

つまり、__gt__() メソッドのみが実装されている場合に __lt__() メソッドが呼び出されると、selfother を入れ替えて、other.__gt__(self) が実行されることになります。もちろん、otherself と同じ型(type)であることが前提となります。

投稿2022/12/02 11:39

melian

総合スコア19714

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rkymjr1991

2022/12/02 12:00

回答ありがとうございます。 呼び出されるメソッドは不等号の向きで決まっているのですね。 selfとotherが入れ替わった場合に、大きなコードになれば何かしら問題が発生する可能性もありそうなので、基本的に両方実装する認識で勉強を進めていこうと思います。 初歩的な質問に快くご回答いただきありがとうございました。
otn

2022/12/02 13:56 編集

左右同じ型限定なら問題は起きそうにないですが、左右が異なる型に対して適用すると、よく設計しないと混乱するかもです。 でも、継承関係に無い異なる型同士で不等号を定義したいと思うことは普通はないと思います。 猫クラスに、< の右辺が犬クラスなら真、ネズミクラスなら偽と定義するとか?
otn

2022/12/02 14:19

他の方の会頭にある通り、既存のクラスとの比較を行いたい場合は、両方定義要ですね。 まあ、今回の質問は、「片方の定義で事足りるのに何故両方定義するのか?」だと思うので、 同じ方同士のように片方で事足りる場合は片方で良いと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問