Underscore phenomena in python
By: Muhammad Hanif
Tags: Oop Concepts, Object Oriented, Python, Python Programming

My first encounter with underscores was when I started to learn C++ in high school. Among my first lessons was the naming conventions of variables and interestingly underscore was the only non-alphanumeric character that could be used in variable names. Later in college, as I moved to Python I found it very intriguing that how an underscore was used in so many different ways to accomplish different goals.

This article will have the following topics:

Single Leading underscore

Single Trailing Underscore

Single stand-alone Underscore

Double Leading Underscore

Double Leading and Trailing Underscore

_single_leading_underscore:

Many online resources define leading underscores as protected access modifier in python. But it is not the case. Python does not have access modifiers similar to other Object Oriented languages like Java or C++.

let’s see an example:

#file1.py

class Example: def __init__(self):

self.foo = 1

self._bar = 2 >>> from file1 import Example

>>> example = Example()

>>> print(example.foo, example._bar)

1 2

Interestingly having a leading underscore in a method name does change its behavior in one way.

# file1.py

def first_method():

print("output --- First Method") def _second_method():

print("output --- second method")

Let’s import these functions into another file.

from file1 import * first_method()

_second_method()

Output:

output --- First Method

Traceback (most recent call last):

File "file2.py", line 4, in <module>

_second_method()

NameError: name '_second_method' is not defined

The reason for the above output is that if we use a wildcard import to import all the names from the module, Python will not import names with a leading underscore.

PS: it is never a good idea to use wildcard imports.

single_trailing_underscore_:

Any special usage of the trailing underscore is totally by convention rather than rules. Sometimes the most suitable variable name is already taken by a

keyword in the Python language. In this case, you can append a single underscore to break the naming conflict:



>>> def make_object(name, class):

SyntaxError: “invalid syntax”

>>> def make_object(name, class_):

pass

_:

A single underscore is often used as a placeholder variable. But this is totally by convention. For example, in the following loop, we don’t need access to the running index and we can use “ _ ” to indicate that it is just a temporary value:

for _ in range(32):

print('Hello, World.')

Or in this following piece of code where we only need to print the first and third attribute of every tuple in the list so we can ignore the second.

list_ = [('red', 'suzuki', '2016'), ('Blue', 'suzuki', '2017'), ('Green', 'Sedan', '2017') ] for color, _, make in list_:

print(color, make)

Output:

red 2016

Blue 2017

Green 2017

__double_leading_underscore:

In the above three naming patterns that we have covered so far everything is all about agreed-upon conventions only. But now things are about to get changed.

“A double underscore prefix causes the Python interpreter to rewrite

the attribute name in order to avoid naming conflicts in subclasses.”

let’s do some experimentation:

class Test: def __init__(self):

self.foo = 10

self._bar = 20

self.__baz = 30 test = Test()

print(test.foo, test._bar, test.__baz)

Output:

Traceback (most recent call last):

File "file1.py", line 10, in <module>

print(test.foo, test._bar, test.__baz)

AttributeError: 'Test' object has no attribute '__baz'

Python Interpreter is not recognizing any attribute __baz in Test class. Let’s try to have a deeper look.

>>> t = Test()

>>> dir(t)

['_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']

dir() gives us the list of attributes associated with an object.

Let’s have a look at the list. The self.foo and self._bar variable appears unmodified as in the attribute list. However, with self.__baz things look a little strange. There is not attribute __baz. But what exactly happened to it?

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.

__double_leading_trailing_underscore__:

Surprisingly enough, name mangling is not applied if a name starts

and ends with double underscores.

class PrefixPostfixTest:

def __init__(self):

self.__bam__ = 42

>>> PrefixPostfixTest().__bam__

42

However, names that have both leading and trailing double under-

scores are reserved for a special use in the language. This rule covers

things like __init__ for object constructors, or __call__ to make ob-

jects callable. So to keep yourself safe from any trouble it’s best to stay away from names like this to avoid naming collision with any future python release.