Python: single underscores vs. double underscores
This Youtube video is a really great 6 minute demonstration of the distinction between _ and __. I’ll embed the linked video at the end of this post. One of the top comments on that video says
For C++ people,
foois public,_baris protected, and__bazis private.
Here’s a simple class, with an initializer implemented, including the three variables foo, _bar, and __baz.
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
self.__baz = 42
t = Test()
The single underscore
The single underscore takes its meaning by convention. That is, it’s not something that’s enforced by the interpreter. The single underscore “suggests” that the variable is private to the class, forcing a user to actually type an underscore e.g. ._bar to access it.
The double underscore
The Python interpreter does some “name mangling” when you define something with a double underscore. It’s explained in the Youtube video above, and it’s done to prevent name collisions when somebody extends the class. The name mangling means that trying to access __baz the usual way (t.__baz) won’t work.
So the single underscore variable can be directly accessed, because no name mangling takes place:
t._bar
23
But the “dunder” (double underscore) variable can’t be accessed directly:
t.__baz
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-16-ead6810a10a9> in <module>()
----> 1 t.__baz
AttributeError: 'Test' object has no attribute '__baz'
What’s going on? Let’s look at the instance’s “attributes” (not sure if that’s what they’re called).
dir(t)
['_Test__baz',
'__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'_bar',
'foo']
You can see that _bar is directly accessible (and of course, foo is too), but __baz has been name-mangled to _Test__baz. So if somebody extended the class, the extended class’s field would be something like _ExtendedTest__baz.