Wednesday, July 15, 2026

Understanding Python Dunder Methods: __str__ vs __repr__

Introduction

If you've spent any time writing Python classes, you've probably noticed something odd: print an object and you get a memory address like <__main__.Point object at 0x7f8b1c0a5d90>. It's not exactly useful. This is where two of Python's most important "dunder" (double underscore) methods come in — __str__ and __repr__.

They look similar, get confused constantly, and yet they serve genuinely different purposes. Let's clear that up.

What Are Dunder Methods, Anyway?

"Dunder" is short for "double underscore." Methods like __init__, __len__, __add__, __str__, and __repr__ are special methods Python calls automatically in response to built-in operations. You rarely call them directly — instead, Python's syntax and built-in functions trigger them behind the scenes.

  • obj + other triggers __add__
  • len(obj) triggers __len__
  • print(obj) triggers __str__
  • repr(obj) (and the interactive shell) triggers __repr__

This system is often called "dunder methods" or the "data model," and it's what lets custom objects behave like built-in types.

The Default Behavior (Why You Need These)

Without any customization, printing an object gives you almost nothing useful:


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

p = Point(3, 4)
print(p)
# <__main__.Point object at 0x7f8b1c0a5d90>


That's technically "correct," but useless for debugging or logging. Defining __str__ and __repr__ fixes this.

__repr__: The Developer's View

__repr__ should return a string that is unambiguous and, ideally, could be used to recreate the object. Think of it as the representation you'd want to see in a debugger, a log file, or the Python REPL.


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

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

p = Point(3, 4)
p
# Point(x=3, y=4)

repr(p)
# 'Point(x=3, y=4)'


