These methods are assumed to return a string value that represents the object. The former is meant to be more strict and specific—ideally, you should be able to copy the outcome, run it as a code, and get an identical instance, like this:
class Animal:
def __init__(self, age, diet):
self.age = age
self.diet = diet
def __repr__(self):
return f"Animal(age={self.age}, diet='{self.diet}')"
This is what the representation of this class will look like:
>>> Animal(1, 'worms')
Animal(age=1, diet='worms')
Indeed, if you copy that text and run it as a code, you'll get an identical copy. It should be noted, though, that far from all libraries follow that rule.
The latter method, __str__, is meant to be more human-readable. If this function is not defined, but __repr__ is, then it will be used instead:
>>> print(Animal(1, 'worms'))
Animal(age=1, diet='worms')
Now, let's take a look at Python operations.