Comparable Classes in Python 3

Comparable classes in Python 3

To make classes comparable, you only need to implement __lt__ and decorate the class with functools.total_ordering. You should also provide an __eq__ method if possible. This provides the rest of the comparison operators so you don't have to write any of them yourself.

How do I make my class comparable in Python?

How about the following (Pair sortable by key but you can easily define any other way to sort them):

class Pair:
def __init__(self, key, val):
self.key = key
self.value = val

def __eq__(self, other: "Pair"):
return self.key == other.key

def __lt__(self, other: "Pair"):
return self.key < other.key

def __le__(self, other: "Pair"):
return self.key <= other.key

def __gt__(self, other: "Pair"):
return self.key > other.key

def __ge__(self, other: "Pair"):
return self.key >= other.key

def __str__(self):
return f"{self.key}={self.value}"

def __repr__(self):
return f"{self.key}={self.value} ({id(self)})"

test = [
Pair("a2", "1"), Pair("a1", "2"), Pair("b1", "3"),Pair("br", "4")
]

print(sorted(test))

Output:

$ python3 ~/tmp/test.py
[a1=2 (4352627216), a2=1 (4352622288), b1=3 (4352627344), br=4 (4352627408)]

To sort by value and then by key if the values are equal you would dp something like:

    def __lt__(self, other: "Pair"):
if self.value != other.value:
return self.value < other.value

return self.key < other.key

example input/output with the above lt:

# Input
test = [
Pair("a2", "1"), Pair("a1", "2"), Pair("b1", "1"),Pair("br", "4")
]

# Output
[a2=1 (4466773648), b1=1 (4466778768), a1=2 (4466778640), br=4 (4466778832)]

Additionally, if you plan to use the pairs as dictionary keys or in sets you can implement __hash__. For more operators see here

To make objects of a custom class comparable, is it enough to define just a few of the members in `__eq__` and `__lt__` family?

For two objects a and b, __cmp__ requires that one of a < b, a == b, and a > b is true. But that might not be the case: consider sets, where it's very common that none of those are true, e.g. {1, 2, 3} vs {4, 5, 6}.

So __lt__ and the likes were introduced. But that left Python with two separate ordering mechanisms, which is kind of ridiculous, so the less flexible one was removed in Python 3.

You don't actually have to implement all six comparison methods. You can use the functools.total_ordering class decorator to help define the rest of the magic comparison methods:

from functools import total_ordering
@total_ordering
class PQItem:
def __init__(self, priority, item):
self.priority = priority
self.item = item

def __eq__(self, other):
return isinstance(other, PQItem) and self.priority == other.priority

def __lt__(self, other):
return isinstance(other, PQItem) and self.priority < other.priority

How to compare two classes/types in python?

Explanation

This is why your comparison doesn't work as expected

>>> class ClassA(object):
... pass
...
>>> class ClassB(object):
... pass
...
>>> type(ClassB)
<class 'type'>
>>> type(ClassA)
<class 'type'>
>>> type(ClassA) == type(ClassB)
True

But why do ClassA and ClassB have the same type type? Quoting the docs:

By default, classes are constructed using type(). The class body is
executed in a new namespace and the class name is bound locally to the
result of type(name, bases, namespace).

Example:

>>> ClassB
<class '__main__.ClassB'>
>>> type('ClassB', (), {})
<class '__main__.ClassB'>
>>> type(ClassB)
<class 'type'>
>>> type(type('ClassB', (), {}))
<class 'type'>

Getting the type of ClassB is exactly the same as getting the type of type('ClassB', (), {}), which is type.

Solutions

Compare them directly (w/out using the type() function):

>>> ClassA
<class '__main__.ClassA'>
>>> ClassB
<class '__main__.ClassB'>
>>> ClassA == ClassB
False

or initialize them and compare the types of their objects:

>>> a = ClassA()
>>> b = ClassB()
>>> type(a)
<class '__main__.ClassA'>
>>> type(b)
<class '__main__.ClassB'>
>>> type(a) == type(b)
False

FWIW you can also use is in place of == (for classes).

Enabling comparison for classes

Define or override the comparison operators for the class. http://docs.python.org/reference/expressions.html#notin

