The Python Underscore

Dunder magic

Everything in Python is an object, underscore included.

The dir() function returns information about any object in Python. Calling dir on dir is valid.

Call the dir() function on underscore to see all its attributes and functions. Try it.

Members of Underscore

_ = dir('_')
print(_)

Output

['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

The underscore holds the value of the last evaluated expression. Since the last statement returned a list, we know its size, like so.

size = len(_)
print(size)
print(type(_))

The size value returned is 80, that is the number of attributes and functions associated with the underscore. The type is list in this case, but it can take on any type depending on what is assigned to it.

Underscore Anonymous

Consider the following code snippet. What do you see?

for _ in range(1, 5):
    print(_)

print(type(_))

Output

1
2
3
4

<class 'int'>

No identifier has been assigned in the loop, because we don't use that variable elsewhere in the code block. Underscore here is a stand-in replacement for an anonymous variable, also called a throw away variable.

Notice the type: it is an int this time round, as it should be.

Underscore for Dunder methods

Dunder is short for double underscore. It is PySpeak if you like.

Methods in Python classes contain leading and trailing double underscores to signify that they are special methods.

Let's write a class that has two dunders and one regular method.

class X:

    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f'{self.value}'

    def double_it(self):
        return 2 * self.value

The init() method has a double underscore before and after the method name to show that it is a class constructor.

The repr() method returns a string representation of the class attribute value.

You have probably guessed that double_it() is a regular method, even though it has an underscore in its name. This way of using underscore in variable and function names is called snake case.

x = X(5) # constructor
print(x)
r = x.double_it()
print(r)

We don't have to call the dunder methods in our program. The print function calls the repr() method and Python calls the init() method at object construction.

Underscore in class declaration

Python has a notion of a private variable in its OOP repertoire. The variables with a leading underscore are not to be accessed outside the class.

Let us add a private member to our X class above.

class X:

    def __init__(self, value):
        self.value = value
        self._var = 101

    def get_var(self):
        return self._var

    ...

The self._var is private to the class. If you access it outside the class, you will get a warning.

Warning Acess to a protected member of a client class.

You may however access it by calling the method get_var().

You can get the complete class data by simply calling the dunder method dict on the instance of the class.

print(x.__dict__)

Output {'value': 5, '_var': 101}

In Python, private members of a class are protected by the programmer, and not enforced by the language, like in Java.

Underscore as a Separator

Underscore can be used as a thousands separator in a number. The Python interpreter ignores the underscore while performing operations.

amount = 1_234_567.89
value = amount + .11
print(f'{value:,}')

Output

1,234,568.0

Note If you replace the comma with an underscore, you get back the original format. For internationalisation you can use functions from the locale module.

You can read more about underscores in Python from Python docs available here.