The Many Roles of the Underscore in Python

The underscore (_) character plays a surprisingly large number of roles in Python. From naming conventions to special interpreter shortcuts, understanding how and when to use underscores will make your code more expressive and Pythonic.

1. Ignoring Values You Don't Need

When unpacking, use _ as a throwaway variable for values you don't care about:

first, _, last = ("Alice", "B.", "Smith")
# We only need first and last

# Or ignore multiple values with *_
first, *_, last = [1, 2, 3, 4, 5]
# first=1, last=5

2. The REPL's Last Result

In the interactive Python shell, _ holds the result of the last evaluated expression:

>>> 42 * 3
126
>>> _ + 4
130

This is handy for quick calculations without reassigning to a variable.

3. Thousand Separators in Numbers

Python 3.6+ lets you use underscores as visual separators in numeric literals for readability:

population = 8_100_000_000
price_in_cents = 1_299_99
pi_approx = 3.141_592_653_589

Python ignores these underscores completely — they're purely for human readability.

4. Single Leading Underscore: "Internal Use Only"

A single leading underscore (_name) is a convention signaling that an attribute or function is meant for internal use and shouldn't be part of a public API:

class DataProcessor:
    def process(self, data):
        return self._clean(data)

    def _clean(self, data):  # "private" helper
        return data.strip()

Note: This is a convention, not enforcement. Python won't stop you from calling _clean() externally.

5. Single Trailing Underscore: Avoiding Keyword Conflicts

When a variable name clashes with a Python keyword or built-in, add a trailing underscore:

class_ = "Mathematics"   # 'class' is a keyword
type_ = "integer"        # 'type' is a built-in
list_ = [1, 2, 3]        # 'list' is a built-in

6. Double Leading Underscore: Name Mangling

Double leading underscores (__name) trigger Python's name mangling mechanism, making attributes harder to accidentally override in subclasses:

class Base:
    def __init__(self):
        self.__secret = 42  # becomes _Base__secret

class Child(Base):
    def __init__(self):
        super().__init__()
        self.__secret = 99  # becomes _Child__secret (different!)

7. Dunder Methods: Double Leading and Trailing Underscores

Methods like __init__, __str__, __len__, and __repr__ are Python's "magic" or "dunder" methods. They define how objects behave with built-in operations:

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

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

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

Quick Reference Table

PatternMeaning
_Throwaway variable / last REPL result
1_000_000Readable numeric literal
_nameInternal / "private" by convention
name_Avoid keyword/built-in conflict
__nameName mangling in classes
__name__Dunder / magic method

These conventions are part of what makes Python code feel idiomatic. Once you internalize them, you'll both write and read Python more fluently.