__str__ and __repr_ in Python 3

Welcome to part 24 of the intermediate Python programming tutorial series. In this tutorial, we're going to cover two new special methods: __str__ and __repr__.

There are many different explanations about what __str__ and __repr__ are each used for. The main confusion, at least from what I can tell, is where and how __repr__ actually differs from __str__.

The __str__ method is useful for a string representation of the object, either when someone codes in str(your_object), or even when someone might do print(your_object). The __str__ method is one that should be the most human-readable possible, yet also descriptive of that exact object.

The __repr__ method is present either when doing something like repr(your_object), or when a programmer might actually just type the object directly into the interpreter. The repr method is really meant to be just for developers, and more for debugging than actual use of the module. For this reason, you might have never even heard of __repr__ until now, but you've probably noticed the difference between simply calling the object and the str() version of that object. Let's see an example with datetime.datetime.now().

import datetime

If you have both a __repr__ and __str__ method, then the __str__ method will be favored when you use print(), so you see a fairly clean date:

2016-12-04 19:11:30.072375

To prove this, we can do:

2016-12-04 19:11:30.074376

To see the __repr__, we can do:

datetime.datetime(2016, 12, 4, 19, 13, 49, 596592)

Also, in the interactive interpreter / shell, you could just type the object:

>>> datetime.datetime.now()
datetime.datetime(2016, 12, 4, 19, 11, 40, 804063)

Just typing the object out will give you the __repr__, or "representation" of the object.

Let's make our own __str__ and __repr__ methods for our blobs. Currently, our blob.py file looks like:

import random

class Blob:

    def __init__(self, color, x_boundary, y_boundary, size_range=(4,8), movement_range=(-1,2)):
        self.size = random.randrange(size_range[0],size_range[1])
        self.color = color
        self.x_boundary = x_boundary
        self.y_boundary = y_boundary
        self.x = random.randrange(0, self.x_boundary)
        self.y = random.randrange(0, self.y_boundary)
        self.movement_range = movement_range

    def move(self):
        self.move_x = random.randrange(self.movement_range[0],self.movement_range[1])
        self.move_y = random.randrange(self.movement_range[0],self.movement_range[1])
        self.x += self.move_x
        self.y += self.move_y

    def check_bounds(self):
        if self.x < 0: self.x = 0
        elif self.x > self.x_boundary: self.x = self.x_boundary
        if self.y < 0: self.y = 0
        elif self.y > self.y_boundary: self.y = self.y_boundary

