Last active
July 30, 2020 08:21
-
-
Save maxfischer2781/346a2452efbff7baf9a58a569c5082f7 to your computer and use it in GitHub Desktop.
Python private class attributes via decorator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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