Skip to main content

Summary of Underscore Usage in Python

··5 mins·
Python
Makoto Morinaga
Author
Makoto Morinaga
A personal notebook for tech notes, coding, and system experiments.
Table of Contents

In Python, underscores are used before and after functions and methods, and also as variables. It is either a convention or a system constraint, but it is sometimes easy to forget.

Positions where underscores are used
#

Underscores are used in a variety of positions. The following is a list of positions where underscores are conventionally or systematically used.

Underscore position Example
Underscore only _
One underscore at the beginning _foo
One underscore at the end foo_
Two underscore at the beginning __foo
Two underscores each at the beginning and end __foo__
One or more underscores along the way foo_bar_baz, 10_000_000

Each case is examined in order below.

Underscore only
#

A single underscore is conventionally used for values that are not needed, such as temporary variables. For example, when the index is not needed in a for loop:

sample.py
for _ in range(47):
    print('Hello, world')

It is also used to assign unneeded values or return values.

sample.py
temp_list = [0, 1, 2, 3]
a, b, _, _ = temp_list
print(a)  # 0
print(b)  # 1
print(_)  # 3  (In interactive mode, _ holds the last evaluated expression, but in scripts, it behaves as a normal variable.)

# When only the x-axis is needed in a function to get 3D coordinates
x, _, _ = get_coordinate(area)

One underscore at the beginning
#

A single underscore at the beginning is used in the following cases:

  • Variables and methods in a class: Conventional implications
  • Functions: May be subject to systematic constraints

Adding a single underscore to the beginning of variables and methods in a class
#

A single underscore at the beginning of a variable or method in a class indicates that it is intended for internal use.

sample.py
class Test:
    def __init__(self):
        # The _foo variable is not intended to be called directly from outside the class.
        self._foo = 0

    def bar(self):
        self._foo += 1
        return self._foo

    def _baz(self):
        self._foo = 0
        return self._foo

t = Test()
print(t._foo)  # 0
print(t.bar())  # 1
print(t._baz())  # 0

Adding a single underscore to the beginning of a function
#

A function prefixed with a single underscore is excluded from wildcard imports.

test_module.py
def foo():
    return 0
def _bar():
    return 1
sample.py
from test_module import *

foo()  # Imported from test_module
_bar()  # _ is at the beginning and will not be imported

One underscore at the end
#

When a variable name conflicts with a Python reserved keyword, adding a single underscore at the end avoids the conflict.

sample.py
def foo(class_):
    print(class_)

To check Python’s reserved keywords:

sample.py
import keyword
print(keyword.kwlist)

Two underscores at the beginning
#

A variable or method prefixed with two underscores undergoes name mangling in class scope. In this case, name mangling will be applied to the target.

Name mangling
#

Name mangling converts a variable or method prefixed with __ into _ClassName__VariableName.

sample.py
class Test:
    def __init__(self, name):
        self.name = name
        self._name = name
        self.__name = name

t = Test('foo')
print(t.name)  # Accessible
print(t._name)  # Accessible (but discouraged)
# print(t.__name)  # Not accessible
print(t._Test__name)  # Accessible after name mangling

Notes on name mangling
#

Name mangling is useful, but there is one thing have to be careful about. It is the behavior when inheriting.

  • Inheritance behavior of variables with name mangling applied

      A name-mangled variable in a base class is not directly accessible in a derived class, as shown below.
    

    This occurs because the string Alice is assigned to _Parent__name in the base class, but the derived class refers to _Child__name.

    sample.py
    class Base():
        def __init__(self):
            self.__name = 'alice'
    
    # Inherit Base class
    class Child(Base):
        def print_name(self):
            print(self.__name)
    
    c = Child()
    c.print_name()
    
    # AttributeError: 'Child' object has no attribute '_Child__name'
  • Inheritance behavior of methods with name mangling applied

    The following code outputs the title name retrieved by the __get_title method with the print_title method. It overrides __get_title in the Child class, so it expects the method to be called after the override.

    However, the `__get_title` method has name mangling applied, and the `__get_title` referenced by `print(self.__get_title())` in the `Parent` class is actually `_Parent__get_title`. This is not the expected behavior.

    sample.py
    class Parent():
        def print_title(self):
            print(self.__get_title()) # Outputting the return value of _Parent__get_title
    
        def __get_title(self): #  _Parent__get_title is defined
            return "Alice's Adventures in Wonderland"
    
    # Inherit from Parent class
    class Child(Parent):
        def __get_title(self): # _Child__get_title is defined
            return "Through the Looking-Glass, and What Alice Found There"
    
    c = Child()
    # Expected output: "Through the Looking-Glass, and What Alice Found There"
    # Actual output: "Alice's Adventures in Wonderland" (because print_title in Parent calls _Parent__get_title instead of _Child__get_title)
    
    c.print_title()
    # Through the Looking-Glass, and What Alice Found There

Two underscores each at the beginning and end
#

A name with double underscores at both ends is a special method (dunder method) in Python and is not affected by name mangling.

sample.py
foo = 1
dir(foo)

# ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

One or more underscores along the way
#

Underscores within identifiers improve readability in variable names and numerical literals.

sample.py
value = 100_000_000
print(value, type(value))  # 100000000, <class 'int'>

def get_final_params():
    pass