11

So I know the way to make a variable "private" in python like this:

class Foo:
    def __init__(self):
        self.__private = 'bar'

This "works" and doesn't, as shown below:

foo = Foo()
'__private' in vars(foo) #False
'_Foo__private' in vars(foo) #True

Now, I understand this is the way to make private variables in python and I like this way. It allows you to mangle names so that no subclasses accidentally override this (because it begins with the class's name), and that nobody will accidentally use it. It also gives you the power to change the private variables if you know what you are doing. Also, it is the best way to do it, because truly private variables are impossible.

Or so I thought.

Recently, I was reading PEP 8 and I saw this line:

We don't use the term "private" here, since no attribute is really private in Python (without a generally unnecessary amount of work).

This quote is found in the Designing for Inheritance section of PEP 8.

Note the phrase "without a generally unnecessary amount of work". I am now sure that there must be a way to get truly private variables in python. How would I do that?

I have tried overriding __getattribute__, but the problem is that there is no way to tell if the call is coming from inside the class or not (that I am aware of).

Also, the __dict__ attribute is annoying when trying to do this because it holds references to all instance variables.

I also thought of metaclasses, but those seem to have the same problems as __getattribute__.

Thoughts?


Note: I understand that any way to make truly private variables in python should never be done in productive code. I just want to know how it could be done.

Eb946207
  • 748
  • 8
  • 26
  • If you really want privacy, have you considered compiling your code? Depending on what you compile it to, a determined user may (or may not!) still be able to de-obfuscate the source code, but it's pretty close – Charles Landau Dec 14 '18 at 01:59
  • 2
    [No, you can't have private attributes in Python](https://www.techrepublic.com/article/no-you-cant-have-private-attributes-in-python/) – Selcuk Dec 14 '18 at 02:02
  • 1
    @CharlesLandau The question is about private (as in public, private, protected in C++/Java world) attributes, not source code privacy. – Selcuk Dec 14 '18 at 02:03
  • @Selcuk I get that, I was suggesting that obfuscation of source code is a more promising way to pursue privacy for python devs – Charles Landau Dec 14 '18 at 02:08
  • @CharlesLandau I don't care about the "can someone see my source code" part. I only want to know if it is possible to do as stated above. I don't really have a reason for privacy other than I want to learn more python. – Eb946207 Dec 14 '18 at 02:11
  • I think selcuk's link is the end of the road for you in that @EthanK, [we're all consenting adult here](https://mail.python.org/pipermail/tutor/2003-October/025932.html) – Charles Landau Dec 14 '18 at 02:18
  • 1
    @CharlesLandau I see, but the question is extending base classes and I fail to see how it is possible to extend a class from a compiled executable. In any case, it would still be easily circumvented with a `dir(classname)` call. – Selcuk Dec 14 '18 at 02:21
  • @Selcuk in terms of extending base classes I think you're right, that would be difficult. In terms of beating a `dir(classname)` call I think you would have to do some layering but I don't see any reason it would be unsolvable. Of course nothing is foolproof... – Charles Landau Dec 14 '18 at 02:27
  • Thanks for your help, everyone! I actually managed to do it myself somehow! See [my answer](https://stackoverflow.com/questions/53772454/truly-private-variables-in-python-3/53772947#answer-53772947) – Eb946207 Dec 14 '18 at 03:05
  • What is extremely unclear, not just in your specific question, but in all the ones like it that I've seen, is what does "truly private" actually mean? You seem to be treating it as some universally accepted term, but it's really not well defined to begin with. And I think that's a big part of the problem. If you could define your requirements, I'm sure it would be possible to implement them. – Mad Physicist Dec 14 '18 at 13:10
  • @MadPhysicist To me, "truly private" means the variable only visible in the class, with no way to access the variable from the outside, without a hack like getting it by the id of the object or something like that. – Eb946207 Dec 14 '18 at 22:50
  • Ethan K. That is a very poorly delineated set of constraints in Python given that classes are mutable objects themselves, monkeypatching is a real and useful thing, etc. – Mad Physicist Dec 15 '18 at 00:41
  • There is fundamentally no difference between a function accepting self as the first argument and a method in a class. How do you intend to handle such external functions, before and after they get assigned to the class dictionary? – Mad Physicist Dec 15 '18 at 00:44
  • @MadPhysicist I know functions inside and outside of a class are no different in type (and that you can monkey patch a normal function into an class), but I was thinking a difference is that a class function is not in the same scope as the scope is was created in (unless you count a clas as a scope), and can only be referenced by `Class.function`. This means the function lives in the `__dict__` and therefore can be tested to be in the class. – Eb946207 Dec 15 '18 at 00:54
  • @MadPhysicist I have edited my part-answer to include that check (done with `inspect`) – Eb946207 Dec 15 '18 at 00:58
  • This is really cool when you look into it more! – Eb946207 Feb 02 '19 at 21:34
  • 1
    The private-est attributes would come from writing a C extension, which would let you store data in a way that can't be accessed from external code except through the sledgehammer approach of manual memory manipulation. C extensions are a lot of work. – user2357112 Feb 26 '19 at 02:23
  • @user2357112 True, but private C variable can be found, it just takes a bit more work. That’s at least the case for C++ and Java, and I would assume the same for C. – Eb946207 Feb 26 '19 at 20:22

5 Answers5

5

I have tried overriding getattribute, but the problem is that there is no way to tell if the call is coming from inside the class or not (that I am aware of).

You can use the inspect module to find the name and module of the calling function, which you could compare against a whitelist.

But inspect also has getattr_static, which can bypass any __getattribute__.


Nothing is truly private in Python. There are ways to make access difficult, but there are always ways around those ways.

The only solution then, is outside of the current Python interpreter. You could use a foreign function interface to some other more secure language or a remote procedure call (e.g. xmlrpc) to the same or to another Python interpreter running in a subprocess, or even one running as a different user with different permissions. The private variable and all the functions allowed to access it will live outside the current interpreter. Then there's no way to inspect it.

This type of privilege separation is even one of the stated use cases for the Pyro RPC library.

gilch
  • 10,813
  • 1
  • 23
  • 28
5

You can get nearly the same effect without the fancy inspection by using closures instead of attributes.

class Foo:
    def __init__(self):
        private = 'bar'
        def print_private():
            print(private)
        self.print_private = print_private

foo = Foo()
foo.print_private()  # works
foo.private  # kaboom

Of course, inspect can see into closures too.

gilch
  • 10,813
  • 1
  • 23
  • 28
  • 1
    though this isn't good when you need to manage objects that can be changed, just access the private object from the closure and change it at will... also you can change the content in closure cells: https://stackoverflow.com/a/37666086/2131849 – Tcll Jan 08 '19 at 17:44
4

The reason why Python has no private attributes is that we can't tell whether it is inside or outside a class. They share the same process of attribute access. self.private is exactly the obj.private. So, if we prevent from obj.private, self.private is also prevented. The only way to differ them is to give different name and make the obj.private be the proxy of self._private by @property or data descriptor and believe that people using it are all adults.

Anyway, I'd like to share the concept of data descriptor which could make NEARLY private attributes by adding a layer of attribute proxy (As I said, this would prevent the access from 'inside' the class):

class Private:
    def __init__(self, attribute):
        self.attribute = attribute

    def __get__(self, obj, type=None):
        raise AttributeError("'{}' object has no attribute '{}'".format(obj, self.attribute))

    def __set__(self, obj, value):
        obj.__dict__[self.attribute] = value

class YourClass:
    private = Private('private')

    def __init__(self):
        self.private = 10
        print(self.private)  # Raise AttributeError

Use double underlines or change __getattribute__are both bad practices, especially the latter, may cause disasters.

Hou Lu
  • 3,012
  • 2
  • 16
  • 23
2

Well after looking at this answer about the inspect module, I (kind of) have done it!

class Foo:
    def __init__(self, private):
        self.private = private

    def __getattribute__(self, attr):
        import inspect
        frame = inspect.currentframe()
        try:
            back_self = frame.f_back.__self__
            if not back_self == self: #is it inside the class?
                ban = ('private', '__dict__') #all private vars, ban __dict__ for no loopholes
                if attr in ban:
                    msg = 'Foo object has no attribute {!r}'
                    raise AttributeError(msg.format(attr))
        finally:
            del frame
        return super().__getattribute__(attr)

    def print_private(self):
        print(self.private) #access in the class!


foo = Foo('hi')
foo.print_private() #output: hi
foo.private #makes an error

Well, almost. inspect can also be used to find the value, too. This is very close, though. It allows object.attr inside the class but creates an error if called from the outside. This is probably as close as one can get.

Eb946207
  • 748
  • 8
  • 26
  • `inspect.getattr_static(foo, 'private')`? – gilch Dec 14 '18 at 03:08
  • I tried it with your code, which is supposed to ban `__dict__` and was still able to access it from outside. Try it. – gilch Dec 14 '18 at 03:11
  • Also, `object.__getattribute__(foo, '__dict__')` can get the instance dict. – gilch Dec 14 '18 at 03:18
  • 1
    What if your first argument isn't called `self`? For that matter, what if you have an argument called `self` that isn't the first one? The name `self` is not special in Python, it's just a widely used convention. – Mad Physicist Dec 14 '18 at 03:20
  • @MadPhysicist I know, but it is a good bet. The code could be edited to change for any name, I just didn't want to loop through all names to see if they were self. – Eb946207 Dec 14 '18 at 03:29
  • What's with the `frame.f_back.__self__`? Frame objects don't have a `__self__` attribute. – user2357112 Feb 26 '19 at 01:45
  • @user2357112 Yes they do. See [the inspect documentation](https://docs.python.org/3/library/inspect.html). `__self__` is right before `__func__`. – Eb946207 Feb 26 '19 at 20:18
  • @EthanK888: Those are the attributes for method objects, not frame objects. – user2357112 Feb 26 '19 at 20:20
  • @user2357112 This was posted a bit of time ago, but I remember testing this and it worked. Have you proved this doesn’t work? – Eb946207 Feb 26 '19 at 20:21
  • @EthanK888: Try accessing `frame.__self__` on a frame object. [You'll get an AttributeError.](https://ideone.com/raFeOP) – user2357112 Feb 26 '19 at 20:22
  • @user2357112 No, not `frame.__self__` - that’s an error. Do `frame.f_back.__self__`, and make sure *that there is something to `f_back` to*. – Eb946207 Feb 26 '19 at 20:24
  • @user2357112 Note that the documentation only lists `__self__` under “methods”, which means it must be in a class. You setup your test wrong. – Eb946207 Feb 26 '19 at 20:46
  • @EthanK888: `__self__` is listed under methods because it's an attribute of method objects, not because it's an attribute of frame objects for methods. Frame object attributes are listed in a completely different section of the table. – user2357112 Feb 26 '19 at 22:10
  • @EthanK888: [Even with `frame.f_back.self`, even in a class, the same AttributeError occurs.](https://ideone.com/A1FzBg) – user2357112 Feb 26 '19 at 22:11
  • [Here's a test that just runs your code verbatim](https://ideone.com/IbjQB8), which encounters `AttributeError: 'frame' object has no attribute '__self__'` before it can even enter `print_private` (and note that this AttributeError comes from the attempt to access `frame.f_back.self`, not from your own `raise AttributeError(msg.format(attr))`). – user2357112 Feb 26 '19 at 22:17
  • @EthanK888: That's because you're on Python 2 and you wrote an old-style class. User-defined `__getattribute__` isn't supported on old-style classes. Your `__getattribute__` doesn't run at all. Note how there's no AttributeError on the attempt to access `foo.private` from outside the class. – user2357112 Feb 27 '19 at 00:13
  • [Have `Foo` inherit from `object`](https://ideone.com/Ih73un), so your `__getattribute__` actually gets called, and you get the same `AttributeError: 'frame' object has no attribute '__self__'` as on Python 3. – user2357112 Feb 27 '19 at 00:15
2

something I like to do, though it's not exactly 100% private, is to use closures in methods to R/W normally inaccessible attributes as member_descriptor objects:

def privateNS():

    class MyObject(object):
        __slots__ = ['private'] # name doesn't matter

        def __new__(cls, value): # only sets inst.private on new instance creation
            inst = object.__new__(cls)

            setprivate(inst, value)

            return inst

        # __init__ is not needed, and can't be used here to set inst.private

        def showprivate(inst):
            return getprivate(inst)

    dsc = MyObject.private # get descriptor
    getprivate = dsc.__get__
    setprivate = dsc.__set__
    del MyObject.private # revoke normal access

    return MyObject

MyObject = privateNS()
del privateNS

inst = MyObject( 20 )
print( inst.showprivate() ) # 20

note that the inst.private name does not exist and will raise an AttributeError if referenced.
but the member descriptor itself does exist, and is bound to the class.

but like I said, it's not 100% private...
you can access the descriptor methods provided to the class methods through their closures:

>>> inst.showprivate.__closure__[0].cell_contents
<method-wrapper '__get__' of member_descriptor object at 0x00E588A0>

that's the first backdoor, if said method contains __set__ in it's closures.
but if not, the 2nd backdoor is only a tad more complicated:

>>> inst.showprivate.__closure__[0].cell_contents.__self__.__set__( inst, 30 )
>>> inst.showprivate()
30

something that helps though is when using multiple closures, the order of the closure cells is dependent on the current run (like dictionary keys).

sadly though, I can't seem to figure out anything more secure than this...

the problem is as stated in an earlier answer:
attributes can't tell where they're being accessed, and providing that level of functionality through python code always leaves them open because they can always be accessed and changed.

if I'm wrong on this, please comment :)

Tcll
  • 7,140
  • 1
  • 20
  • 23
  • 1
    In my answer, I found a way to see where the call is coming from. The problem was that `inspect` seems to be able to see though anything. :( – Eb946207 Jan 08 '19 at 21:08
  • Oh wait, I misread your answer, so while mine completely hides the attribute, yours actually restricts access! nice job! – Tcll Jan 09 '19 at 01:17
  • Thanks! Sadly, `inspect` can see through it... But it is as close as I think you can get. If you find a way to stop `inspect`, though... ...that would be great! – Eb946207 Jan 09 '19 at 01:21
  • actually, it looks like my method does just that already, at least through `getattr_static` as it seems to check the class or instance `__dict__` for the attribute (I'm using `__slots__` so only the class has `__dict__`, and the private attribute is unlinked from it with `del`). but my problem is I inject the attribute's descriptor into the method closures. – Tcll Jan 09 '19 at 11:39
  • also, the issue with yours is that `getattr_static` uses `type` mechanics (base-level functionality) to break into your class and bypass your `__getattribute__` method (if you look at inspect.py, you can see what goes on, since most of the standard library is written in poor quality python code), and that's why banning `__dict__` doesn't work from you. ;) – Tcll Jan 09 '19 at 11:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/186409/discussion-between-tcll-and-ethan-k). – Tcll Jan 09 '19 at 11:54