Underscore Python

The Single and Double Underscore in Python [“_” vs “__”]

The single underscore in Python “_” is used to either make a variable different from a Python keyword (e.g. “float_=8”), or to indicate that it should be used in a private context (e.g. “_var=8”).

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

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)
    y = eval(repr(x))

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

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 code bases.

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 (e.g. this_is_a_long_variable = 42). If you start a name of an instance attribute with the underscore (e.g. _var = 8), you indicate to the reader of your code base that this instance attribute is meant to be “private”. In other words, you tell them that they should not access it 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:
# []

# 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__

1. 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:

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

        # 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)
# AttributeError: 'Wizard' object has no attribute '__supersecretphrase'

# 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.

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 to modify 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.

2. When enclosing a method name with dunders (e.g. __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:

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)
# <__main__.Wizard1 object at 0x000001FEFF2ACA90>

harry = Wizard2(101)
# 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 (we will look into inheritance in a later email). In other words, Wizard2 “inherits” all methods and attributes from the parent class Wizard1.

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

When printing a 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 you defined it.

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)
# 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 determine 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 Pyton email series. Join 5,439 readers. It’s fun! (… and completely free)

Leave a Comment

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