カスタムクラスを書くとき、==
と!=
演算子を使って等価を許可することはしばしば重要です。 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
は、a is b
という同一性のテストを実際に実行するため、False
と評価されます(つまり、「a
はb
と同じオブジェクトですか?」)。
__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では、 documentation のように、常に__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では、 documentation のようにこれはもう必要ありません。
デフォルトでは、
__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種類のクラスがあります。
classic-style(またはold-style)クラスは、notをobject
から継承し、class A:
、class A():
として宣言されます。またはclass A(B):
、ここでB
はクラシックスタイルのクラスです。
object
から継承し、class A(object)
またはclass A(B):
として宣言されている---(new-styleクラス。ここで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
値を返す必要があります。 documentation はNotImplemented
の値を次のように定義します。
数値メソッドおよびリッチ比較メソッドは、提供されているオペランドに対して操作を実装していない場合、この値を返すことがあります。 (インタプリタは、オペレータに応じて、反映された操作またはその他のフォールバックを試みます。)その真理値は真です。
この場合、演算子は比較操作をotherオペランドの反射されたメソッドに委任します。 documentation は反映されたメソッドを以下のように定義します。
これらのメソッドの引数を入れ替えたバージョンはありません(左の引数が操作をサポートしていないが右の引数がサポートしている場合に使用されます)。むしろ、
__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 not NotImplemented:
return not x
return NotImplemented
==
演算子と!=
演算子のcommutativityが無関係の型である(継承がない)場合は、新しいスタイルのクラスに対しても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
継承には注意が必要です。
>>> class Foo:
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
>>> class Bar(Foo):pass
>>> b = Bar()
>>> f = Foo()
>>> f == b
True
>>> b == f
False
このように、型をより厳密にチェックしてください。
def __eq__(self, other):
if type(other) is type(self):
return self.__dict__ == other.__dict__
return False
それ以外にも、あなたのアプローチはうまくいくでしょう、それが特別な方法があるのです。
あなたが説明する方法は私がいつもそれをした方法です。それは完全に汎用的なので、あなたはいつでもその機能性をmixinクラスに分割して、あなたがその機能性が欲しいクラスでそれを継承することができます。
class CommonEqualityMixin(object):
def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
class Foo(CommonEqualityMixin):
def __init__(self, item):
self.item = item
直接的な答えではありませんが、時々冗長な作業を少し省くことができるので、取り組むには十分な意味があるようです。ドキュメントから直接切り取ります...
1つ以上のリッチ比較順序付けメソッドを定義するクラスが与えられると、このクラスデコレータは残りを提供します。これにより、可能なすべてのリッチ比較を指定する作業が簡単になります。オペレーション:
クラスは、lt()、le()のいずれかを定義する必要があります。 gt()、またはge()。さらに、クラスはeq()メソッドを提供する必要があります。
バージョン2.7の新機能
@total_ordering
class Student:
def __eq__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) ==
(other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other):
return ((self.lastname.lower(), self.firstname.lower()) <
(other.lastname.lower(), other.firstname.lower()))
__eq__
と__ne__
の両方をオーバーライドする必要はありません。__cmp__
のみをオーバーライドすることができますが、これは==、!==、<、>などの結果に影響します。
is
は、オブジェクトの同一性をテストします。つまり、aとbの両方が同じオブジェクトへの参照を保持している場合、a is
bはTrue
になります。 pythonでは、実際のオブジェクトではなく、常に変数へのオブジェクトへの参照を保持しているので、本質的にa is bが真になるためには、それらのオブジェクトは同じメモリ位置に配置されるべきです。どのようにそして最も重要なことに、なぜあなたはこの振る舞いを無効にしますか?
編集:私は__cmp__
がpython 3から削除されたことを知らなかったので、それを避けてください。
この答えから: https://stackoverflow.com/a/30676267/541136 私は、__ne__
を__eq__
という用語で定義するのは正しいのですが、それを証明しました。
def __ne__(self, other):
return not self.__eq__(other)
あなたが使用する必要があります:
def __ne__(self, other):
return not self == other
私はあなたが探している2つの用語は平等(==)とアイデンティティ(is)であると思います。例えば:
>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b
True <-- a and b have values which are equal
>>> a is b
False <-- a and b are not the same list object
'is'テストは、本質的にオブジェクトのメモリアドレスを返す組み込みの 'id()'関数を使ってアイデンティティをテストします。
ただし、クラスの等価性をテストする場合は、テストについてもう少し厳密にして、クラス内のデータ属性のみを比較することをお勧めします。
import types
class ComparesNicely(object):
def __eq__(self, other):
for key, value in self.__dict__.iteritems():
if (isinstance(value, types.FunctionType) or
key.startswith("__")):
continue
if key not in other.__dict__:
return False
if other.__dict__[key] != value:
return False
return True
このコードはあなたのクラスの非関数データメンバーを比較するだけでなく、一般的にあなたが望むものである私的なものは何もスキップします。普通のPythonオブジェクトの場合、__ init__、__str__、__repr__および__eq__を実装する基本クラスがあります。そのため、私のPOPOオブジェクトはそれ以外の(そしてほとんどの場合は同一の)ロジックの負担を負いません。
サブクラス化/ミックスインを使う代わりに、ジェネリッククラスデコレータを使うのが好きです。
def comparable(cls):
""" Class decorator providing generic comparison functionality """
def __eq__(self, other):
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
def __ne__(self, other):
return not self.__eq__(other)
cls.__eq__ = __eq__
cls.__ne__ = __ne__
return cls
使用法:
@comparable
class Number(object):
def __init__(self, x):
self.x = x
a = Number(1)
b = Number(1)
assert a == b