11

What's wrong with this? From objective, and functional standpoints?

import sys

class EncapsulationClass(object):

  def __init__(self):
    self.privates = ["__dict__", "privates", "protected", "a"]
    self.protected = ["b"]

    print self.privates

    self.a = 1
    self.b = 2
    self.c = 3
    pass

  def __getattribute__(self, name):
    if sys._getframe(1).f_code.co_argcount == 0:
      if name in self.privates:
        raise Exception("Access to private attribute \"%s\" is not allowed" % name)
      else:
        return object.__getattribute__(self, name)
    else:
      return object.__getattribute__(self, name)

  def __setattr__(self, name, value):
    if sys._getframe(1).f_code.co_argcount == 0:
      if name in self.privates:
        raise Exception("Setting private attribute \"%s\" is not allowed" % name)
      elif name in self.protected:
        raise Exception("Setting protected attribute \"%s\" is not allowed" % name)
      else:
        return object.__setattr__(self, name, value)
    else:
      return object.__setattr__(self, name, value)


example = EncapsulationClass()

example.a = 10 # Exception: Setting private attribute "a" is not allowed
example.b = 10 # Exception: Setting protected attribute "b" is not allowed
example.c = 10 # example.c == 10

example.__dict__["privates"] # Exception: Setting protected attribute "b" is not allowed

What would actually be wrong with doing something like this?

Is there any better way to achieve encapsulation in Python?

kenorb
  • 155,785
  • 88
  • 678
  • 743