Let's add the __repr__ first:

    def __repr__(self):
        return 'Blob({},{},({},{}))'.format(self.color,

So, this will give us just a quick and dirty representation of this object. It's not super human-readable, but someone familiar with what to expect will know what this means.

Now, let's do the __str__ method:

    def __str__(self):
        return "Color: {} blobject of size {}. Located at {},{}".format(self.color,

Very similar, and both of these methods contain the same information, but the __str__ is a bit more human friendly.

Now, taking our blobworld.py file, which is:

import pygame
import random
from blob import Blob
import numpy as np
import logging

DEBUG   Detailed information, typically of interest only when diagnosing problems.
INFO    Confirmation that things are working as expected.
WARNING An indication that something unexpected happened, or indicative of some problem in the near future (e.g. ‘disk space low’). The software is still working as expected.
ERROR   Due to a more serious problem, the software has not been able to perform some function.
CRITICAL    A serious error, indicating that the program itself may be unable to continue running.



WIDTH = 800
HEIGHT = 600
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
RED = (255, 0, 0)

game_display = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Blob World")
clock = pygame.time.Clock()

class BlueBlob(Blob):
    def __init__(self, x_boundary, y_boundary):
        Blob.__init__(self, (0, 0, 255), x_boundary, y_boundary)

    def __add__(self, other_blob):
        logging.info('Blob add op {} + {}'.format(str(self.color), str(other_blob.color)))
        if other_blob.color == (255, 0, 0):
            self.size -= other_blob.size
            other_blob.size -= self.size
        elif other_blob.color == (0, 255, 0):
            self.size += other_blob.size
            other_blob.size = 0
        elif other_blob.color == (0, 0 , 255):
            raise Exception('Tried to combine one or multiple blobs of unsupported colors.')

class RedBlob(Blob):
    def __init__(self, x_boundary, y_boundary):
        Blob.__init__(self, (255, 0, 0), x_boundary, y_boundary)

class GreenBlob(Blob):
    def __init__(self, x_boundary, y_boundary):
        Blob.__init__(self, (0, 255, 0), x_boundary, y_boundary)

def is_touching(b1,b2):
    return np.linalg.norm(np.array([b1.x, b1.y])-np.array([b2.x, b2.y])) < (b1.size + b2.size)

def handle_collisions(blob_list):
    blues, reds, greens = blob_list
    for blue_id, blue_blob, in blues.copy().items():
        for other_blobs in blues, reds, greens:
            for other_blob_id, other_blob in other_blobs.copy().items():
                logging.debug('Checking if blobs are touching {} + {}'.format(str(blue_blob.color), str(other_blob.color)))
                if blue_blob == other_blob:
                    if is_touching(blue_blob, other_blob):
                        blue_blob + other_blob
                        if other_blob.size <= 0:
                            del other_blobs[other_blob_id]
                        if blue_blob.size <= 0:
                            del blues[blue_id]
    return blues, reds, greens
def draw_environment(blob_list):
    blues, reds, greens = handle_collisions(blob_list)
    for blob_dict in blob_list:
        for blob_id in blob_dict:
            blob = blob_dict[blob_id]
            pygame.draw.circle(game_display, blob.color, [blob.x, blob.y], blob.size)

    return blues, reds, greens
def main():
    blue_blobs = dict(enumerate([BlueBlob(WIDTH,HEIGHT) for i in range(STARTING_BLUE_BLOBS)]))
    red_blobs = dict(enumerate([RedBlob(WIDTH,HEIGHT) for i in range(STARTING_RED_BLOBS)]))
    green_blobs = dict(enumerate([GreenBlob(WIDTH,HEIGHT) for i in range(STARTING_GREEN_BLOBS)]))

    while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:

            blue_blobs,red_blobs,green_blobs = draw_environment([blue_blobs,red_blobs,green_blobs])
        except Exception as e:

if __name__ == '__main__':

We can just change what happens in the if __name__ == '__main__': part to:

if __name__ == '__main__':
    blue_blobs = dict(enumerate([BlueBlob(WIDTH,HEIGHT) for i in range(STARTING_BLUE_BLOBS)]))
    red_blobs = dict(enumerate([RedBlob(WIDTH,HEIGHT) for i in range(STARTING_RED_BLOBS)]))
    green_blobs = dict(enumerate([GreenBlob(WIDTH,HEIGHT) for i in range(STARTING_GREEN_BLOBS)]))

Run that, then we can play in the shell. Just referencing the object:

>>> blue_blobs[0]
Blob((0, 0, 255),7,(332,528))

The above gives us the __repr__ method. We can next do:

>>> str(blue_blobs[0])
'Color: (0, 0, 255) blobject of size 7. Located at 332,528'

The above gives us the __str__ method, which we also get if we use print():

>>> print(green_blobs[0])
Color: (0, 255, 0) blobject of size 6. Located at 370,223

If we were to remove the __str__ method, then Python will just by default give us the __repr__ method, like:

>>> print(red_blobs[1])
Blob((255, 0, 0),6,(742,265))

If we remove the __repr__, any reference to the object will go back to the default, like:

>>> red_blobs[1]
<__main__.RedBlob object at 0x0000021E56750198>

For the typical Python programmer, this information is worthless, which is why it is helpful if you as the developer of a program actually add in the __str__ and __repr__ methods, assuming of course that you make their outputs actually useful.

The next tutorial:

  • Intermediate Python Programming introduction
  • String Concatenation and Formatting Intermediate Python Tutorial part 2
  • Argparse for CLI Intermediate Python Tutorial part 3
  • List comprehension and generator expressions Intermediate Python Tutorial part 4
  • More on list comprehension and generators Intermediate Python Tutorial part 5
  • Timeit Module Intermediate Python Tutorial part 6
  • Enumerate Intermediate Python Tutorial part 7
  • Python's Zip function
  • More on Generators with Python
  • Multiprocessing with Python intro
  • Getting Values from Multiprocessing Processes
  • Multiprocessing Spider Example
  • Introduction to Object Oriented Programming
  • Creating Environment for our Object with PyGame
  • Many Blobs - Object Oriented Programming
  • Blob Class and Modularity - Object Oriented Programming
  • Inheritance - Object Oriented Programming
  • Decorators in Python Tutorial
  • Operator Overloading Python Tutorial
  • Detecting Collisions in our Game Python Tutorial
  • Special Methods, OOP, and Iteration Python Tutorial
  • Logging Python Tutorial
  • Headless Error Handling Python Tutorial
  • __str__ and __repr_ in Python 3
  • Args and Kwargs
  • Asyncio Basics - Asynchronous programming with coroutines