-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprivate_attrs.py
More file actions
180 lines (155 loc) · 7.39 KB
/
private_attrs.py
File metadata and controls
180 lines (155 loc) · 7.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python3
# encoding:utf-8
"""
This module provides support for easy addition of private attributes inside your custom objects,
which are totally unreachable from outside the class definition, as in C++ 'private' clause.
"""
import inspect
from multiprocessing import Manager
from typing import Dict, Any, Tuple
def PrivateAttrs(proxy=False):
'''
Create a brand new PrivateAttrs instance. If 'proxy' is True, then the stored attributes are shared
between processes, as long as those attributes are also proxy objects when they are mutable, like lists or dictionaries.
:param proxy: Whether the stored attributes have to be shared between processes. False by default.
:return: An independent and empty PrivateAttrs instance.
'''
# In this dict we store the private attributes of each instance,
# following this pattern { id(self): { "attr1": val1, "attr2": val2, ...}, }
# with self as the object instance.
private_attrs: Dict[int, Dict[str, Any]] = {}
# In this dict we store the private static attributes, which are
# shared between all the instances of a same object.
static_private_attrs: Dict[str, Any] = {}
if proxy:
m = Manager()
private_attrs: Dict[int, Dict[str, Any]] = m.dict()
static_private_attrs: Dict[str, Any] = m.dict()
def get_instance():
return inspect.currentframe().f_back.f_back.f_locals['self']
class PrivateAttrs:
def __init__(self):
if proxy:
super().__setattr__('manager', m)
@staticmethod
def register_instance(instance: object) -> None:
'''
We need to register the instance in the PrivateAttrs object before starting storing our own attributes. This
method must be called in the class __init__() method, and only once. It will create an empty dictionary
to store the private attrs of the instance.
:param instance: The object instance.
'''
if private_attrs.get(id(instance)) is None:
if proxy:
private_attrs[id(instance)] = m.dict()
else:
private_attrs[id(instance)] = {}
@staticmethod
def delete(instance: object) -> None:
'''
Call this method inside, and only inside, the __del__() method of your custom class to avoid
really unlikely but possible references problems.
:param instance: The object instance.
'''
# Needs to be in a try-except block because of unexpected errors
try:
private_attrs.pop(id(instance), None)
except:
pass
@staticmethod
def get_private_attr(instance: object, name: str, exception: bool = True, default: Any = None) -> Any:
'''
Retrieve a private attribute from an object instance.
:param instance: The object instance.
:param name: Name of the attribute.
:param exception: Whether this method should raise an AttributeError or not if the attribute does not exist.
True by default.
:param default: Default value to return if 'exception' is False and the attribute does not exist. None by default.
'''
try:
return private_attrs[id(instance)][name]
except KeyError:
if exception:
raise AttributeError(f"'{type(instance)}' object has no private attribute '{name}'")
return default
def __getattr__(self, item):
if item.endswith('_static'):
return self.get_static_private_attr(item)
instance = get_instance()
return self.get_private_attr(instance=instance, name=item)
@staticmethod
def set_private_attr(instance: object, name: str, value: Any) -> None:
'''
Create or modify, if already exists, a private attribute in a object instance.
:param instance: The object instance.
:param name: Name of the attribute.
:param value: Value of the attribute.
'''
private_attrs[id(instance)][name] = value
def __setattr__(self, key, value):
if key.endswith('_static'):
self.set_static_private_attr(name=key, value=value)
else:
instance = get_instance()
self.set_private_attr(instance=instance, name=key, value=value)
@staticmethod
def del_private_attr(instance: object, name: str) -> None:
'''
Remove a private attribute from an object instance.
This method always returns None, even if the attribute doesn't exist.
:param instance: The object instance.
:param name: Name of the attribute to remove.
:return: None
'''
private_attrs[id(instance)].pop(name, None)
def __delattr__(self, item):
if item.endswith('_static'):
self.del_static_private_attr(name=item)
else:
instance = get_instance()
self.del_private_attr(item, instance)
@staticmethod
def get_static_private_attr(name: str, exception: bool = True, default: Any = None) -> Any:
'''
Retrieve a private static attribute from a class.
:param name: Name of the static attribute.
:param exception: Whether this method should raise an AttributeError or not if the attribute does not exist.
True by default.
:param default: Default value to return if 'exception' is False and the attribute does not exist. None by default.
'''
try:
return static_private_attrs[name]
except KeyError:
if exception:
raise AttributeError(f"This class has no private static attribute '{name}'")
return default
@staticmethod
def set_static_private_attr(name: str, value: Any) -> None:
'''
Create or modify, if already exists, a private static attribute in a class.
:param name: Name of the static attribute.
:param value: Value of the static attribute.
:param return: None
'''
static_private_attrs[name] = value
@staticmethod
def del_static_private_attr(name: str) -> None:
'''
Remove a private static attribute from a class.
This method always returns None, even if the attribute doesn't exist.
:param name: Name of the static attribute to remove.
'''
static_private_attrs.pop(name, None)
@staticmethod
def getstate(instance: object) -> Tuple[int, Dict[str, Any], Dict[str, Any]]:
uid, private = id(instance), private_attrs[id(instance)]
return uid, private, static_private_attrs
@staticmethod
def setstate(instance: object, state: Tuple[int, Dict[str, Any], Dict[str, Any]]):
uid, private, static = state
private_attrs[id(instance)] = private_attrs[uid] if uid in private_attrs.keys() else private
for key in static.keys():
if key not in static_private_attrs.keys():
static_private_attrs[key] = static[key]
PrivateAttrs.__qualname__ = 'PrivateAttrs'
return PrivateAttrs()