<p>考虑这个简单的问题:</p>
<pre><code>class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
</code></pre>
<p>因此,Python默认使用对象标识符进行比较操作:</p>
<pre><code>id(n1) # 140400634555856
id(n2) # 140400634555920
</code></pre>
<p>重写<code>__eq__</code>函数似乎可以解决问题:</p>
<pre><code>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
</code></pre>
<p>在<em>Python 2</em>中,也要记住重写<code>__ne__</code>函数以及<a href="https://docs.python.org/2.7/reference/datamodel.html#object.__ne__" rel="noreferrer">documentation</a>状态:</p>
<blockquote>
<p>There are no implied relationships among the comparison operators. The
truth of <code>x==y</code> does not imply that <code>x!=y</code> is false. Accordingly, when
defining <code>__eq__()</code>, one should also define <code>__ne__()</code> so that the
operators will behave as expected.</p>
</blockquote>
<pre><code>def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
</code></pre>
<p>在<em>Python 3</em>中,由于<a href="https://docs.python.org/3/reference/datamodel.html#object.__ne__" rel="noreferrer">documentation</a>声明:</p>
<blockquote>
<p>By default, <code>__ne__()</code> delegates to <code>__eq__()</code> and inverts the result
unless it is <code>NotImplemented</code>. There are no other implied
relationships among the comparison operators, for example, the truth
of <code>(x<y or x==y)</code> does not imply <code>x<=y</code>.</p>
</blockquote>
<p>但这并不能解决我们所有的问题。让我们添加一个子类:</p>
<pre><code>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
</code></pre>
<p><strong>注意:Python 2有两种类型:</p>
<ul>
<li><p><em><a href="https://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes" rel="noreferrer">classic-style</a></em>(或<em>旧样式</em>)类,它们不从<code>object</code>继承,声明为<code>class A:</code>、<code>class A():</code>或<code>class A(B):</code>,其中<code>B</code>是一个经典样式类;</p></li>
<li><p><em><a href="https://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes" rel="noreferrer">new-style</a></em>类,继承自<code>object</code>,声明为<code>class A(object)</code>或<code>class A(B):</code>,其中<code>B</code>是一个新样式的类。Python 3只有声明为<code>class A:</code>、<code>class A(object):</code>或<code>class A(B):</code>的新样式类。</p></li>
</ul>
<p>对于经典样式的类,比较操作总是调用第一个操作数的方法,而对于新样式的类,它总是调用子类操作数的方法<a href="https://stackoverflow.com/a/12984987/78234">regardless of the order of the operands</a>。</p>
<p>所以在这里,如果<code>Number</code>是一个典型的样式类:</p>
<ul>
<li><code>n1 == n3</code>调用<code>n1.__eq__</code></li>
<li><code>n3 == n1</code>调用<code>n3.__eq__</code></li>
<li><code>n1 != n3</code>调用<code>n1.__ne__</code></li>
<li><code>n3 != n1</code>调用<code>n3.__ne__</code>。</li>
</ul>
<p>如果<code>Number</code>是一个新样式的类:</p>
<ul>
<li><code>n1 == n3</code>和<code>n3 == n1</code>都调用<code>n3.__eq__</code></li>
<li><code>n1 != n3</code>和<code>n3 != n1</code>都调用<code>n3.__ne__</code>。</li>
</ul>
<p>要解决Python 2经典样式类的<code>==</code>和<code>!=</code>运算符的不可交换性问题,当不支持操作数类型时,<code>__eq__</code>和<code>__ne__</code>方法应返回<code>NotImplemented</code>值。<a href="https://docs.python.org/2.7/reference/datamodel.html#the-standard-type-hierarchy" rel="noreferrer">documentation</a>将<code>NotImplemented</code>值定义为:</p>
<blockquote>
<p>Numeric methods and rich comparison methods may return this value if
they do not implement the operation for the operands provided. (The
interpreter will then try the reflected operation, or some other
fallback, depending on the operator.) Its truth value is true.</p>
</blockquote>
<p>在这种情况下,运算符将比较操作委托给<em>其他</em>操作数的<em>反射方法</em>。<a href="https://docs.python.org/2.7/reference/datamodel.html#object.__lt__" rel="noreferrer">documentation</a>将反射方法定义为:</p>
<blockquote>
<p>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, <code>__lt__()</code> and <code>__gt__()</code> are each other’s
reflection, <code>__le__()</code> and <code>__ge__()</code> are each other’s reflection, and
<code>__eq__()</code> and <code>__ne__()</code> are their own reflection.</p>
</blockquote>
<p>结果如下:</p>
<pre><code>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
</code></pre>
<p>如果<code>==</code>和<code>!=</code>运算符的<em>交换性</em>在操作数为不相关类型(没有继承)时是需要的,那么返回<code>NotImplemented</code>值而不是<code>False</code>是正确的,即使对于新样式的类也是如此。</p>
<p>我们到了吗?不完全是。我们有多少个唯一的号码?</p>
<pre><code>len(set([n1, n2, n3])) # 3 -- oops
</code></pre>
<p>集合使用对象的散列,默认情况下Python返回对象标识符的散列。让我们尝试覆盖它:</p>
<pre><code>def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
</code></pre>
<p>最终结果如下(我在最后添加了一些断言以供验证):</p>
<pre><code>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
</code></pre>