Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
125: Magic Methods for Comparison Operators
I've seen this happen to a lot of junior devs when they start building their own domain models. You create a class to represent a real-world concept—like a software version, a price, or a game character's stats—and then you try to sort a list of them or check if one is "greater" than another. Then, Python hits you with a TypeError.
class SoftwareVersion:
def __init__(self, major, minor, patch):
self.major = major
self.minor = minor
self.patch = patch
def __repr__(self):
return f"{self.major}.{self.minor}.{self.patch}"
v1 = SoftwareVersion(1, 2, 0)
v2 = SoftwareVersion(1, 10, 0)
if v1 < v2:
print("Updating software...")
# TypeError: '<' not supported between instances of 'SoftwareVersion' and 'SoftwareVersion'
The 'TypeError' that stops your app in its tracks
The problem here is that Python has no inherent idea of what makes one SoftwareVersion "less than" another. To Python, your object is just a heap of data in memory. When you use the < operator, Python looks for a specific "magic method" inside your class. If it doesn't find it, it gives up and throws that error.
You might be tempted to just write a helper function like is_version_less(v1, v2), but that's a mistake. It makes your code clunky and prevents you from using built-in Python features like sorted() or min() and max(), which rely entirely on these magic methods.
Teaching Python how to actually compare objects
To fix this, we need to implement the comparison dunder (double-underscore) methods. The most critical ones are __eq__ (equal) and __lt__ (less than).
When comparing versions, we can't just compare the whole object; we have to compare the components in order of importance: major, then minor, then patch. A great trick here is to use tuples. Python knows how to compare tuples element-by-element, so we can leverage that instead of writing a mountain of if/else statements.
class SoftwareVersion:
def __init__(self, major, minor, patch):
self.major = major
self.minor = minor
self.patch = patch
def __eq__(self, other):
if not isinstance(other, SoftwareVersion):
return NotImplemented
return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)
def __lt__(self, other):
if not isinstance(other, SoftwareVersion):
return NotImplemented
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
def __repr__(self):
return f"{self.major}.{self.minor}.{self.patch}"
v1 = SoftwareVersion(1, 2, 0)
v2 = SoftwareVersion(1, 10, 0)
print(v1 < v2) # Now it returns True!
I added that isinstance check and the NotImplemented return for a reason. If you accidentally try to compare a SoftwareVersion object to a string or an integer, returning NotImplemented tells Python, "I don't know how to handle this type," which allows Python to potentially ask the other object if it knows how to handle the comparison before finally failing.
Saving time with total_ordering
Now, you might be thinking: "Do I really have to write __gt__, __le__, and __ge__ as well?" The answer is: usually, no. Writing all six comparison methods is tedious and error-prone.
Python provides a decorator in the functools module called total_ordering. If you define __eq__ and one other comparison method (like __lt__), this decorator automatically fills in the rest for you. It's a massive time-saver.
from functools import total_ordering
@total_ordering
class SoftwareVersion:
def __init__(self, major, minor, patch):
self.major = major
self.minor = minor
self.patch = patch
def __eq__(self, other):
if not isinstance(other, SoftwareVersion):
return NotImplemented
return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)
def __lt__(self, other):
if not isinstance(other, SoftwareVersion):
return NotImplemented
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
v1 = SoftwareVersion(1, 2, 0)
v2 = SoftwareVersion(1, 10, 0)
# These now work automatically because of @total_ordering
print(v1 > v2) # False
print(v1 <= v2) # True
📋 Practical Task
Exercise: Implementing a Product Ranking System
You are building an e-commerce backend. You need to create a Product class that can be compared and sorted. The business logic for comparing products is as follows:
- Products are primarily compared by their
price(lower price is "less than" higher price). - If two products have the exact same price, they should be compared alphabetically by their
name.
Requirements:
- Create a
Productclass withnameandpriceattributes. - Use
functools.total_orderingto minimize the number of methods you write. - Implement
__eq__and__lt__to handle the price-then-name logic. - Verify your implementation by creating a list of
Productobjects (including some with identical prices) and using thesorted()function to sort them.
There are no comments for now.