Underscores in Python
TL;DR
Pattern | Example | Meaning |
---|---|---|
Single Leading Underscore | _var | |
Single Trailing Underscore | var_ | Used by convention to avoid naming conflicts with Python keywords. |
Double Leading Underscore | __var | |
Double Leading and Trailing Underscore | __var__ | |
Single Underscore | _ |
Single Leading Underscore: _var
When it comes to variable and method names, the single underscore prefix has a meaning by convention only.
- It is meant as a hint to another programmer that a variable or method starting with a single underscore is intended for internal use.
- It does NOT affect the behavior of your programs and this isnât enforced by Python.
Example
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
t = Test()
print(t.foo)
print(t._bar) # we can still access _bar
11
23
However, leading underscores do impact how names get imported from modules. If you use a wildcard import (should be avoided!) to import all names from the module, Python will NOT import names with a leading underscore (unless the module defines an __all__
list that overrides this behavior).
Wildcard imports should be avoided as they make it unclear which names are present in the namespace. Itâs better to stick to regular imports for the sake of clarity.
Example
my_module.py
def external_func():
print("external")
def _internal_func():
print("internal")
from my_module import *
_internal_func()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-6-3757d8ee357f> in <module>()
----> 1 _internal_func()
NameError: name '_internal_func' is not defined
Unlike wildcard imports, regular imports are not affected by the leading single underscore naming convention:
import my_module
my_module._internal_func()
internal
Single Trailing Underscore: var_
Sometimes the most fitting name for a variable is already taken by a keyword. Therefore names like class
or def
cannot be used as variable names in Python. In this case you can append a single underscore to break the naming conflict.
Example
def make_obj(name, class_):
pass
Double Leading Underscore: __var
âdunderâ in python:
Double underscores are often referred to as âdundersâ in the Python community. The reason is that double underscores appear quite often in Python code and to avoid fatiguing their jaw muscles Pythonistas often shorten âdouble underscoreâ to âdunder.â
For example, youâd pronounce
__baz
as âdunder bazâ. Likewise__init__
would be pronounced as âdunder initâ.
A double underscore prefix causes the Python interpreter to rewrite the attribute name in order to avoid naming conflicts in subclasses. This is also called name mangling â the interpreter changes the name of the variable in a way that makes it harder to create collisions when the class is extended later.
Example
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
self.__baz = 42
>>> t = Test()
>>> dir(t) # show the list with the object's attributes
['_Test__baz', '__class__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bar', 'foo']
This gives us a list with the objectâs attributes. Thereâre some interesting findings:
- The
self.foo
variable appears unmodified as foo in the attribute list. self._bar
behaves the same wayâit shows up on the class as_bar
.- However with
self.__baz
, things look a little different. When you search for__baz
in that list youâll see that there is no variable with that name.
If you look closely youâll see thereâs an attribute called _Test__baz
on this object. This is the name mangling that the Python interpreter applies. It does this to protect the variable from getting overridden in subclasses.
Letâs create another class that extends the Test
class and attempts to override its existing attributes added in the constructor:
class ExtendedTest(Test):
def __init__(self):
super().__init__()
self.foo = 'overridden'
self._bar = 'overridden'
self.__baz = 'overridden'
>>> t2 = ExtendedTest()
>>> t2.foo
'overridden'
>>> t2._bar
'overridden'
>>> t2.__baz
AttributeError:
"'ExtendedTest' object has no attribute '__baz'"
It turns out this object doesnât even have a __baz
attribute. Letâs check the list of objectâs attributes:
>>> dir(t2)
['_ExtendedTest__baz', '_Test__baz', '__class__',
'__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bar', 'foo', 'get_vars']
__baz
got turned into _ExtendedTest__baz
to prevent accidental modification.
>>> t2._ExtendedTest__baz
'overridden'
The original _Test__baz
is also still around:
>>> t2._Test__baz
42
Double underscore name mangling is fully transparent to the programmer. Name mangling affects ALL names that start with two underscore characters (âdundersâ) in a class context. E.g.
class ManglingTest:
def __init__(self):
self.__mangled = "hello"
def get_mangled(self):
return self.__mangled
def __method(self):
return 42
def call_it(self):
return self.__method()
>>> ManglingTest().get_mangled()
'hello'
>>> ManglingTest().__mangled
AttributeError:
"'ManglingTest' object has no attribute '__mangled'"
>>> MangledMethod().call_it()
42
>>> MangledMethod().__method()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-29-3a27f66f344d> in <module>()
----> 1 MangledMethod().__method()
AttributeError: 'MangledMethod' object has no attribute '__method'
Double Leading and Trailing Underscore: __var__
Name mangling is NOT applied if a name starts and ends with double underscores.
Names that have both leading and trailing double underscores are reserved for special use in the language.
- This rule covers things like
__init__
for object constructors, or__call__
to make an object callable. These dunder methods are often referred to as magic methods. - Itâs best to stay away from using names that start and end with double underscores (âdundersâ) in your own programs to avoid collisions with future changes to the Python language.
- This rule covers things like
Single Underscore _
Ignoring Values
If you donât want to use specific values while unpacking, just assign that value to underscore(_
).
# Ignoring a value
a, _, b = (1, 2, 3)
print(f"a={a}, b={b}")
a=1, b=3
You can also use *_
to ignore multiple values. (Only available in Python 3.x)
a, *_, b = (1, 2, 3, 4, 5, 6, 7)
print(f"a={a}, b={b}")
print(*_)
a=1, b=7
2 3 4 5 6
Use in Looping
for _ in range(5):
print(_)
0
1
2
3
4
Use in Interprerter
_
is a special variable in most Python REPLs that represents the result of the last expression evaluated by the interpreter.
>>> 20 + 3
23
>>> _
23