Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Checking Equivalence Between Objects

00:00 In this lesson, you’re going to find out how to check equivalence between objects. Objects behave slightly differently from normal values in that you can’t compare them the same way you would, say, a number.

00:10 There is a built-in function called isinstance(), which is the recommended way to check between objects to check if they’re the same type, but you can also make use of the built-in type() function as well.

00:24 So say you had two points, origin and target, and say that they were actually equivalent in the sense that you and I know from looking at these points that they represent the same point in space, the 0, 0 origin point.

00:38 So in many ways, they can be considered the same or equal. However, if you try and just do a direct compare (==), you’ll see that they’re False.

00:47 And this is true also of is. This is because objects or instances behave very differently. They actually live at different memory addresses, as you’ve seen at the start of the course. They are separate objects.

01:00 Even though they have the same instance attributes doesn’t mean they are the same or can be compared straight away. There are some things that you can check right off the bat using isinstance().

01:12 If you call isinstance(), which is a inbuilt function—you don’t need to import it or anything—the first argument is the object that you want to test, and you want to check if it’s an instance of the second argument, a Point.

01:26 So in this case, you are checking whether origin, is that an instance of Point? And that is True. Likewise, target is also an instance of Point.

01:38 You can also check that both are the same type by checking type(origin) == type(target). These will produce the same output, so you can check that they are the same type.

01:53 To copy the example that you’ve just seen, you’ve got two points loaded up, and you can’t just compare them directly.

02:00 That will give you a False value because these are different objects. They’ve been both instantiated individually, so they are different. So you can’t just compare them directly.

02:10 There are ways to override how objects behave when in between the == (equals to) operator and also the > (greater than) and < (less than).

02:18 You can define all of these behaviors individually, but that’s going to come in a later lesson with something called special methods, or dunder methods. You do have a couple tools right off the bat, which is isinstance(),

02:33 and that will check if, say, like target, I want to know if target is a member of the Point class. That’s True.

02:41 And you can also check what the type of target is, and this will give you this notation here that says it is a member of the Point class. But as you can see, this type of output isn’t maybe as clear as just checking isinstance(), where you can put the name of the class, and it’s much easier to work with. If you wanted to check whether origin and target had the same values and so that they represented the same point, without dunder methods, what you’d have to do for now is to go origin.x == target.x and origin.y == target.y, and that will give you True because they both have 0, 0 as their point.

03:28 So this more verbose way is the way you’d have to do it for now. And that’s just to say you can’t compare classes directly without doing something special first.

Avatar image for DimaG

DimaG on Jan. 26, 2023

I have a question. If I check class instances for equality (==) it returns True when class is declared using dataclass class syntax and False if a class is declared with regular class syntax. Why is that? I had read the documentation on dataclasses, but it does not say anything about storing class instances any differently than class that is using regular class syntax. The difference in output applies only to equality comparison, every other comparison is the same between two classes. I use Python 3.11 if it matters.

@dataclass
class Point:
    dimensions = 2
    x: int
    y: int


origin = Point(0, 0)
target = Point(0, 0)

print("Comparison using dataclass class syntax:\n")
print(f"{origin == target = }") # True
print(f"{origin is target = }") # False
print(f"{isinstance(origin, Point) = }") # True
print(f"{isinstance(target, Point) = }") # True
print(f"{type(origin) == type(target) = }") # True

print("=" * 54)

class Point2:
    dimensions = 2

    def __init__(self, x, y):
        self.x = x
        self.y = y


origin2 = Point2(0, 0)
target2 = Point2(0, 0)

print("Comparison using original class syntax:\n")
print(f"{origin2 == target2 = }") # False
print(f"{origin2 is target2 = }") # False
print(f"{isinstance(origin2, Point2) = }") # True
print(f"{isinstance(target2, Point2) = }") # True
print(f"{type(origin2) == type(target2) = }") # True
Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on Jan. 27, 2023

@DimaG With a regular class, you have ultimate control over the comparison logic between its instances. By default, Python compares objects of your class by their identity, which is their memory address in CPython. Take a look at this example:

>>> class RegularClass:
...     pass

>>> instance1 = RegularClass()
>>> instance2 = RegularClass()

>>> instance1 == instance2
False

The two objects compare unequal because they are two separate objects, even if they happen to represent the same value conceptually. To change that, for example, by tying the result of the equality test to values stored in your objects, you can override the equality test operator (==) by implementing the special method .__eq__() in your class:

>>> class RegularClass:
...     def __init__(self, value):
...         self.value = value
...     
...     def __eq__(self, other):
...         if other is self:
...             return True
...         if type(self) is not type(other):
...             return False
...         return self.value == other.value

>>> instance1 = RegularClass(42)
>>> instance2 = RegularClass(42)
>>> instance3 = RegularClass(555)

>>> instance1 == instance2
True

>>> instance1 == instance3
False

Now, the objects of your class behave differently. The two instances that represent the same value, 42, compare equal. However, the third instance is considered unequal to the other two because, although it has the same type, it contains a different value.

Side note: If you intend to use objects of your class as dictionary keys or set members, then you should also provide the corresponding implementation of the special method .__hash__() to follow the hash-equal contract.

When you decorate your class with the @dataclass decorator, Python generates a number of special methods, including the .__eq__() and .__hash__() for you. Because data classes represent data, their comparison is assumed to work like in the second example above, where the attribute values of your data class determine equality:

>>> from dataclasses import dataclass

>>> @dataclass
... class DataClass:
...     value: int
... 
>>> DataClass(42) == DataClass(42)
True

>>> DataClass(42) == DataClass(555)
False

There’s nothing special about data classes. They’re just syntactic sugar that does some work for you under the surface.

Become a Member to join the conversation.