Python encapsulation

Adventures in Pythonic Way of Hiding Data


25 Nov 2017 View Comments
#python #code style #encapsulation #oop #getter #setter #property

As you are probably already familiar, “encapsulation” is one of the key Object Oriented Programming concepts which restricts access to method and variables which holds the purpose of keeping implementation details hidden from other users. It is an essential concept to control the information hiding for keeping data safe from outside interference and misuse. Usually, program languages haveprotected, private and public keywords to control. In Python, there aren’t those keywords available. This brings many people to believe that Python really isn’t a true OOP language without the proper data encapsulation. I don’t necessarily agree with this though. Regardless of the existence of those keywords, Python facilitates and encourages bundling data together with methods that work on that data.

If you would like to define the Object Oriented language by the existence of these keywords/terms, then Python may not be a perfect OOP language. Python is a very flexible language and it provides coders with various options to code. Anyways, I believe Python does have a proper data encapsulation and I am here to elaborate on my points why I think that way.

Python’s Private, Protected, Public identifiers

1. Private:

In Python, methods or variables can be handled privately which could be useful to hide the implementation to protect its own class. Those private attributes then are modifiable within its own class method and not outside of the class. Objects can hold important/sensitive data for your application and you do not want that data to be tinkered from anywhere outside the class because as an author of the class, you would know the most about the data and methods in that class that you determined its best to not let others alter the value/function. In Python, you can use private attributes by assigning a method or variable with __ prefix (aka “dunder”).

Let’s take a look at an example:

class C(object):
    def __init__(self):
        self.__a = 123 

c = c()

In above example, the variable __a will not be accessible as an instance/class variable. When you try to run whats inside c.__a, you will encounter an error saying, class C has no attribute '__c'. What exactly is going on? Python basically does a little mangling on the name. It updates the __a variable to _Myclass__a, which in this example, it is _C__a. So if you execute c._C__a instead, you will be getting “123” as the output. However, accessing directly to that variable is really discouraged and should not be used. Think about when the class name changes to something else or something. You will be in a nightmare trying to fix all the codes written this way. Simply, just use it to act privately like any other language.

However, there may be a case where you might need to read or update this variable from outside of the class. If so, introduce an accessor/mutator(getter/setter) instead, like this:

def getA(self):
    return self.__a
def setA(self,newA):
    self.__a = newA

Although we will go into details on the Pythonic getters/setters later, as a general programming guideline, you can use the getter/setter to get/set the private variable if you really need to fiddle with the private variable in your application. Some people may argue that using the double underscores, __, isn’t a true form of handling the private feature. However, regardless of how Python mangles the variable prefixed with a double underscore, that really is an implementation detail that developers don’t really need to worry about. It is important to know and understand why and how it is acting behind the scene, but not so important that you should worry/sweat about whether it will work the way it should.

2. Protected:

"Some people teach that _x is Python's equivalent of protected, and __x its equivalent of private, but that's very misleading."

Despite the fact that “protected” identifier is used seldomly, they do indeed carry values. The “protected” identifier is useful to control access levels within its package and subclasses. In Python, the “protected” identifier is handled really by the “convention”. Python doesn’t have a dedicated keyword to handle the protected behaviour between the classes (inheritance). Normally, by the convention, you would add a prefix of an underscore, _, to denote protected identifier. It’s important to keep in mind that the single underscore has only a conventional meaning only in Python. Python’s _ doesn’t actually stop you from accessing the attribute from outside, it just discourages you. You do not need to follow this convention, you can simply use it to denote that this identifier with the underscore prefix is to be used cautiously aka “use with caution”. The convention is really up to those groups writing the software. Commonly though, a leading underscore in Python is used as an indicate that something is internal, and not a part of the API, and use it on your own risk type of thing which doesn’t necessarily mean the variables/methods is “protected”.

3. Public:

Almost all variables and methods that you’ve seen so far (with the exception of the constructors and identifiers with leading underscores) are probably all public. Public identifiers can be accessed/modified from anywhere whether its inside or outside of the class. To create a public variable or method, it’s quite simple. “Don’t use any underscore” to define things.

Below is a table which summarizes what each private, protected, private means in Python:

Type Accessibility
public Accessible from anywhere
protected Like a public member, but they shouldn’t be directly accessed from outside.
private method Accessible only in their own class. starts with two underscores
private variable Accessible only in their own class or by a method if defined. starts with two underscores

Getters and Setters

People would typically say that “The Pythonic way is to not use getter/setter” or “Python is not Java, don’t use getter/setter”. Well, they are not entirely wrong. Python code strives to adhere to the Uniform Access Principle. However, UAP does not say to avoid using getters/setters, it means use it properly at the end of the day. Specifically, the accepted approach says:

  1. It is acceptable to simply access class or object members directly.
    Allow, for instance, foo.x = 0, not foo.set_x(0)

    class Square(object):
        def __init__(self, x):
            self.x = x
    
    foo = Square(5)
    print(foo.x)
    foo.x = 6
  2. If you need to wrap the accesses inside methods, for a possible reason described below*, use @property decorator (annotation), which preserves the access semantics. That is, foo.x = 0 now invokes foo.set_x(0).

    class Foo(object):
        def __init__(self):
            self.__x = None
    
        @property
        def x(self):
            """I'm the 'x' property."""
            return self.__x
    
        @x.setter
        def x(self, value):
            self.__x = value
    
        @x.deleter
        def x(self):
            del self.__x
    In order to use these methods, you can first create an instance, for example, foo then you can access the "x" by calling foo.x, you can set a value using foo.x = 0 (or foo.set_x(0)) or you can delete the variable by del(foo.x) to see the value is gone.

* I would like to encourage use of getter/setter when they are useful. It is an important to remember getter/setter are required in some cases. Remember the setter example I gave above? What if we are putting in a condition for setting the value of the private variable?

def setA(self, newA):
    if (newA > CONSTANT)
        self.__a = newA

This ensures our private variable is set only when the new value is greater than the CONSTANT value. We have the control of how the variable will be set depending on the requirement. Being able to control the getting/setting is where the “data encapsulation” really kicks in. We are essentially protecting the private __a variable by allowing the user to update this private variable on the conditions abided by the rules we created. Pythonic way of having the above setter would look something like this though:

@x.setter
def a(self, newA):
    if (newA > CONSTANT)
        self.__a = newA
Share this post

Me

I am a passionate software developer working in Downtown, Vancouver. I strongly believe in art of algorithms and together with it to write clean and efficient software to build awesome products. If you would like to connect with me, choose one from below options :) You can also send me an email at