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 “
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 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: 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:
- leading dunders: __var
- enclosing dunders: __init__
1. When starting a name with
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.
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
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) 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 (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) 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 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)
While working as a researcher in distributed systems, Dr. Christian Mayer found his love for teaching computer science students.
To help students reach higher levels of Python success, he founded the programming education website Finxter.com. He’s author of the popular programming book Python One-Liners (NoStarch 2020), coauthor of the Coffee Break Python series of self-published books, computer science enthusiast, freelancer, and owner of one of the top 10 largest Python blogs worldwide.
His passions are writing, reading, and coding. But his greatest passion is to serve aspiring coders through Finxter and help them to boost their skills. You can join his free email academy here.