Skip to content

Python: Modernize 3 quality queries for comparison methods #20038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Update doc for equalsNotEquals
  • Loading branch information
joefarebrother committed Jul 14, 2025
commit ea48fcca8f55b76ed0383182734363b385c9b4cf
42 changes: 28 additions & 14 deletions python/ql/src/Classes/Comparisons/EqualsOrNotEquals.qhelp
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,47 @@
<qhelp>

<overview>
<p>In order to conform to the object model, classes should define either no equality methods, or both
an equality and an inequality method. If only one of <code>__eq__</code> or <code>__ne__</code> is
defined then the method from the super class is used. This is unlikely to result in the expected
behavior.</p>
<p>In order to ensure the <code>==</code> and <code>!=</code> operators behave consistently as expected (i.e. they should be negations of each other), care should be taken when implementing the
<code>__eq__</code> and <code>__ne__</code> special methods.</p>

<p>In Python 3, if the <code>__eq__</code> method is defined in a class while the <code>__ne__</code> is not,
then the <code>!=</code> operator will automatically delegate to the <code>__eq__</code> method in the expected way.
</p>

<p>However, if the <code>__ne__</code> method is defined without a corresponding <code>__eq__</code> method,
the <code>==</code> operator will still default to object identity (equivalent to the <code>is</code> operator), while the <code>!=</code>
operator will use the <code>__ne__</code> method, which may be inconsistent.

<p>Additionally, if the <code>__ne__</code> method is defined on a superclass, and the subclass defines its own <cde>__eq__</code> method without overriding
the superclass <code>__ne__</code> method, the <code>!=</code> operator will use this superclass <code>__ne__</code> method, rather than automatically delegating
to <code>__eq__</code>, which may be incorrect.

</overview>
<recommendation>

<p>When you define an equality or an inequality method for a class, remember to implement both an
<code>__eq__</code> method and an <code>__ne__</code> method.</p>
<p>Ensure that when an <code>__ne__</code> method is defined, the <code>__eq__</code> method is also defined, and their results are consistent.
In most cases, the <code>__ne__</code> method does not need to be defined at all, as the default behavior is to delegate to <code>__eq__</code> and negate the result. </p>

</recommendation>
<example>
<p>In the following example the <code>PointOriginal</code> class defines an equality method but
no inequality method. If this class is tested for inequality then a type error will be raised. The
<code>PointUpdated</code> class is better as it defines both an equality and an inequality method. To
comply fully with the object model this class should also define a hash method (identified by
a separate rule).</p>
<p>In the following example, <code>A</code> defines a <code>__ne__</code> method, but not an <code>__eq__</code> method.
This leads to inconsistent results between equality and inequality operators.
</p>

<sample src="examples/EqualsOrNotEquals1.py" />

<p>In the following example, <code>C</code> defines an <code>__eq__</code> method, but its <code>__ne__</code> implementation is inherited from <code>B</code>,
which is not consistent with the equality operation.
</p>

<sample src="EqualsOrNotEquals.py" />
<sample src="examples/EqualsOrNotEquals2.py" />

</example>
<references>


<li>Python Language Reference: <a href="http://docs.python.org/2/reference/datamodel.html#object.__ne__">object.__ne__</a>,
<a href="http://docs.python.org/2/reference/expressions.html#comparisons">Comparisons</a>.</li>
<li>Python Language Reference: <a href="http://docs.python.org/3/reference/datamodel.html#object.__ne__">object.__ne__</a>,
<a href="http://docs.python.org/3/reference/expressions.html#comparisons">Comparisons</a>.</li>


</references>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ If the ordering is not consistent with default equality, then <code>__eq__</code
</p>

<p>
The <code>functools.total_ordering</code> class decorator can be used to automatically implement all four comparison methods from a single one,
The <code>functools.total_ordering</code> class decorator can be used to automatically implement all four comparison methods from a
single one,
which is typically the cleanest way to ensure all necessary comparison methods are implemented consistently.</p>

</recommendation>
Expand Down
33 changes: 0 additions & 33 deletions python/ql/src/Classes/Comparisons/examples/EqualsOrNotEquals.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class A:
def __init__(self, a):
self.a = a

# BAD: ne is defined, but not eq.
def __ne__(self, other):
if not isinstance(other, A):
return NotImplemented
return self.a != other.a

x = A(1)
y = A(1)

print(x == y) # Prints False (potentially unexpected - object identity is used)
print(x != y) # Prints False
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class B:
def __init__(self, b):
self.b = b

def __eq__(self, other):
return self.b == other.b

def __ne__(self, other):
return self.b != other.b

class C(B):
def __init__(self, b, c):
super().init(b)
self.c = c

# BAD: eq is defined, but != will use superclass ne method, which is not consistent
def __eq__(self, other):
return self.b == other.b and self.c == other.c

print(C(1,2) == C(1,3)) # Prints False
print(C(1,2) != C(1,3)) # Prints False (potentially unexpected)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy