1

In an attempt to discover the boundaries of Python as a language I'm exploring whether it is possible to go further with information hiding than the convention of using a leading underscore to denote 'private' implementation details.

I have managed to achieve some additional level of privacy of fields and methods using code such as this to copy 'public' stuff from a locally defined class:

from __future__ import print_function

class Dog(object):
    def __init__(self):
        class Guts(object):
            def __init__(self):
                self._dog_sound = "woof"
                self._repeat = 1

            def _make_sound(self):
                for _ in range(self._repeat):
                    print(self._dog_sound)

            def get_repeat(self):
                return self._repeat

            def set_repeat(self, value):
                self._repeat = value

            @property
            def repeat(self):
                return self._repeat

            @repeat.setter
            def repeat(self, value):
                self._repeat = value

            def speak(self):
                self._make_sound()

        guts = Guts()

        # Make public methods
        self.speak = guts.speak
        self.set_repeat = guts.set_repeat
        self.get_repeat = guts.get_repeat

dog = Dog()
print("Speak once:")
dog.speak()
print("Speak twice:")
dog.set_repeat(2)
dog.speak()

However, I'm struggling to find a way to do the same for the property setter and getter.

I want to be able to write code like this:

print("Speak thrice:")
dog.repeat = 3
dog.speak()

and for it to actually print 'woof' three times.

I've tried all of the following in Dog.__init__, none of which blow up, but neither do they seem to have any effect:

Dog.repeat = guts.repeat
self.repeat = guts.repeat
Dog.repeat = Guts.repeat
self.repeat = Guts.repeat
self.repeat = property(Guts.repeat.getter, Guts.repeat.setter)
self.repeat = property(Guts.repeat.fget, Guts.repeat.fset)
Matthew Murdoch
  • 28,946
  • 26
  • 89
  • 125
  • 2
    Why are you doing this? If you want Java, use Java, don't try to un-Python Python. – Silas Ray May 21 '13 at 19:29
  • Have you read the [Descriptor HowTo Guide](http://docs.python.org/2.7/howto/descriptor.html)? If not, it will be hard to explain why your code doesn't do what you want, and how to make it do so. And, once you finish reading it, you'll probably know the answer yourself. – abarnert May 21 '13 at 19:35
  • @sr2222 As I said, I'm exploring the boundaries of Python. This is the best way I know of to deeply understand a language and why to use (and not use) it in certain ways. – Matthew Murdoch May 21 '13 at 19:57
  • @abamert Yes, I have read it in the past, but I shall go back and take another look. Thanks for the prompting! – Matthew Murdoch May 21 '13 at 19:59

3 Answers3

2

Descriptors only work when defined on the class, not the instance. See this previous question and this one and the documentation that abarnert already pointed you to. The key statement is:

For objects, the machinery is in object.__getattribute__() which transforms b.x into type(b).__dict__['x'].__get__(b, type(b)).

Note the reference to type(b). There is only one property object for the whole class, and the instance information is passed in at access time.

That means you can't have a property on an individual Dog instance that deals only with that particular dog's guts. You have to define a property on the Dog class, and have it access the guts of the individual dog via self. Except you can't do that with your setup, because you're not storing a reference to the dog's guts on self, because you're trying to hide the guts.

The bottom line is that you can't effectively proxy attribute access to an underlying "guts" object without storing a reference to that object on the "outward-facing" object. And if you store such a reference, people can use it to modify the guts in an "unauthorized" way.

Also, sad to say, even your existing example doesn't really protect the guts. I can do this:

>>> d = Dog()
>>> d.speak.__self__._repeat = 3
>>> d.speak()
woof
woof
woof

Even though you try to hide the guts by exposing only the public methods of the guts, those public methods themselves contain a reference to the actual Guts object, allowing anyone to sneak in and modify the guts directly, bypassing your information-hiding scheme. This is why it's futile to try to enforce information-hiding in Python: core parts of the language like methods already expose a lot of stuff, and you just can't plug all those leaks.

Community
  • 1
  • 1
BrenBarn
  • 210,788
  • 30
  • 364
  • 352
1

You could set up a system with descriptors and a metaclass where the Dog metaclass creates descriptors for all the public attributes of the class and constructs a SecretDog class containing all the private methods, then have each Dog instance has a shadow SecretDog instance tracked by the descriptors that houses your 'private' implementation. However, this would be going an awfully long way to "secure" the private implementation in a language that by it's nature can't really have anything private. You'll also have a hell of a time getting inheritance to work reliably.

Ultimately, if you want to hide a private implementation in Python, you should probably be writing it as a C extension (or not trying to in the first place). If your goal is a deeper understanding of the language, looking at writing a C extension isn't a bad place to start.

Silas Ray
  • 24,298
  • 5
  • 44
  • 60
-1

You will need something like this

>>> class hi:
...     def __setattr__(self, attr, value):
...             getattr(self, attr)(value)
...     def haha(self, val):
...             print val
...
>>> a = hi()
>>> a.haha = 10
10

just be careful with this

gariel
  • 667
  • 3
  • 13