Skip to content

Instantly share code, notes, and snippets.

@hallazzang
Created January 31, 2018 12:32
Show Gist options
  • Save hallazzang/487d1243a9bf9429467b876aa0f90d9b to your computer and use it in GitHub Desktop.
Save hallazzang/487d1243a9bf9429467b876aa0f90d9b to your computer and use it in GitHub Desktop.
Private variables in Python: dark magic
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