Created
January 31, 2018 12:32
-
-
Save hallazzang/487d1243a9bf9429467b876aa0f90d9b to your computer and use it in GitHub Desktop.
Private variables in Python: dark magic
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
from collections import defaultdict | |
def is_private_name(name): | |
return not name.startswith('__') and name.startswith('_') | |
class InstanceProxy: | |
def __init__(self, instance, privates): | |
self._instance = instance | |
self._privates = privates | |
def __getattr__(self, name): | |
if is_private_name(name): | |
if name in self._privates: | |
return self._privates[name] | |
else: | |
raise AttributeError( | |
"'%s' object has no private attribute '%s'" % ( | |
self._instance.__class__.__name__, name)) | |
else: | |
return getattr(self._instance, name) | |
def __setattr__(self, name, value): | |
if name not in ('_instance', '_privates') and is_private_name(name): | |
self._privates[name] = value | |
else: | |
super().__setattr__(name, value) | |
class PrivateMeta(type): | |
_privates_table = defaultdict(dict) | |
def __new__(cls, name, bases, clsdict): | |
def decorate_method(method): | |
def _decorated_method(instance, *args, **kwargs): | |
privates = cls._privates_table[instance.__class__] | |
return method(InstanceProxy(instance, privates), *args, **kwargs) | |
return _decorated_method | |
privates = { | |
k: v for k, v in clsdict.items() | |
if not callable(v) and is_private_name(k) | |
} | |
clsdict = { | |
k: v for k, v in clsdict.items() | |
if k not in privates | |
} | |
for key in clsdict.keys(): | |
if callable(clsdict[key]): | |
clsdict[key] = decorate_method(clsdict[key]) | |
klass = super().__new__(cls, name, bases, clsdict) | |
cls._privates_table[klass] = privates | |
return klass | |
class Foo(metaclass=PrivateMeta): | |
public = "I'm public attribute" | |
_private = "I'm private attribute" | |
def bar(self): | |
print('Foo.bar:', self.public) | |
print('Foo.bar:', self._private) | |
def _inner(self): | |
print('Foo._inner:', self.public) | |
print('Foo._inner:', self._private) | |
if __name__ == '__main__': | |
foo = Foo() | |
foo.bar() | |
foo._inner() | |
print(dir(foo)) # you can see '_private' attribute is actually hidden | |
print(foo.public) | |
print(foo._private) # throws AttributeError | |
""" | |
Output: | |
Foo.bar: I'm public attribute | |
Foo.bar: I'm private attribute | |
Foo._inner: I'm public attribute | |
Foo._inner: I'm private attribute | |
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', | |
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', | |
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', | |
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', | |
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', | |
'__weakref__', '_inner', 'bar', 'public'] | |
I'm public attribute | |
Traceback (most recent call last): | |
File "test.py", line 78, in <module> | |
print(foo._private) # throws AttributeError | |
AttributeError: 'Foo' object has no attribute '_private' | |
""" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment