[解決済み] Pythonのクラスで等価性(equality)をサポートするエレガントな方法
質問
カスタムクラスを記述する際、しばしば
==
と
!=
演算子を使用します。Pythonでは、これを実現するために
__eq__
と
__ne__
という特殊なメソッドをそれぞれ使用します。私が見つけた最も簡単な方法は、次の方法です。
class Foo:
def __init__(self, item):
self.item = item
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
もっとエレガントな方法をご存じですか?を比較する上記の方法について、特に不利な点をご存知ですか?
__dict__
s?
備考
: 少し明確にしておきます。
__eq__
と
__ne__
が未定義の場合、このような動作になります。
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
ということです。
a == b
は、次のように評価されます。
False
を実行するので、本当に
a is b
は、同一性のテスト(すなわち、"である。
a
と同じオブジェクトです。
b
?quot;)となります。
いつ
__eq__
と
__ne__
が定義されている場合、このような動作になります(これが私たちが求めている動作です)。
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
解決方法は?
この簡単な問題を考えてみましょう。
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
つまり、Pythonはデフォルトで比較演算にオブジェクト識別子を使用するのです。
id(n1) # 140400634555856
id(n2) # 140400634555920
をオーバーライドして
__eq__
関数が問題を解決してくれるようです。
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
で
Python 2
をオーバーライドすることを忘れないでください。
__ne__
関数も同様に
ドキュメント
と記載されています。
比較演算子の間に暗黙の関係はない。また の真偽は
x==y
を意味するものではありません。x!=y
は偽である。したがって 定義__eq__()
を定義する必要があります。__ne__()
というように 演算子は期待通りの動作をします。
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
で Python 3 のように、これはもはや必要ではありません。 ドキュメント と記載されています。
デフォルトでは
__ne__()
に委ねる。__eq__()
を反転させ、その結果を でない限りNotImplemented
. その他の暗黙の了解はありません。 比較演算子間の関係、例えば、真偽判定は の(x<y or x==y)
を意味するものではありません。x<=y
.
しかし、それですべての問題が解決するわけではありません。サブクラスを追加してみましょう。
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
注 Python 2には2種類のクラスがあります。
-
クラシックスタイル (または 旧式 を行うクラスです。 ではなく を継承しています。
object
として宣言されているもので、かつclass A:
,class A():
またはclass A(B):
ここでB
はクラシックスタイルのクラスです。 -
新スタイル を継承しているクラスは
object
として宣言されclass A(object)
またはclass A(B):
ここでB
は新スタイルのクラスです。Python 3 では、新スタイルのクラスは以下のように宣言されたものだけです。class A:
,class A(object):
またはclass A(B):
.
クラシックスタイルのクラスでは、比較演算は常に最初のオペランドのメソッドを呼び出しますが、新しいスタイルのクラスでは、常にサブクラス・オペランドのメソッドを呼び出します。 オペランドの順番に関係なく .
つまり、ここでは、もし
Number
はクラシックスタイルのクラスです。
-
n1 == n3
コールn1.__eq__
; -
n3 == n1
コールn3.__eq__
; -
n1 != n3
コールn1.__ne__
; -
n3 != n1
コールn3.__ne__
.
そして、もし
Number
は新スタイルのクラスです。
-
両方
n1 == n3
とn3 == n1
コールn3.__eq__
; -
両方
n1 != n3
とn3 != n1
コールn3.__ne__
.
の非可換性の問題を解決するために
==
と
!=
演算子で、Python 2 のクラシックスタイルのクラスでは
__eq__
と
__ne__
メソッドは
NotImplemented
の値は、オペランド型がサポートされていない場合に使用されます。また
ドキュメント
が定義している
NotImplemented
の値として使用します。
数値メソッドやリッチな比較メソッドは、次のような場合にこの値を返すことがあります。 は、指定されたオペランドに対してその操作を実装していない。(その インタープリタは反映された操作を試すか、あるいは他の 演算子によってはフォールバックします)。真偽値はtrueである。
この場合、演算子は比較演算を 反映メソッド の その他 オペランドを指定します。は、その ドキュメンテーション は、反映されたメソッドを次のように定義している。
これらのメソッドには、引数を入れ替えたバージョン(Swapped-argument versions)はありません。 左の引数がその操作をサポートしていないが、右の引数がその操作をサポートしている場合 の引数があります)、むしろ
__lt__()
と__gt__()
は、互いの を反映したものです。__le__()
と__ge__()
は互いの反射であり__eq__()
と__ne__()
は自分自身の反映です。
結果はこのようになります。
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is NotImplemented:
return NotImplemented
return not x
を返します。
NotImplemented
の代わりに
False
の場合は、新スタイルのクラスでも正しい動作です。
可換性
の
==
と
!=
演算子は、オペランドが無関係な型である場合(継承されない場合)に使用されることが望まれます。
まだですか?まだです。ユニークナンバーはいくつあるのでしょうか?
len(set([n1, n2, n3])) # 3 -- oops
セットはオブジェクトのハッシュを使用し、デフォルトではPythonはオブジェクトの識別子のハッシュを返します。それをオーバーライドしてみましょう。
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
最終的には以下のようになります(最後に検証のためのアサーションを追加しました)。
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2
関連
-
Python カメの描画コマンドとその例
-
Python百行で韓服サークルの画像クロールを実現する
-
Python 入出力と高次代入の基礎知識
-
[解決済み】socket.error: [Errno 48] アドレスはすでに使用中です。
-
[解決済み] 'DataFrame' オブジェクトに 'sort' 属性がない
-
[解決済み】SyntaxError: デフォルト以外の引数がデフォルトの引数に続く
-
[解決済み] Pythonはショートカットに対応していますか?
-
[解決済み] Pythonのクラスはなぜオブジェクトを継承するのですか?
-
[解決済み] Pythonの旧スタイルのクラスと新スタイルのクラスの違いは何ですか?
-
[解決済み] Pythonにはクラス内に「プライベート」変数がある?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Pythonの非常に便利な2つのデコレーターを解説
-
Python jiabaライブラリの使用方法について説明
-
Python interpreted model libraryによる機械学習モデル出力の可視化 Shap
-
[解決済み】ilocが「IndexError: single positional indexer is out-of-bounds」を出す。
-
[解決済み】pygame.error: ビデオシステムが初期化されていない
-
[解決済み】ImportError: PILという名前のモジュールがない
-
[解決済み】TypeError: re.findall()でバイトのようなオブジェクトに文字列パターンを使用することはできません。)
-
[解決済み】Pythonでgoogle APIのJSONコードを読み込むとエラーになる件
-
[解決済み] Pythonで__eq__はどのように処理され、どのような順序で処理されるのですか?
-
[解決済み] ne__は__eq__の否定として実装されるべきか?