Looks like you are on the right track, except you only need to pass the second circle object to your comparison. self refers to the first circle object. So self.r would give you the r of the first circle. Also you need to return True or False from the method.

def __gt__(self, circle2):
return self.r > circle2.r

Note that this is just comparing the r's of the circles.

How to use a method in a class to compare two objects of the class

You mix class attributes and instance attributes. Class attributes are shared between instances:

class Banks:
total = 0
def __init__(self,name,money):
self.name=name
self.money=money
Banks.total += money

b1 = Banks("one",100)
b2 = Banks("two",5000000)

# prints 5000100 - all the money of all banks,
# does not help you comparing avgs ov instances at all
print(b1.total)

Output:

5000100

You need seperated averages per instance and a function that compares one instance (self) agiant another instance:

class School: 
def __init__(self):
# self.n =int(input()) # not needed - use len(one of your lists)
list_ages = [float(x) for x in input("Gimme ages, space seperated: ").split()]
list_hight = [float(x) for x in input("Gimme hights, space seperated: ").split()]
list_weight = [float(x) for x in input("Gimme weights, space seperated: ").split()]

# shortest list downsizes all other lists
self.list_ages, self.list_hight, self.list_weight = zip(
*zip( list_ages,list_hight,list_weight ))

def get_av(self):
self.avg_age = sum(self.list_ages) / len(self.list_ages)
print(self.avg_age)
self.avg_height = sum(self.list_hight) / len(self.list_hight)
print(self.avg_height)
self.avg_weight = sum(self.list_weight) / len(self.list_weight)
print(self.avg_weight)
return self.avg_age, self.avg_height, self.avg_weight

def compare(self,other):
self.get_av()
other.get_av()
print("Our pupils are younger: ", self.avg_age < other.avg_age)
print("Our pupils are smaller: ", self.avg_height < other.avg_height)
print("Our pupils are lighter: ", self.avg_weight < other.avg_weight)

c = School() # 4 5 6 22 44 66 88 99 20.2 20.2 20.2 20.2 20.2 20.2
d = School() # 100 100 100

c.compare(d)

Output (formatted with newlines in between):

Gimme ages, space seperated: 4 5 6
Gimme hights, space seperated: 22 44 66 88 99
Gimme weights, space seperated: 20.2 20.2 20.2 20.2 20.2 20.2

Gimme ages, space seperated: 100
Gimme hights, space seperated: 100
Gimme weights, space seperated: 100

5.0
44.0
20.2

100.0
100.0
100.0

Our pupils are younger: True
Our pupils are smaller: True
Our pupils are lighter: True

More info:

  • What is the difference between class and instance attributes?
  • python class documentation

In Python, how to know whether objects can be compared?

No, there is no such ABC, because an ABC only dictates what attributes are there. ABCs cannot test for the nature of the implementation (or even if those attributes are actually methods).

The presence of comparison methods (__lt__, __gt__, __le__, __ge__ and __eq__) does not dictate that the class is going to be comparable with everything else. Usually you can only compare objects of the same type or class of types; numbers with numbers for example.

As such, most types* implement the comparison methods but return the NotImplemented sentinel object when comparing with other incompatible types. Returning NotImplemented signals to Python to give the right-hand value a say in the matter too. If a.__lt__(b) returns NotImplemented then b.__gt__(a) is tested too.

The base object provides default implementations for the methods, returning NotImplemented:

>>> class Foo:
... pass
...
>>> Foo() < Foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Foo() < Foo()
>>> Foo().__lt__
<method-wrapper '__lt__' of Foo object at 0x10f1cf860>
>>> Foo().__lt__(Foo())
NotImplemented

which is exactly what dict.__lt__ does:

>>> {}.__lt__({})
NotImplemented

Numbers, however, only return NotImplemented when the other type is not comparable:

>>> (1).__lt__(2)
True
>>> (1).__lt__('2')
NotImplemented
>>> 1 < 2
True
>>> 1 < '2'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()

As such, your best choice is to simply catch the TypeError thrown when values are not comparable.


* I am not aware of any types in the Python 3 standard library that do not implement the comparison methods at this time.



Related Topics



Leave a reply



Submit