will
  • 10,260
  • 6
  • 46
  • 69
  • 4
    ...what do you mean? What do you think is wrong with it? Does it run? – jonrsharpe Oct 06 '14 at 12:53
  • 2
    Could've asked in [codereview](http://codereview.stackexchange.com/) –  Oct 06 '14 at 12:56
  • It seems to work fine, yes, but i so often see people saying "python does not have encapsulation", so presumed it couldn't be as simple as this. – will Oct 06 '14 at 12:57
  • 2
    I wouldn't call code that accesses sys._getframe(1).f_code.co_argcount "simple", but never mind. What happens if I do `EncapsulationClass.protected = []` from my code? – Daniel Roseman Oct 06 '14 at 12:59
  • @DanielRoseman - you break it! I didn't spot that one... – will Oct 06 '14 at 13:01
  • You can never stop people doing whatever they like in your class. What happens if I do `EncapsulationClass.__getattribute__ = ....` or `EncapsulationClass.__setattr__ = ....` to rewrite them to more 'standard' versions? – Ben Oct 06 '14 at 13:48
  • @Ben that can be prevented by adding `"__getattribute__"` and `"__setattr__" to the `privates` array though. And sure, then you could evade it by doing `sys._getframe = lambda x: type("a", (object,), dict(f_code=type("a", (object,), dict(co_argcount=1))))`, but how far down this rabbit hole do you go? – will Oct 06 '14 at 14:14
  • @will That's kind of our question too - how far down the rabbit hole do you go to *stop* people doing this? What's the point? – Ben Oct 06 '14 at 14:40
  • @Ben i guess my question is would it even be possible to do do this? It's purely curiosity. I can protect against redefining `sys._getframe` by reloading `sys`, i can protect against someone subclassing it and then refdefining `__get/set...` by making the class final as done [here](http://stackoverflow.com/a/3949004/432913), but i can't think of any way to truly make it so it can't be broken. – will Oct 06 '14 at 14:52
  • @will I think it's wonderful for the occasions that you really want encapsulation. Python allows it. – Heath Hunnicutt Dec 03 '15 at 07:48
  • @HeathHunnicutt It does not really though, since the methods can be overwritten to remove the protection. – will Dec 03 '15 at 12:08
  • @will, who cares? In C, the private members can be accessed with pointer tricks. Every mechanism has its backdoor, and you aren't concerned with people who need to deliberately bypass your encapsulation. The purpose of what you've implemented is to prevent casual use of private members, in order to avoid the implementation of bugs. That's the purpose of encapsulation, and your code accomplishes it. – Heath Hunnicutt Dec 03 '15 at 18:24
  • This is stronger enforcement of class member access rather than encapsulation. Could this be done with introspection rather than using on `self.privates` and `self.protected`? Follow the common Python style of naming variables `__a` and `_b`. Then the methods would check whether the names begin with `__` or `_` to allow or deny access. – Mr. Lance E Sloan Jul 19 '18 at 17:24

3 Answers3

48

Python has encapsulation - you are using it in your class.

What it doesn't have is access control such as private and protected attributes. However, in Python, there is an attribute naming convention to denote private attributes by prefixing the attribute with one or two underscores, e.g:

self._a
self.__a 

A single underscore indicates to the user of a class that an attribute should be considered private to the class, and should not be accessed directly.

A double underscore indicates the same, however, Python will mangle the attribute name somewhat to attempt to hide it.

class C(object):
    def __init__(self):
        self.a = 123    # OK to access directly
        self._a = 123   # should be considered private
        self.__a = 123  # considered private, name mangled

>>> c = C()
>>> c.a
123
>>> c._a
123
>>> c.__a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute '__a'
>>> c._C__a
123

You can see in the last example that the name was changed from __a to _C__a, although it is still accessible within the class as self.__a.

mhawke
  • 84,695
  • 9
  • 117
  • 138
  • 1
    I know all this, this isn't quite the same as what i'm talking about though, and it seems that there are two definitions of encapsulation. One of them is just the packign of several pieces of data into a class, and the other is to do with actually hiding the data / making it inaccessible. the `__a` stuff i'm fine with, but that's not really the same as what i'm after - i'm interested more in emulating the `private`, `protected`, etc. variable modifiers seen in other languages. – will Oct 06 '14 at 13:12
  • My understanding of the whole `__a` variables was just that it's a method to let people using your codes know that they are not variables you intend to have used outside of the module - and as such people wouldn't rely on them, as they're liable to change at any time. – will Oct 06 '14 at 13:13
  • 2
    [We are all consenting adults](http://codingstyleguide.com/style/39/python-we-are-all-consenting-adults) – PM 2Ring Oct 06 '14 at 13:29
  • @Will - yes, encapsulation usually includes a mechanism for restricting access as you say. And yes, `__` is a convention, as I said. – mhawke Oct 06 '14 at 13:31
  • @PM2Ring i'm aware of this, and have read exactly that before. It's more of a curiosity thing than disagreeing with the design decisions of python – will Oct 06 '14 at 14:06
  • Re-posting: [We are all consenting adults](http://web.archive.org/web/20140709202519/http://codingstyleguide.com/style/39/python-we-are-all-consenting-adults) and copy at [docs.python-guide](http://web.archive.org/web/20140702180105/http://docs.python-guide.org/en/latest/writing/style/#we-are-all-consenting-adults) – kenorb Sep 03 '15 at 22:51
  • "consenting adults" → ["responsible users"](https://docs.python-guide.org/writing/style/#we-are-all-responsible-users) – Mr. Lance E Sloan Jul 19 '18 at 17:14
1

Well, Python does not have encapsulation as a sort of "philosophical" decision, in the same way that we use duck typing a lot. Personally I don't see the point of using private or protected arguments in a Python code.

Speaking of your code, it seems to work fine with the following getters and setters:

def set_a(self, v):
    self.a = v

def get_a(self):
    return self.a

if you make the following modification to your last line of __ getattribute __(self, name):

return object.__getattribute__(self, name)

However, you can use sort of a notion of variable-protecting, if you prefix your private variables with __, as mhawke mentioned. Plus, Daniel's comment points out a limitation of your list arguments. You could keep the protected "get/set" behaviour by adding "private" and "protected"in your private list.

Flavian Hautbois
  • 2,940
  • 6
  • 28
  • 45
0

In Mark Lutz's book Learning Python, Fifth edition, he mentioned a way of simulating encapsulation of class level like this:

"""
Created on Sun Oct  4 10:16:30 2020
@author: Mark Lutz
A typical implementation of encapsulation in python,
to use, call:@private(‘var1’, ‘var2’...)
"""

def private(*values):
    def decorator(cls):
        class Proxy:
            def __init__(self, *args, **kwargs):
                self.inst = cls(*args, **kwargs)
            def __call__(self, cls, *args, **kwargs):
                return self.inst
            def __getattr__(self, attr):
                if attr in values:
                    raise AttributeError("Private valueiables are not accessible!")
                else: return getattr(self.inst, attr)
            def __setattr__(self, attr, val):
                # Allow access inside the class
                if attr == 'inst': self.__dict__[attr] = val
                elif attr in values:
                    raise AttributeError("Private valueiables are not accessible!")
                else: setattr(self.inst, attr, val)
            def __str__(self):
                return self.inst.__str__()
        return Proxy
    return decorator

this can be used for class-level encapsulation (e.g.limiting the access of a variable or method in a class).

For module-level encapsulation, however, the only way that I can think of is that you create a file and write the init.py. However if those who writes the client program knows the structure of your file / package, this can still not stop them from importing stuff.

Sam Y
  • 52
  • 5