Back to Blog
Python

Python ne: Implementing Not Equal for Custom Classes

Learn how Python's __ne__ method controls !=, why the default can be surprising, and how to implement it correctly for custom classes.

__ne__Python dunder methodsobject comparisonoperator overloadingcustom classes
Illustration of two Python objects being compared with a not-equal operator, highlighting the __ne__ method.

In Python, != is controlled by the __ne__ method. Built-in types handle this for you, but for custom classes the behavior is worth understanding: if you define only __eq__, Python derives != from it. That fallback usually works, but it can be surprising when __eq__ returns NotImplemented or when equality is asymmetric.

What ne Means in Python

ne is short for not equal. The != operator calls __ne__ on the left operand, passing the right operand as an argument. When a class does not define __ne__, Python uses a default implementation that delegates to __eq__ and negates the result. If __eq__ returns NotImplemented, the default __ne__ also returns NotImplemented, which lets Python try the reflected comparison on the other operand.

Consider a simple 2D point class:

class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if isinstance(other, Point): return self.x == other.x and self.y == other.y return NotImplemented

This class defines __eq__ but not __ne__. Evaluating Point(1, 2) != Point(3, 4) uses the default __ne__, which delegates to __eq__ and returns True. The simple case works.

Default Behavior and the NotImplemented Fallback

When a comparison method returns NotImplemented, Python does not treat it as True or False. Instead, it tries the same comparison on the other operand. If both sides return NotImplemented, == and != fall back to identity comparison: == is False unless the operands are the same object, and != is the opposite.

For example, Point(1, 2) != 5 calls the default __ne__, which delegates to Point.__eq__(5). That returns NotImplemented because 5 is not a Point. The default __ne__ propagates NotImplemented, so Python tries the other operand's __ne__; when that is also unsupported, the comparison falls back to identity and returns True. This is often the correct result, but it depends on both operands having cooperative comparison methods.

Implementing __ne__ Correctly

In modern Python, defining __eq__ also gives you a usable default __ne__; it delegates to __eq__ and propagates NotImplemented. You usually do not need to define __ne__ separately. However, defining it explicitly makes the comparison contract visible and ensures you review the behavior if __eq__ changes later.

If you choose to implement it, the recommended pattern is:

class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if isinstance(other, Point): return self.x == other.x and self.y == other.y return NotImplemented def __ne__(self, other): result = self.__eq__(other) if result is NotImplemented: return NotImplemented return not result

This makes != the logical negation of == for supported types and passes NotImplemented through for unsupported types. The check for NotImplemented is essential because NotImplemented is truthy: not NotImplemented would evaluate to False, incorrectly reporting that two objects are equal when the comparison is unsupported.

NotImplemented and Asymmetric Comparisons

A custom __eq__ may support comparison in only one direction. Consider a Temperature class:

class Temperature: def __init__(self, celsius): self.celsius = celsius def __eq__(self, other): if isinstance(other, Temperature): return self.celsius == other.celsius if isinstance(other, (int, float)): return self.celsius == other return NotImplemented def __ne__(self, other): result = self.__eq__(other) if result is NotImplemented: return NotImplemented return not result

Here, Temperature(20) == 20 returns True, and Temperature(20) != 20 returns False.

What about 20 == Temperature(20)? Python first calls int.__eq__, which returns NotImplemented, then calls Temperature.__eq__(20), which returns True. For 20 != Temperature(20), the default int.__ne__ delegates to int.__eq__ and propagates NotImplemented; Python then calls Temperature.__ne__(20), which returns False. The result is consistent with ==.

Common Pitfalls When Defining __ne__

The dangerous version is calling __eq__ directly without checking for NotImplemented:

class BadPoint: def __init__(self, x): self.x = x def __eq__(self, other): if isinstance(other, BadPoint): return self.x == other.x return NotImplemented def __ne__(self, other): return not self.__eq__(other) # Wrong: __eq__ may return NotImplemented

Comparing BadPoint(1) != 5 calls BadPoint.__ne__, which calls BadPoint.__eq__(5) directly. Because that call returns NotImplemented, not NotImplemented evaluates to False, so != incorrectly returns False. The == operator itself would have resolved NotImplemented through the fallback, but a direct call to __eq__ does not.

Another source of bugs is letting __eq__ and an explicit __ne__ drift apart. If you change __eq__, update or remove the custom __ne__ so the two methods stay consistent.

Using Automatic Equality Methods

For simple value objects, you can avoid manually writing either method. A @dataclass generates __eq__; Python's default __ne__ then keeps != consistent with it:

from dataclasses import dataclass @dataclass class Point: x: int y: int

Named tuples behave similarly. If you use functools.total_ordering, define __eq__ and one ordering method; the decorator adds the remaining ordering comparisons, and Python's default __ne__ is derived from __eq__.

Writing Predictable Custom Classes

Implementing __ne__ deliberately is most useful for domain classes with custom equality rules, such as comparing objects by a business key. Using the __eq__-delegating pattern keeps == and != in sync. Performance is rarely a reason to avoid the explicit method; the cost of one extra method call is small compared with the actual comparison work.

Correctly handling NotImplemented is the important part. Once __ne__ propagates that sentinel and inverts only real equality results, != will behave predictably for custom classes.

Python __ne__: How to Implement Not Equal for Custom Classes | RYUSLOG DEV