PYTHON PROGRAMMING

The underscore – that is, the _
character—plays significant and sometimes quite specific roles in Python Programming. Every Pythonista should be aware of its versatility and understand how to effectively utilize the underscore in their coding. From improving readability and managing private attributes to enabling specific functionalities in data processing and internationalization, the underscore is more than just a mere character; it’s one of the most significant characters used in Python syntax, making it a fundamental tool in the Python language.
This article analyzes the different roles of the underscore in Python, exploring both common and niche uses that make _
an essential character in the Python language. Whether you’re a beginner or an advanced programmer, understanding the underscore’s uses will significantly enhance your coding skills.
Underscore use cases
Naming
Perhaps the most common and important use of the underscore is in naming. According to PEP 8,
Function names should be lowercase, with words separated by underscores as necessary to improve readability.
Variable names follow the same convention as function names.
So, the underscore is used to separate words in function and variable names. The same convention is used for method names and class instance variables.
This is an idiomatic naming convention for Python. Here are the most important naming conventions used in various programming languages:
- Camel case (
myVariableName
): Lowercase the first word, capitalize the first letter of subsequent words. Used in: JavaScript, Java, C#, Swift. - Pascal case (
MyVariableName
): Capitalize the first letter of each word. Used in: Python (for class names), C#, Pascal, Java, C++. - Snake case (
my_variable_name
): Words are lowercase and separated by underscores. Used in: Python (for variables and function names), Ruby. - Screaming snake case (
MY_VARIABLE_NAME
): All letters are uppercase with words separated by underscores. Used in: Python (for constants), C, C++, Java. - Kebab case (
my-variable-name
): Words are lowercase and separated by hyphens. Used in: URLs and CSS class names. - Hungarian notation (
iCount
,strName
): Variable names use prefixes indicating types or scopes. Used in: Older C and C++ codes.
Some examples of Pythonic variable names that use the underscore are as follows:
write_to_database()
read_data()
df_history
df_actual
The underscore served yet another role in naming in Python. According to PEP 8, if you need to create a name (e.g., of an argument) whose name clashes with a reserved name, the underscore can be added at the end of the name:
If a function argument’s name clashes with a reserved keyword, it is generally better to append a single trailing underscore rather than use an abbreviation or spelling corruption. Thus
class_
is better thanclss
.
Frequent uses are, for instance, class_
and type_
.
The underscore is also used in the names of constants. PEP 8 again:
Constants are usually defined on a module level and written in all capital letters with underscores separating words.
These are three examples of names of constants:
NO_OF_DAYS
SIGNIF_LEVEL
RUN_DEBUGGER
As you see, the underscore play various roles in naming in Python. Some are more important than others – but the point is, several naming conventions used in Python heavily underly on the underscore.
Do remember, however, that Python classes typically do not use the underscore. So, you will unlikely name a class as book_publisher
but BookPublisher
. There are very well-known exceptions, like list
or dict
, but this doesn’t mean you should make such exceptions yourself.
Dunder (double underscore, or magic) methods
This role is related to naming, too, but here we’re talking about internal names in the Python language. You’ll see a lot of underscores in the names of so-called magic methods. These are special methods that begin and end with double underscores (__
). Due to the use of the double underscore, such methods are sometimes called "dunder" methods – dunder as in _d_ouble _under_score.
Dunder methods are used by various Python language features and syntax. The double underscores in their names are essential, as it is them that indicate that these methods are special. This naming convention prevents custom methods from overwriting these built-in (magic) methods.
Here are several examples of dunder methods in Python:
__init__
: responsible for creating instances of the class.__str__
: defines the behavior ofstr()
andprint()
functions used on the object; see this article to learn more.__len__
: returns the length of the container.__getitem__
: allows and defines indexing.__add__
,__mul__
, etc.: allow objects to support arithmetic operations
Note that you aren’t supposed to use dunder methods directly; instead, they are invoked by the Python interpreter during the execution of various operations. For example, when you call len(x)
, Python internally calls x.__len__()
. Although you’re not supposed to use the latter, it will work just fine:
>>> x = [1, 2, 3]
>>> len(x)
3
>>> x.__len__()
3
It’s a good practice to not define new custom dunder methods. It’s fine, however, to either overwrite existing magic methods or to define them in custom classes.
Special attributes
While methods that begin and end with double underscores are called magic or dunder, attributes following this naming convention are typically called special attributes. Automatically created and managed by Python, such attributes provide information about objects. Here are some examples:
__name__
: used by modules, classes, class methods and functions (interestingly, partial functions created using[functools.partial](https://docs.python.org/3/library/functools.html#functools.partial)
don’t have this attribute) to keep the name of an object.__doc__
: keeps the docstring of a module, class, method or function.__file__
: used by modules to store the path to the file from which the module was loaded.
Dummy variables
Another frequent use of the underscore is as dummy variables. This means that the underscore is used as a name to represent a variable that will not be used in the current code.
Something like that is often used in loops, when we don’t use the looping variable. Compare the following situations:
Use the looping variable:
>>> for i in range(1, 4):
... print(f"number {i}")
1
2
3
Do not use the looping variable:
>>> for _ in range(3):
... print("Zuma Cuma")
Zuma Cuma
Zuma Cuma
Zuma Cuma
It’s also a good practice to use the underscore to catch an object returned from a function or method when this object is not to be used, like here:
def save(obj: Any, path: pathlib.Path) -> bool:
# the object is saved,
# with or without success
if not success:
return False
return True
_ = save(obj, pathlib.Path("file.csv")
Here, we assigned the output of save()
to _
because we didn’t need to use this output in the code. If you do need to use it, you’d do the following:
saved = save(obj, pathlib.Path("file.csv")
Often, you will see an alternative – in my opinion, worse – usage, in which the output is ignored altogether:
save(obj, pathlib.Path("file.csv")
I don’t like this approach because such code is a little less clear, as it suggests that the save()
function doesn’t return anything. I ignore a function’s output only when it returns None
no matter what.
Indication of private methods and attributes
Python doesn’t have truly private methods or attributes in OOP. You can read about this here:
Indication and Hide-and-Seek Privacy of Attributes in Python Classes
Despite that, you can indicate the users which of the class methods or attributes you want to remain private, by preceding the name with a single (_
) or double (__
) underscore – read the above article to learn the difference. When you do so, it’s like telling the users they shouldn’t use these methods or attributes outside the class. The users can do this anyway, but it’s on them – they were informed they shouldn’t be doing that.
Consider the following class:
class Me:
def __init__(self, name, smile=":-D"):
self.name = name
self.smile = smile
self._thoughts = []
def say(self, what):
return str(what)
def _think(self, what):
self._thoughts += what
We have a class Me
, which represents, well, you. You can create yourself, with:
.name
, a public attribute → your name is definitely public.smile
, a public attribute → your smile is visible outside, so it’s definite public._thoughts
, a private attribute → your thoughts are definitely private, aren’t they?
As you see, the two public attributes are named without the underscore, and the only private attribute’s name starts with the underscore.
Now let’s have a look at the methods:
.say()
, a public method → when you say something, people can hear you.._think()
, a private method → when you think something, it’s your private thought; if you want to say it out loud, do it using the public.say()
method, but if you want to keep your thought to yourself, you should use the private_think()
method.
You can create a public method to say a private thought out loud:
def say_thought(self, which):
return self._thoughts[which]
The last operation in an interactive session
In Python 3, the underscore is also used to store the result of the last operation in an interpreter session. This can be useful for quickly using the result of the previous operation in a new operation when the calculation wasn’t assigned to a name. For example:
>>> 1 + 2
3
>>> _ * 3
9
>>> y = 10
>>> _
9
>>> 100
>>> _
100
As you see, the underscore keeps only the result of the last non-assigned operation, even if it was just an object, without any calculations (like 100
in the code block above).
Formatting numerical values
As of Python 3.6, underscores can be used as visual separators to improve the readability of large numeric values. This feature is particularly helpful for large integers, but it can also be applied to floating-point numbers. For example:
>>> x = 1_000_000
>>> x
1000000
>>> 1.009_232_112
1.009232112
>>> 1_021_232.198_231_111
1021232.198231111
While commonly used for large integers, underscores can also enhance readability of decimal values, though this practice is less frequent.
functools.singledispatch
use case
In [functools.singledispatch](https://docs.python.org/3/library/functools.html#functools.singledispatch)
, the underscore (_
) is commonly used as a function name to indicate that the dispatched functions are anonymous implementations meant for handling specific types. This stylistic choice suggests that the function’s name is irrelevant; instead, what matters is the type the function handles. This usage helps keep the namespace clean and emphasizes that the logic is directly linked to the singledispatch
mechanism, rather than being intended for direct calls. Here’s an example from PEP 443:
>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
... if verbose:
... print("Let me just say,", end=" ")
... print(arg)
>>> @fun.register(int)
... def _(arg, verbose=False):
... if verbose:
... print("Strength in numbers, eh?", end=" ")
... print(arg)
...
>>> @fun.register(list)
... def _(arg, verbose=False):
... if verbose:
... print("Enumerate this:")
... for i, elem in enumerate(arg):
... print(i, elem)
In this setup, _
serves to implement the behavior for specific types without cluttering the namespace with function names that aren’t used elsewhere.
If you want to use static checkers on the above code, like [MyPy](https://mypy-lang.org/)
, be aware that it can throw an error that you’re defining _
more than once. The simplest solution to this problem is by adding a # type: ignore
comment to the end of the lines where _
is defined. Alternatively, you can name these functions currently called as _
, which would be an atypical approach for functools.singledispatch
.
Internationalization and localization
Internationalization (often abbreviated as i18n) and localization (abbreviated as l10n) make applications adaptable to different languages and regions, without the necessity to engineer changes. Thanks to internationalization, applications can be adapted to various languages and regions without modifying its code. Localization, on the other hand, helps adapt the internationalized software for a specific region or language, by adding locale-specific components and translating text.
In Python, these two processes can be performed using the [gettext](https://docs.python.org/3/library/gettext.html)
module. It allows applications to support multiple languages.
In gettext
, a common practice is to use the underscore (_
) as an alias for the gettext
function, which marks strings for translation:
>>> import gettext
>>> import locale
Set the locale to Polish:
>>> locale.setlocale(locale.LC_ALL, "pl_PL")
Set the path to the .mo translation files
and select the text domain:
>>> gettext.bindtextdomain(
... "myapp",
... "/path/to/my/locale/directory"
... )
>>> gettext.textdomain("myapp")
The underscore is typically used as an alias for gettext.gettext:
>>> _ = gettext.gettext
>>> _("Hello, World!")
Witaj, świecie!
Here, _()
wraps the text to be translated. When run in a locale with translations, gettext.gettext()
– and so, _()
– retrieves the translated string. Using the underscore is simply simpler; compare:
>>> gettext.gettext("Hello, World!")
Witaj, świecie!
>>> _("Hello, World!")
Witaj, świecie!
This is especially helpful when an app uses gettext.gettext
– and so, _
– quite extensively.
Ignoring values in unpacking
In Python, you can use the underscore to ignore unnecessary values when unpacking sequences. This makes the code cleaner and more readable, as you don’t define variables that wouldn’t be used in the code at all.
So, you can assign values that are not needed to the underscore (_
), which acts as a throwaway variable:
>>> a, _, b = (1, 2, 3)
>>> a
1
>>> b
3
Here, _
is used to ignore the middle value (2
).
If you need to ignore multiple values, which can be the case especially for longer sequences, you can use a starred underscore (*_
):
>>> a, *_, b = [1, 2, 3, 4, 5]
>>> a
1
>>> b
5
We assigned the first and the last values of the list to a
and b
, respectively. The other values – 2
, 3
and 4
– are assigned to *_
, which means they are ignored and won’t be used anymore.
Using underscores in this way improves the readability of the code by making it clear that these particular values won’t be used and thus aren’t needed. This keeps the code – and its reader – focused on the relevant data. Moreover, why would we assign these three values to variables if we aren’t going to use them? It’d go against good coding style.
Conclusion
Just one tiny character, a short line, the underscore occurs an incredibly useful character in Python programming. It has a variety of different uses that make it an essential tool for any Python programmer. It’s also a significant element of Python itself, and oftentimes, not knowing what _
means can lead you to troubles – or, at the very least, to misunderstanding of Python code.
We’ve covered the most important uses of the underscore; I might have overlooked some of the less important cases, however, and if I have, please let us know in the comments. Anyway, all of these uses make the underscore an indispensable character in Python programming. It’s perhaps the most useful character in the language, and every Python programmer should be familiar with its various uses.