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:
for _ in range(47):
print('Hello, world')
It is also used to assign unneeded values or return values.
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.
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.
def foo():
return 0
def _bar():
return 1
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.
def foo(class_):
print(class_)
To check Python’s reserved keywords:
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
.
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 theprint_title
method. It overrides__get_title
in theChild
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.
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.
value = 100_000_000
print(value, type(value)) # 100000000, <class 'int'>
def get_final_params():
pass