1

I am reading up on how we ensure data encapsulation in python.One of the blog says "Data Encapsulation means, that we should only be able to access private attributes via getters and setters"

Consider the following snippets from the blog:

class Robot:
    def __init__(self, name=None, build_year=None):
        self.name = name
        self.build_year = build_year

Now, if i create the object of the class as below:

obj1=Robot()
obj1.name('Robo1")
obj1.build_year("1978")

Currently, i can access the attributes directly as i have defined them public(without the __notation)

Now to ensure data encapsulation, i need to define the attributes as privates using the __ notation and access private attributes via getters and setters.

So the new class definition is as follows:

class Robot:

    def __init__(self, name=None, build_year=2000):
        self.__name = name
        self.__build_year = build_year            
    def set_name(self, name):
        self.__name = name

    def get_name(self):
        return self.__name    

    def set_build_year(self, by):
        self.__build_year = by

    def get_build_year(self):
        return self.__build_year    

Now i instantiate the class as below:

x = Robot("Marvin", 1979)
x.set_build_year(1993)

This way, i achive data encapsulation as private data members are no longer accessed directly and they can only be accessed via the class methods.

Q1:Why are we doing this? Who are we protecting the code from? Who is outside world?Anyone who has the source code can tweak it as per their requirement, so why at all do we add extra methods(get/set) to modify/tweak the attributes?

Q2:Is the above example considered data encapsulation?

fsociety
  • 977
  • 3
  • 12
  • 23
  • I urge you to read http://stackoverflow.com/questions/18300953/why-encapsulation-is-an-important-feature-of-oop-languages – Quaker Dec 11 '16 at 13:50
  • 1
    In Python you can always get at the data bypassing the `setters` and `getters`. In most other real-wordl computer language implementations as well, just not so easy as in Python thereby giving a false sense of security. – Anthon Dec 11 '16 at 13:50
  • Note that in the linked article, at the end of the "Public- Protected- and Private Attributes" section, it says: "There are at least two good reasons against such an approach. First of all not every private attribute needs to be accessed from outside. Second, we will create non-pythonic Code this way, as you will learn soon". – PM 2Ring Dec 11 '16 at 13:58
  • The usual Python philosophy is that "We're all consenting adults here", so we don't normally go around hiding attributes and then adding extra code to access them via simplistic getter & setter methods. OTOH, Python's descriptor machinery allows you to have getters & setters when you really do need them (as chepner's answer shows), and those getters & setters appear to callers of your code like simple attribute accesses, so you get the best of both worlds. – PM 2Ring Dec 11 '16 at 14:03

2 Answers2

5

Data encapsulation is slightly more general than access protection. name and build_year are encapsulated by the class Robot regardless of how you define the attributes. Python takes the position that getters and setters that do nothing more than access or assign to the underlying attribute are unnecessary.

Even using the double-underscore prefix is just advisory, and is more concerned with preventing name collisions in subclasses. If you really wanted to get to the __build_year attribute directly, you still could with

# Prefix attribute name with _Robot
x._Robot__build_year = 1993

A better design in Python is to use a property, which causes Python to invoke a defined getter and/or setter whenever an attribute is defined directly. For example:

class Robot(object):
    def __init__(self, name, by):
        self.name = name
        self.build_year = by

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, newname):
        self._name = newname

    @property
    def build_year(self):
        return self._build_year

    @build_year.setter
    def build_year(self, newby):
        self._build_year = newby

You wouldn't actually define these property functions so simply, but a big benefit is that you can start by allowing direct access to a name attribute, and if you decide later that there should be more logic involved in getting/setting the value and you want to switch to properties, you can do so without affecting existing code. Code like

x = Robot("bob", 1993)
x.build_year = 1993

will work the same whether or not x.build_year = 1993 assigns to build_year directly or if it really triggers a call to the property setter.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • I've edited your code example, I think that the underscores in __init__()-method for the instance variables would not work if you add more logic e.g. in the name-setter. Also I've corrected the setter variable (name -> newname). Feel free to undo my changes if they were misleading, just want to inform you about these: https://stackoverflow.com/review/suggested-edits/20192932 – colidyre Jul 03 '18 at 12:20
  • The underscores are intentional; the names are private implementation details of the property. – chepner Jul 03 '18 at 12:22
  • Hmmm, have you tried to build more logic, e.g. in name-setter: `if newname == "foo": raise AttributeError("Nope")`. Then you should not create a new robot with name "foo". But with your code it's still allowed. I guess the main point is, that the private/protected/public detail is handled in the function decorated by property and the instance variable looks like a public one. It is possible that I missed a huge point here?! – colidyre Jul 03 '18 at 12:36
  • You can add whatever logic you want to the property functions. There is still no real notion of public-vs-private; the `_` is simply conventional to suggest that the user should not access the variable directly, and only use whatever interface the property provides. – chepner Jul 03 '18 at 13:46
  • Yes, but this is not my point. Question: Why is your code not working as expected if you leave the underscore for name in init-method by adding e.g. `if newname == "foo": raise` in name setter. I would expect that you cannot create an object with name "foo" then. But it's working! If you set the name without underscore, only then you cannot create an object with name "foo". – colidyre Jul 03 '18 at 14:01
  • Oh, because I'm lazy. You can use `self.name = name` in `__init__` to make sure the setter is used. I've updated the answer. (If the value is being hard-coded, not set by an argument to `__init__`, I would bypass the setter for efficiency.) – chepner Jul 03 '18 at 14:09
  • Okay, that's exactly why I edited your answer. In this case, the code was simply not accurate enough. Unfortunately, you had rejected my changes and didn't fully understand my point, but now I am satisfied. :) – colidyre Jul 03 '18 at 14:37
1

About source code: sometimes you supply others with compiled python files that does not present the source, and you don't want people to get in mess with direct attribute assignments.

Now, consider data encapsulation as safe guards, last point before assigning or supplying values:

You may want to validate or process assignments using the sets, to make sure the assignment is valid for your needs or enters to the variable in the right format, (e.g. you want to check that attribute __build_year is higher than 1800, or that the name is a string). Very important in dynamic languages like python where a variable is not declared with a specific type.

Same goes for gets. You might want to return the year as a decimal, but use it as an integer in the class.


Yes, your example is a basic data encapsulation.

Uriel
  • 15,579
  • 6
  • 25
  • 46