Private Scope In Python
TL;DR Python does not have private scope, but the conventions used to “make” something private can confuse new programmers.
A common feature of many programming languages, particularly languages students are likely to encounter in university courses, is the idea of “private” scope in classes. This refers to the fact that a class can have internal state that is only visible to an instance of that class. In C++ this looks like:
class Foo
{
public:
Foo();
~Foo();
void bar();
private:
void buf();
int baz;
};
Understanding this is pretty straightforward. The methods Foo()
,
~Foo()
, and bar()
are public, callable by any other part of the
program. buf()
and baz
are not accesible to parts of the program
that are not the object itself.
Python, however, does not have scope restrictions like this. The same class as in the above example would look like this:
class Foo:
def __init__(self):
self.baz = 0
def bar():
pass
def buf():
pass
No access modifiers, everything is public! However, a common convention in Python is prefixing private methods or attributes with an underscore, which would look like this:
class Foo:
def __init__(self):
self._baz = 0
def bar():
pass
def _buf():
pass
By convention, _foo
means that the foo
attribute is an internal
method or field, and should not be used by programmers. However, some
resources describe this as “making” something private, which is
incorrect! Observe:
In [1]: class Foo:
...: def __init__(self):
...: self._baz = 0
...:
...: def bar():
...: pass
...:
...: def _buf():
...: pass
...:
In [2]: foo = Foo()
In [3]: foo._baz
Out[3]: 0
In [4]:
One other thing I have seen in resources for new developers (looking through them for my class) is that a double underscore makes something “super private”. This is… not true. But it looks true:
In [4]: class Foo:
...: def __init__(self):
...: self.__baz = 0
...:
...: def bar():
...: pass
...:
...: def _buf():
...: pass
...:
In [5]: foo = Foo()
In [6]: foo.__baz
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-6-906e6460fe73> in <module>
----> 1 foo.__baz
AttributeError: 'Foo' object has no attribute '__baz'
In [7]:
What happened? Turning to the vars
method, we see this output:
In [7]: vars(foo)
Out[7]: {'_Foo__baz': 0}
In [8]:
So the attribute is there, just… renamed? Mangled, almost? In fact,
what’s happening is called name
mangling. The
main idea of name mangling is that a subclass of Foo
might implement
an attribute also called _baz
, and then you’re in trouble for
deciding which one should be used. What the double underscore is
actually doing is exactly what it appears to be doing: rewriting the
name of the method so that it won’t conflict with a subclass. You can
still access the supposedly private parts of a class with no problems
at all, if you happen to know the name mangling scheme:
In [8]: foo._Foo__baz
Out[8]: 0
In [9]:
So while Python doesn’t have functionally private scope, it has
conventionally private scope using _
, and subclass-private scope
using __
.