Python was designed as an object-oriented language from the start, and the first element of object-oriented thinking is encapsulation. Simply put, encapsulation means that the attributes and methods within a class are divided into public and private; public ones can be accessed externally, while private ones cannot. This is the most crucial concept in encapsulation—access control.
There are three levels of access control: Private, Protected, and Public.
Private: Accessible only within the class itself.
Protected: Accessible within the class itself and its subclasses.
Public: Accessible by any class.
Since Python, unlike Java, does not have access control modifiers (private/public/protected), access control in Python is often overlooked or misunderstood by job applicants.
Public
In Python classes, attributes defined by default are public.
class Foo(object):
bar = 123
def __init__(self, bob):
self.bob = bob
print(Foo.bar) # 123
foo = Foo(456)
print(foo.bob) # 456
In the class Foo
, the bar
attribute is a class attribute, and bob
defined in the __init__
method is an instance attribute. Both bar
and bob
are public attributes and can be accessed externally. The values of bar
in the class and bob
in the instance are printed, showing the corresponding values.
Protected
To define a protected attribute in Python, simply prefix its name with an underscore _
. Let’s modify the bob
and bar
in the Foo
method to _bob
and _bar
, making them protected attributes, as shown in the code below:
class Foo(object):
_bar = 123
def __init__(self, bob):
self._bob = bob
class Son(Foo):
def print_bob(self):
print(self._bob)
@classmethod
def print_bar(cls):
print(cls._bar)
Son.print_bar() # 123
son = Son(456)
son.print_bob() # 456
We define a class Son
that inherits from Foo
. Since protected objects can only be accessed within the class and its subclasses, you cannot directly call print(Son._bar)
or print(son._bob)
to output these attributes’ values. Therefore, we define print_bar
and print_bob
methods to output them in the subclass, and this code correctly outputs the values of _bar
and _bob
.
Next, let’s try to verify in reverse whether these attributes can be accessed externally by modifying the output part of the above code as follows:
print(Son._bar) # 123
son = Son(456)
print(son._bob) # 456
(Surprisingly) we find that no error is reported, and the correct values are output.
In Python, using an underscore to define protected variables is a convention, not a language-level implementation of access control. Therefore, our protected variables can still be accessed externally (this is a feature, not a bug).
Private
To define private attributes in Python, prefix the attribute name with two underscores __
. Modify the above code and run it to find that any print
in the following code will result in an error.
class Foo(object):
__bar = 123
def __init__(self, bob):
self.__bob = bob
class Son(Foo):
def print_bob(self):
print(self.__bob) # Error
@classmethod
def print_bar(cls):
print(cls.__bar) # Error
print(Son.__bar) # Error
son = Son(456)
print(son._bob) # Error
Delving Deeper—Can Private Attributes Really Not Be Accessed?
To understand whether private attributes can truly not be accessed, we need to look at how Python implements private attributes. In CPython, double underscore attributes are transformed into the form _ClassName__PropertyName
. Here’s a demonstration with code:
class Foo(object):
__bar = 123
print(Foo._Foo__bar) # 123
Running this code shows that the value of __bar
is output correctly, but accessing private attributes this way is not recommended because different Python interpreters handle private attributes differently.
Special Case
When using double underscores to define private attributes, there is a special case where if the attribute also ends with two underscores, it will be treated as a magic method by the Python interpreter and not handled as a private attribute.
class Foo(object):
__bar__ = 123
print(Foo.__bar__) # 123
The code above outputs 123, proving that the Python interpreter does not treat __bar__
as a private attribute. When defining private attributes, note that the name can have at most one trailing underscore.
Another Special Case
What if the attribute name is just __
? Let’s try it directly:
class Foo(object):
__ = 123
print(Foo.__) # 123
We find that an attribute named __
is also not considered a private attribute, and attributes with multiple underscores (e.g., _______
) are not private attributes either.
Access Control for Functions
The above mainly introduced access control for attributes. In Python, functions are first-class citizens, meaning they can be used like variables. Therefore, access control for functions follows the same rules as for attributes.