Skip to content

Instantly share code, notes, and snippets.

@maxfischer2781
Last active July 30, 2020 08:21
Show Gist options
  • Save maxfischer2781/346a2452efbff7baf9a58a569c5082f7 to your computer and use it in GitHub Desktop.
Save maxfischer2781/346a2452efbff7baf9a58a569c5082f7 to your computer and use it in GitHub Desktop.
Python private class attributes via decorator
"""
This is a toy example for implementing private fields via decorators
Provides a base type ``Class`` and decorator ``trusted`` to handle
private/public data fields. Classes that derive from ``Class`` can own
private data, and methods marked as ``trusted`` can access it.
Every ``trusted`` method receives a privileged ``self`` that operates on
private data; public data is accessible as ``self.__public__``. Regular
methods and all external clients only operate on the public data.
.. code:: python3
class User(Class):
# can declare public slots
__slots__ = 'name',
@trusted
def __init__(self, name, password):
# trusted: directly access private data
self.password = password
# trusted: explicitly access public data
self.__public__.name = name
# use trusted method to work with private data
@trusted
def balance(self):
query_balance(
user=self.__public__.name,
pw=self.password,
)
# use untrusted method by default
@property
def first_name(self):
return self.name.split()[0]
def __repr__(self):
return '%s(name=%s, password=???)' % (
self.__class__.__name__,
# untrusted: directly access public data
self.name,
)
:note: This implementation of private fields is for demonstration only and not
suitable in a security context. The data is accessible with little effort.
"""
from typing import Optional, TypeVar, Generic
C = TypeVar('C', bound='Class')
class PrivateView(Generic[C]):
"""View to access the private state of an instance"""
__slots__ = '__subject__',
@property
def __public__(self):
"""The public data of the object"""
return self.__subject__
def __init__(self, subject: C):
object.__setattr__(self, '__subject__', subject)
try:
subject.__private__
except AttributeError:
subject.__private__ = {}
def __getattr__(self, symbol):
try:
return self.__subject__.__private__[symbol]
except KeyError:
raise AttributeError
def __setattr__(self, symbol, value):
self.__subject__.__private__[symbol] = value
def __delattr__(self, symbol):
try:
del self.__subject__.__private__[symbol]
except KeyError:
raise AttributeError
def __repr__(self):
return f'<private state of {self.__subject__.__class__.__name__} at {id(self.__subject__}>'
class Class:
"""
Baseclass for classes with private data
:note: This class is only needed when ``__slots__`` are desired.
A regular class is suitable otherwise.
"""
__slots__ = ('__dict__', '__private__')
class trusted:
"""
Decorator to mark a method as trusted
.. code:: python3
class Bar(Class):
@trusted
def trusted_method(self):
...
def untrusted_method(self):
...
"""
def __init__(self, func):
self.func = func
def __get__(self, instance: Optional[Class], owner):
if instance is None:
return self
else:
return self.func.__get__(PrivateView(instance), owner)
if __name__ == '__main__':
class Foo(Class):
@trusted
def __init__(self: 'PrivateView[Foo]'):
self.private = 'private'
self.__public__.public = 'public'
@trusted
def trusted_access(self: 'PrivateView[Foo]'):
self.__public__._fetch(self)
def untrusted_access(self: 'Foo'):
self._fetch(self)
@staticmethod
def _fetch(self):
print('.private:', getattr(self, 'private', '<inaccessible>'))
print('.public:', getattr(self, 'public', '<inaccessible>'))
print('.__public__.public:', getattr(
getattr(self, '__public__', '<inaccessible>'),
'public', '<inaccessible>'
))
instance = Foo()
instance.trusted_access()
instance.untrusted_access()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment