The Underscore in Python [“_” vs “__”]

The single underscore in Python "_" is used to either make a variable different from a Python keyword such as in float_=8, or to indicate that it should be used in a private context such as in _var=8.

>>> _str = 'hello'
>>> _str
'hello'

The double underscore in Python "__" (called dunder) is used to make an instance attribute or method private that cannot be accessed from outside the class) when used as a leading dunder prefix. When used as enclosing dunder such as in "__init__", it indicates that it is a special method in Python called “magic method”.

>>> class Wizard:
	def __init__(self):
		self.mana = 100

		
>>> harry = Wizard()
>>> harry.mana
100

In this article and the bonus video, you will learn the ins and outs of the underscore in Python.

Class definitions can be intimidating. Sometimes, if you read over the simplified code examples from Python tutorials, you may believe that you get the tutorial. But writing and reading code in the real world can become ugly very quickly. For example, look at the following code snippet from an online Python tutorial:

class Length:

    __metric = {"mm" : 0.001, "cm" : 0.01, "m" : 1, "km" : 1000,
                "in" : 0.0254, "ft" : 0.3048, "yd" : 0.9144,
                "mi" : 1609.344 }
    
    def __init__(self, value, unit = "m" ):
        self.value = value
        self.unit = unit
    
    def Converse2Metres(self):
        return self.value * Length.__metric[self.unit]
    
    def __add__(self, other):
        l = self.Converse2Metres() + other.Converse2Metres()
        return Length(l / Length.__metric[self.unit], self.unit )
    
    def __str__(self):
        return str(self.Converse2Metres())
    
    def __repr__(self):
        return "Length(" + str(self.value) + ", '" + self.unit + "')"

if __name__ == "__main__":
    x = Length(4)
    print(x)
    y = eval(repr(x))

    z = Length(4.5, "yd") + Length(1)
    print(repr(z))
    print(z)

If you don’t have already invested time, effort, and money into a proficient Python education, you may ask: What is wrong with this code?

After calming down, you may realize that the main thing you don’t get is the use of the “underscore” in Python. In the code snippet, underscores seem to be all over the place!

The sad thing is that if you don’t get the meaning of the underscore, you will never be able to follow in-depth Python discussions, you won’t understand many Python talks, let alone other people’s codebases.

It’s vital for your career — and for your ability to communicate in Python — that you study these seemingly small pieces of knowledge thoroughly.

What’s the Meaning of the Underscore in Python Object Orientation?

The single underscore has, in fact, no special meaning.

  • You can use the underscore to separate words such as in: this_is_a_long_variable = 42.
  • If you start a name of an instance attribute with the underscore such as in _var = 8, you indicate that this instance attribute is meant to be “private” and it shouldn’t be accessed from outside of the class. However, it’s still possible as you can see in the following code snippet:
class Wizard:


    # underscore = visual separator
    studied_at = "Hogwarts"

    # underscore = "please keep me private"
    _wizards = []


# discouraged but possible:
print(Wizard._wizards)
# []

print(Wizard.studied_at)
# Hogwarts

The double underscore (also called “dunder” by Python pros) on the other hand has a special meaning.

There are two cases:

  1. leading dunders: __var
  2. enclosing dunders: __init__

Leading Dunders

When starting a name with dunders, you ask the Python interpreter to protect its use from outside of the class. The so protected method or attribute cannot be accessed directly from the outside (it’s still possible but you need to actively circumvent the protection).

Here is an example from Harry Potter that shows the private nature of a leading dunder:

class Wizard:


    # underscore = visual separator
    studied_at = "Hogwarts"

    # underscore = "please keep me private"
    _wizards = []

    # enclosing dunder = magic methods
    def __init__(self, mana):

        self.mana = mana
        Wizard._wizards.append(self)

        # trailing underscore = overwrite keyword
        self.key_ = True
        
        # leading dunder = "enforce to keep me private"
        self.__supersecretphrase = "wingardium leviosa"

    def secret_trick(self):
        return self.__supersecretphrase

tom = Wizard(100)
print(tom.__supersecretphrase)
# AttributeError: 'Wizard' object has no attribute '__supersecretphrase'

print(tom.secret_trick())
# wingardium leviosa

You cannot access the instance attribute __supersecretphrase from outside the class! The Python interpreter will throw an error if you try to do it in such a blunt way. But you can do it by calling the non-private method secret_trick() that accesses the private instance attribute __supersecretphrase from within the class definition.

Now, you may ask:

What is the reason for protecting names in this way?

The object-oriented programming paradigm stems from the idea of “encapsulation”. Each object encapsulates data (the instance attributes) and methods to access the data (the instance methods). The most extreme view is to completely forbid modifying the instance attributes from outside. This leads to very clear semantics (what is the effect of your objects) and hides the complexity from the user of your class.

Enclosing Dunders

When enclosing a method name with dunders such as __init__, you indicate that it is a special method. Pro’s call it in fact “magic method” in Python — a term that suits our example very nicely ;).

Probably, you have already used the special __init__ method (the constructor) quite heavily to create new instances from a class description.

But there are also many more special methods. An example is the __str__ method that allows you to create a new textual representation of your object.

Here is an example that shows how the class Wizard2 overwrites the default string representation using the enclosing dunder method __str__:

class Wizard1:
    def __init__(self, mana):
        self.mana = mana


class Wizard2(Wizard1):
    def __str__(self):
        return "Wizard's Mana Level: " + str(self.mana)


tom = Wizard1(99)
print(tom)
# <__main__.Wizard1 object at 0x000001FEFF2ACA90>

harry = Wizard2(101)
print(harry)
# Wizard's Mana Level: 101

The class Wizard1 is the top-level class here. It defines the constructor using the magic method __init__.

The class Wizard1 is the top-level class here. It defines the constructor using the magic method __init__.

The class Wizard2 is a class that inherits from the top-level class (you can learn about inheritance here). In other words, Wizard2 “inherits” all methods and attributes from the parent class Wizard1.

But on top of that, Wizard2 also defines the __str__ method that returns a textual representation of the current instance on which it is called.

When printing the Wizard1 instance (e.g. tom), the default textual representation is really ugly. It only gives you the hex code of the object — not really useful to understand the instance. But when printing the Wizard2 instance (e.g. harry), Python will implicitly call your defined __str__ method and returns the textual representation as defined by you.

There are many other magic methods. For example, you can overwrite the default behavior for addition, subtraction, multiplication, and division:

class Wizard:

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


    def __add__(self, other):
        return Wizard(self.mana + other.mana)


tom = Wizard(99)
harry = Wizard(101)
print((tom+harry).mana)
# 200

In the example, adding together two wizards creates a new wizard that has the additive mana of both wizards.

Where to Go From Here?

Your understanding of the basics determines your success in any field, including Python. Of course, you can study hard to learn every bit about Python — but it may be very inefficient. In my opinion, it’s much better to put your learning system in the hands of a professional teacher who gives you small daily doses of Python knowledge. This way, you can make small daily progress to become a code master over time.

That’s why I have created my 100% FREE Python email series. Join tens of thousands of readers. It’s fun! (… and completely free)


Leave a Comment

Your email address will not be published. Required fields are marked *