0

If you're in one of those predicaments where cls.__private attributes just aren't enough since they can easily be accessed through inst._cls__private, and you need something a little more secure, here's a method I've been playing with for a while that fills that gap.
Note that this uses __slots__, more info on this below the code.

def private(): # no outside access

    class A(object):
        __slots__ = ['__private__']

        def __init__( inst, val=None ):
            setprivate( inst, val )

        def __repr__( inst ): return '<A %s >' % getprivate( inst )

    globals()['A'] = A # add our class to the public namespace

    dsc = A.__private__
    del A.__private__ # this makes your attribute realistically private as it removes all normal access from both the class and it's instances.

    # this is how access to the "attribute" is maintained:
    getprivate = dsc.__get__
    setprivate = dsc.__set__

private()
del private

How this works is __slots__ creates member_descriptor objects (which can't be created normally) used by the class instances, which we want because they're valid spaces in memory for applying values per instance.
When we del class.attr or alternatively delattr(class,'attr') we're simply removing the link to the member_descriptor, not the member_descriptor itself which is still registered with the class even though it's link is gone.

So in the above code in the private namespace getprivate and setprivate can't be accessed anywhere but the class and anything else inside the private namespace.
Now that we have access to the getter and setter methods of the member_descriptor, we can use __closure__ in A's methods to add a layer of security through a form of obscurity that may change as the code is modified, leaving hackers in a sandbox where their code needs updating to match the program's changes.
(if you know what you're doing, you can still figure out how to access these attributes, but it's much harder to do than, and not as solid as the typical __private approach)
So A.__init__ above calls setprivate supplying the instance and the value it's given, where it can be seen with print(repr(inst)) like so:

>>> i = A(15)
>>> repr(i)
'<A 15 >'

Hope you enjoy this little hack :)
Might I note it's also performative ;D

EDIT:
here's a dir(i) so you can see the __private__ attribute really doesn't exist in the instance (the same goes for the class).

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__']

Edited by DarkPikachu

1
Contributor
1
Reply
21
Views
4 Months
Discussion Span
Last Post by DarkPikachu
0

I want to note I'm only using __init__ above because it's standard, but honestly, I hate the method, here's why:

>>> i = A( 15 )
>>> i
<A 15 >
>>> i.__init__(20)
>>> i
<A 20 >

I learned this from hacking property() instances to actively update their getters, setters, and delerers, but it works as well with most anything else that sets it's attributes through __init__.
To prevent unwanted access like this, you should use __new__ which does the actual cration of, and returns the instance:

def private(): # no outside access

    class A(object):
        __slots__ = ['__private__']

        def __new__( cls, val=None ):
            inst = object.__new__( cls ) # since we're inheriting (object), we need to use it's __new__ method to create instances

            setprivate( inst, val ) # we can do everything here just like we do with __init__

            return inst

        def __repr__( inst ): return '<A %s >' % getprivate( inst )

    globals()['A'] = A # add our class to the public namespace

    dsc = A.__private__
    del A.__private__ # this makes your attribute realistically private as it removes all normal access from both the class and it's instances.

    # this is how access to the "attribute" is maintained:
    getprivate = dsc.__get__
    setprivate = dsc.__set__

private()
del private

now if we try to hack the values, nothing happens:

>>> i = A(15)
>>> i.__init__(20)
>>> i
<A 15 >

Edited by DarkPikachu

This question has already been answered. Start a new discussion instead.
Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.