Encapsulation in Python 3
The meaning of the term “encapsulation” is vague and differs from source to source. It is generally accepted that encapsulation is one of the fundamental principles of OOP, although some scientific articles completely omit encapsulation from the list. For example, John Mitchell in the book “Concepts in Programming Languages”, when listing the main concepts in OOP, mentions only abstraction - a term that is considered to be close to encapsulation in meaning, but nevertheless more extensive and high-level. On the other hand, Robert Martin, in his book Pure Architecture, explicitly states that encapsulation, inheritance, and polymorphism are considered the foundation of OOP.
The variety of definitions given to the term “encapsulation” is difficult to bring to a common denominator. In general, two approaches to the meaning of this term can be distinguished. Encapsulation can be considered as:
- communication of data with methods that control this data;
- A set of tools to control access to data or methods that manage this data.
Encapsulation as a connection
This kind of interpretation of the term “encapsulation” is very easy to explain. In this case, any class in which there is at least one variable and one method that controls it clearly demonstrates this principle.
#!/usr/bin/python3 class Phone: number = "111-11-11" def print_number(self): print( "Phone number is: ", self.number ) my_phone = Phone() my_phone.print_number() input( "Press Enter to exit" )
The Phone class combines the data in the variable number with the print_number () method
You can create a class that consists only of methods (and does not contain variables), which may be convenient in some programming languages. It is also possible to create a class containing only data, without methods, which, in many cases, should be avoided. Both practices should be applied when necessary, and their relationship to “unifying” encapsulation is debatable.
Encapsulation as Access Control
Explaining the concept of restricting access to data or methods requires much more detail. First of all, in this context, the term “access” should be understood as the ability to see and / or modify the internal contents of a class. There are several access levels provided by most OOP languages. Summarizing, we can say that the data of the object can be:
- public (
public) - data is accessible to everyone;
- private (
private) - data is available only to the object / class to which it belongs.
Most languages have additional degrees of access that are between these boundaries. For example, in C ++ and Python3 there are three levels of access: public, protected and private; C # adds the keyword "internal" (
internal) to the list.
It is worth noting that in most programming languages, the level of access to any data is set by default. For example, in C ++, by default, the level of access to data in a class is set to private — only members and friends of the class can access its data. The standard level of access to structure (
struct) in C ++ is different - it is public, and data in such a structure can be accessible to anyone. The access level for class variables and methods in Python 3 is completely syntax dependent.
Python 3 provides 3 levels of data access:
- public (
public, there is no special syntax,
- protected (
protected, one underscore at the beginning of the name,
- private (
private, two underscores at the beginning of the name,
For brevity and simplicity, only two basic levels (private and public) are highlighted in the example.
#!/usr/bin/python3 class Phone: username = "Kate" # public variable __how_many_times_turned_on = 0 # private variable def call(self): # public method print( "Ring-ring!" ) def __turn_on(self): # private method self.__how_many_times_turned_on += 1 print( "Times was turned on: ", self.__how_many_times_turned_on ) my_phone = Phone() my_phone.call() print( "The username is ", my_phone.username ) # my_phone.turn_on() # my_phone.__turn_on() # print( “Turned on: “, my_phone.__how_many_times_turned_on) # print( “Turned on: “, my_phone.how_many_times_turned_on) # will produce an error input( "Press Enter to exit" )
Access to public variables and methods can be obtained from the main program. Attempting to retrieve private data or run a private method will result in an error.
The language itself provides the programmer with a syntax tool that can circumvent encapsulation. Reading and modifying private variables and calling private functions is still possible.
#!/usr/bin/python3 class Phone: username = "Kate" # public variable __serial_number = "11.22.33" # private variable __how_many_times_turned_on = 0 # private variable def call(self): # public method print( "Ring-ring!" ) def __turn_on(self): # private method self.__how_many_times_turned_on += 1 print( "Times was turned on: ", self.__how_many_times_turned_on ) my_phone = Phone() my_phone._Phone__turn_on() my_phone._Phone__serial_number = "44.55.66" print( "New serial number is ", my_phone._Phone__serial_number ) input( "Press Enter to exit" )
A few words about Magic
There are methods, so-called “magic methods” or “special methods”, which allow classes to determine their behavior with respect to standard language operators. The following expressions can serve as an example of such language operators:
x > y
x[ i ]
Python 3 supports many of these methods, a full list can be found on the official language documentation page.
__init__(initializer) is the most frequently used of them and starts when a new class object is created. The other,
__lt__(advanced comparison), defines rules for comparing two objects of a user class. Such methods do not fall into the category of “private” or “public”, since they serve other purposes and are deeply rooted in the internal structure of the language.
#!/usr/bin/python3 class Phone: def __init__(self, number): # magic method / inititalizer print( "The Phone object was created" ) self.number = number def __lt__(self, other): # magic method / rich comparison return self.number < other.number my_phone = Phone(20) other_phone = Phone(30) if my_phone < other_phone: print( "Two instances of custom class were compared" ) print( "'__lt__' was called implicitly" ) if my_phone.__lt__(other_phone): print( "Now, '__lt__' was used explicitly" ) input( "Press Enter to exit" )
Magical methods can be called by any user in the same way as any public method in Python, but they are intended to be implicitly used in their special cases. A special case for a method
__init__ is the initialization of a new class object.
__lt__serves to compare two objects.
Python3 does not provide limited access to any class variables or methods. Data that must be hidden can in fact be read and modified. In Python3, encapsulation is more of a convention, and the programmer must take care of preserving it on his own.
- John C. Mitchell, Concepts in programming languages
- Robert C. Martin, Clean Architecture, A Craftsman's Guide to Software Structure and Design