← Back to Blog

Making Objects Behave Like Built-ins

The problem...

You have an Animal object. You print it:

lassie = Animal("Lassie", "dog", 4)
print(lassie)

Output → <__main__.Animal object at 0x10b3c2d50>

A memory address. Useless. You want something readable.

Or you try len(lassie) and get a TypeError. Or you compare two animals with == and Python compares memory addresses instead of actual data.

Your object doesn't speak Python's language yet.

The idea!

Python has a set of special methods — also called dunder methods (double underscore, on both sides) — that define how objects behave with built-in functions and operators. Define them in your class, and your object starts behaving like a native Python type.

__str__ — readable string representation

Called when you use print() or str() on an object.

class Animal:
    def __init__(self, name, species, age):
        self.name    = name
        self.species = species
        self.age     = age
        self.status  = "available"

    def __str__(self):
        return f"{self.name} | {self.species} | age: {self.age} | {self.status}"
lassie = Animal("Lassie", "dog", 4)
print(lassie)

Output → Lassie | dog | age: 4 | available

Clean, readable, useful.

__repr__ — developer representation

Called in the Python shell, in debuggers, and when __str__ isn't defined. Should return a string that could recreate the object.

def __repr__(self):
    return f"Animal('{self.name}', '{self.species}', {self.age})"
lassie = Animal("Lassie", "dog", 4)
repr(lassie)

Output → Animal('Lassie', 'dog', 4)

Rule of thumb: __str__ is for users, __repr__ is for developers.

__len__ — support for len()

class Shelter:
    def __init__(self):
        self.animals = []

    def add(self, animal):
        self.animals.append(animal)

    def __len__(self):
        return len(self.animals)
shelter = Shelter()
shelter.add(Animal("Lassie", "dog", 4))
shelter.add(Animal("Whiskers", "cat", 2))

print(len(shelter))

Output → 2

__eq__ — support for ==

By default, == compares memory addresses. Define __eq__ to compare actual data.

class Animal:
    def __init__(self, name, species, age):
        self.name    = name
        self.species = species
        self.age     = age

    def __eq__(self, other):
        return self.name == other.name and self.species == other.species
a = Animal("Lassie", "dog", 4)
b = Animal("Lassie", "dog", 6)
c = Animal("Rex",    "dog", 4)

print(a == b)   # True  — same name and species
print(a == c)   # False — different name

__contains__ — support for in

class Shelter:
    def __init__(self):
        self.animals = []

    def add(self, animal):
        self.animals.append(animal)

    def __contains__(self, animal):
        return animal in self.animals
shelter = Shelter()
lassie  = Animal("Lassie", "dog", 4)
shelter.add(lassie)

print(lassie in shelter)                    # True
print(Animal("Rex", "dog", 6) in shelter)   # False

The full picture

There are many more dunder methods — __add__ for +, __lt__ for <, __iter__ for loops, __getitem__ for indexing. Each one maps to a built-in operation. Define the ones that make sense for your object.

Heads up!

  • Always define __str__ — it makes debugging dramatically easier
  • __repr__ is used as fallback when __str__ isn't defined
  • __eq__ should compare meaningful data — not memory addresses
  • Dunder methods are called automatically — never call them directly

The mindset shift

Stop thinking: "My object is a custom thing that doesn't work with Python's built-ins."

Start thinking: "My object can speak Python's language — print it, measure it, compare it, search it."

What you should understand now

  • Dunder methods define how objects behave with built-in functions and operators
  • __str__ — called by print(), returns a readable string
  • __repr__ — developer representation, fallback for __str__
  • __len__ — called by len()
  • __eq__ — called by ==, compares meaningful data
  • __contains__ — called by in
[ login to bookmark ] // copied! 29 views · 2 min
// resources
Code Example oop_special_methods.py
← prev Polymorphism: When the Same Call Does Different Things next → Calories Tracker Update — Round 1/2
// 0 comments
// No comments yet. Be the first.
// leave a comment

// Your comment will appear after approval.