The guiding principle (straight from Python's own documentation philosophy) is:

eval(repr(obj)) == obj should ideally hold true.

In our example, Point(x=3, y=4) is literally valid Python code that recreates the object — that's a well-behaved __repr__.

__str__: The User's View

__str__ is meant to return a readable, human-friendly string — something you'd show to an end user, not a developer debugging the internals.


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

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

    def __str__(self):
        return f"({self.x}, {self.y})"

p = Point(3, 4)
print(p)      # calls __str__
# (3, 4)

print(repr(p))  # calls __repr__
# Point(x=3, y=4)


Notice the difference in intent: __str__ gives a clean coordinate pair for a user-facing message, while __repr__ gives full detail for debugging.

What Happens If You Only Define One?

This is the part that trips people up. Python has a fallback rule:

If __str__ is not defined, Python falls back to __repr__.


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

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

p = Point(3, 4)
print(p)
# Point(x=3, y=4)   <- uses __repr__ since __str__ isn't defined


The reverse is not true — if you only define __str__, repr(obj) still falls back to the default <Point object at 0x...> unless you explicitly define __repr__.

This is why the common advice is: always define __repr__. Define __str__ only if you need a different, more user-friendly output.

Where Each One Actually Gets Used

ContextMethod called
print(obj)__str__ (falls back to __repr__)
str(obj)__str__ (falls back to __repr__)
repr(obj)__repr__
Interactive REPL (typing obj and hitting Enter)__repr__
Inside a list/dict: print([obj1, obj2])__repr__ (containers always use repr on their elements)
Debuggers, logging, f"{obj!r}"__repr__
f"{obj}" or f"{obj!s}"__str__

That container detail is worth calling out explicitly — it surprises a lot of people:


points = [Point(1, 2), Point(3, 4)]
print(points)
# [Point(x=1, y=2), Point(x=3, y=4)]


Even though Point has a __str__, printing a list of points uses __repr__ for each element, because containers always show the repr of their contents.

A Practical, Real-World Example

Here's a slightly more realistic class — a simple User model — showing both methods pulling their proper weight:


class User:
    def __init__(self, username, email, is_active=True):
        self.username = username
        self.email = email
        self.is_active = is_active

    def __repr__(self):
        return (f"User(username={self.username!r}, "
                f"email={self.email!r}, is_active={self.is_active})")

    def __str__(self):
        status = "active" if self.is_active else "inactive"
        return f"{self.username} ({status})"


user = User("mchen", "mchen@example.com")

print(user)
# mchen (active)          <- friendly, for UI/logs shown to humans

print(repr(user))
# User(username='mchen', email='mchen@example.com', is_active=True)
# <- precise, for debugging

users = [user, User("jsmith", "jsmith@example.com", is_active=False)]
print(users)
# [User(username='mchen', ...), User(username='jsmith', ...)]
# <- repr used automatically in the list


Notice the !r inside the f-string in __repr__ — that forces the repr() of self.username and self.email, wrapping strings in quotes. This is a small but important habit: it keeps your repr unambiguous (you can tell a string field apart from a number or None at a glance).

Quick Rules of Thumb

  1. Always implement __repr__. It's your safety net for debugging, logging, and the REPL.
  2. Implement __str__ only when a different, friendlier output makes sense for end users.
  3. Make __repr__ unambiguous — ideally valid Python that recreates the object, or at least clearly labeled with class name and field values.
  4. Use !r in f-strings when building __repr__ to correctly quote string fields.
  5. Remember containers use __repr__ on their elements, not __str__.

Wrapping Up

__str__ and __repr__ aren't just cosmetic — they're part of how Python objects communicate with the people who use and debug them. __repr__ is for developers: precise, unambiguous, ideally reconstructable. __str__ is for everyone else: clean and readable. Define both thoughtfully, and your objects will be far more pleasant to work with — whether you're staring at a log file at 2 AM or showing output to an actual user.


Written by

Sunday, January 11, 2015

Modelling Transformations in OpenGL

#include<stdio.h>
#include<GL/glut.h>

/* This function is to draw a triangle  */
void draw_triangle()
{
  glBegin(GL_LINES);
  glVertex2f(-0.40, -0.25);
  glVertex2f(-0.60, -0.25);

  glVertex2f(-0.60, -0.25);
  glVertex2f(-0.50, 0.25);

  glVertex2f(-0.40, -0.25);
  glVertex2f(-0.50, 0.25);
  glEnd();
  glFlush();
}

void draw()
{
  glClear(GL_COLOR_BUFFER_BIT);

  /* Draw a triangle using solid lines */
  draw_triangle();                   /* solid lines */

  /* The same triangle is drawn again, but with a dashed  */
  /* line stipple and translated (to the left along the  */
  /* negative x­axis) */
  glEnable(GL_LINE_STIPPLE);         /* dashed lines */
  glLineStipple(1, 0xF0F0); 
  glLoadIdentity();
  glTranslatef(-0.30, 0.0, 0.0);
  draw_triangle();

  /* A triangle is drawn with a long dashed line stipple,  */
  /* with its height (y­axis) halved and its width (x­axis)  */
  /* increased by 50%  */
  glLineStipple(1, 0xF00F);          /*long dashed lines */
  glLoadIdentity();
  glScalef(1.5, 0.5, 1.0);
  draw_triangle();

  /* A rotated triangle(rotated at 90 degree w.r.t the z-axix), 
  made of dotted lines, is drawn */
  glLineStipple(1, 0x8888);          /* dotted lines */
  glLoadIdentity();
  glRotatef (90.0, 0.0, 0.0, 1.0);
  draw_triangle();
  glDisable (GL_LINE_STIPPLE);

  glFlush();
}

void Init()
{
  /* Set clear color to black */
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
  /* Set fill color to white */
  glColor3f(1.0, 1.0, 1.0);
  gluOrtho2D(0.0 , 1.0 , 0.0 , 1.0);
  /* glViewport() command is used to define the rectangle of */
  /* the rendering area where the final image is mapped */
  glViewport(0.0, 0.0, 1.0, 1.0);
  /* glMatrixMode specifies the mode of transformation */
  glMatrixMode(GL_MODELVIEW);
  /* set the current matrix to the identity matrix */
  glLoadIdentity();
}

int main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
  glutInitWindowPosition(0, 0);
  glutInitWindowSize(640, 480);
  glutCreateWindow("Transformation");
  Init();
  glutDisplayFunc(draw);
  glutMainLoop();
  return 0;
}

Output
======

















Written by