Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python private instance data revisited

I've read various "there is no truly private data in Python instances" posts, but we're all aware of using closures in Perl and JavaScript to effectively achieve private data. So why not in Python?

For example:

import codecs

class Secret:
    def __private():
        secret_data = None

        def __init__(self, string):
            nonlocal secret_data
            if secret_data is None:
                secret_data = string
        
        def getSecret(self):
            return codecs.encode(secret_data, 'rot_13')

        return __init__, getSecret

    __init__, getSecret = __private()

Now we do:

>>> thing = Secret("gibberish")
>>> thing.getSecret()
'tvoorevfu'
>>> dir(thing)
['_Secret__private', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'getSecret']

What can you do to the instance thing to get read access to the original string (ignoring my weak encryption) or write access to it?

I'm teaching my students about Python classes this week and I'm trying to understand why, given closures, the techniques for JavaScript & Perl won't work for Python.

like image 764
cdlane Avatar asked Feb 11 '16 07:02

cdlane


2 Answers

If you just want to access the original, it's not that hard, since Python function implement a rather thorough inspection api. You can access the original secret with something like this:

thing = Secret("gibberish")
# __init__ doesn't need to be used here; anything defined within the closure will do
thing.__init__.__func__.__closure__[0].cell_contents

And, hey! We get the original value.

It is harder---but not impossible---to modify the value (see here). Modified for this setup:

import ctypes
...

thing = Secret("gibberish")
cell = ctypes.py_object(thing.__init__.__func__.__closure__[0])
new_value = ctypes.py_object('whatever')
ctypes.pythonapi.PyCell_Set(cell, new_value)

thing.getSecret()
like image 74
ig0774 Avatar answered Sep 27 '22 18:09

ig0774


You wouldn't ordinarily do this but you can dig into the instance with module inspect.

>>> thing = Secret("gibberish")
>>> thing.getSecret()
'tvoorevfu'
>>> import inspect
>>> inspect.getclosurevars(thing.getSecret).nonlocals['secret_data']
'gibberish'
>>> inspect.getclosurevars(thing.__init__).nonlocals['secret_data']
'gibberish'

Given one of the functions within the closure, you can access the closure's variables. I haven't yet found a way to modify the variable.

So it's not impossible if you are willing to go to some effort. Why you would do that in the normal course of programming I don't know.

like image 25
mhawke Avatar answered Sep 27 '22 17:09

mhawke