This repository has been archived by the owner on Jan 2, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 32
/
prototype_class_decorator.py
257 lines (203 loc) · 7.35 KB
/
prototype_class_decorator.py
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
"""Prototype pattern
"""
from copy import deepcopy
class InstanceNotAvailable(Exception):
pass
def prototype(hash_function=id, auto_register=False, debug=False):
"""Implement the Prototype pattern on a class.
The decorated class gains the following methods:
- register
- unregister
- clone (@classmethod)
- identifier (@property)
- available_identifiers (@staticmethod)
Parameters
----------
hash_function : function
a function
auto_register : bool
if True, automatically add objects to instance pool at instantiation
debug : bool
if True, show some documentation while using this decorator
Returns
-------
inner : function
"""
def inner(klass):
instance_pool = dict()
class Decorated(klass):
def __init__(self, *args, **kwargs):
"""Call __init__ of original class and assign an identifier.
Parameters
----------
args : tuple
args to pass to the __init__ of the original class
kwargs : dict
kwarg to pass to the __init__ of the original class
"""
klass.__init__(self, *args, **kwargs)
self._identifier = hash_function(self)
def __repr__(self, *args, **kwargs):
klass_repr = klass.__repr__(self, *args, **kwargs)
return "{} (id: {})".format(klass_repr, self.identifier)
def register(self):
"""Add this instance to the instance pool."""
instance_pool.update({self.identifier: self})
if debug:
print("{} registered".format(self.identifier))
def unregister(self):
"""Remove this instance from the instance pool."""
if debug:
print("{} unregistered".format(self.identifier))
del instance_pool[self.identifier]
@property
def identifier(self):
"""Return the identifier of this instance."""
return self._identifier
@identifier.setter
def identifier(self, value):
self._identifier = value
Decorated.__name__ = klass.__name__
class ClassObject:
def __repr__(self):
return klass.__name__
__str__ = __repr__
def __call__(self, *args, **kwargs):
if debug:
print("{}.__call__ (prototype)".format(str(self)))
decorated_instance = Decorated(*args, **kwargs)
if auto_register:
decorated_instance.register()
return decorated_instance
@classmethod
def clone(cls, identifier):
"""Get an instance from the pool and return it to the caller.
Parameters
----------
identifier : int
identifier for an instance
Raises
------
InstanceNotAvailable
if the instance is not available in the instance pool
Returns
-------
cloned_obj : decorated class
instance of the decorated class
"""
try:
original_object = instance_pool[identifier]
cloned_obj = deepcopy(original_object)
return cloned_obj
except KeyError:
raise InstanceNotAvailable(
"Instance with identifier {} not found.\nWas it "
"registered?\nThe available identifiers are: {}".format(
identifier, cls.available_identifiers()
)
)
@staticmethod
def available_identifiers():
"""Return the identifiers stored in the instance pool.
Returns
-------
list
identifiers of all instances available in instance pool.
"""
return list(instance_pool.keys())
return ClassObject()
return inner
@prototype(hash_function=id)
class Point(object):
def __init__(self, x, y):
print("{}__init__ (original class)".format(self.__class__.__name__))
self.x = x
self.y = y
def __repr__(self):
return "{}({}, {})".format(self.__class__.__name__, self.x, self.y)
def move(self, x, y):
self.x += x
self.y += y
# TODO: how can we inherit from Point?
# we will decorate this class later
class Stuff(object):
pass
class MoreStuff(Stuff):
pass
def main():
print("\nCreate 2 points")
print("p1")
p1 = Point(x=3, y=5)
print(p1)
print("p2")
p2 = Point(x=100, y=150)
print(p2)
print("p1.identifier != p2.identifier")
assert p1.identifier != p2.identifier
print("\nIdentifiers in the instance pool")
print(Point.available_identifiers())
print(
"\nThe instance pool is empty because we didn't register any "
"instance. Let's fix this"
)
p1.register()
p2.register()
print(Point.available_identifiers())
print("\nCreate a point by cloning p1 (__init__ is not called)")
p3 = Point.clone(p1.identifier)
print(p3)
print("Create a point by cloning p3 (which is a clone of p1)")
p4 = Point.clone(p3.identifier)
print(p4)
print("p1.identifier == p3.identifier == p4.identifier")
assert p1.identifier == p3.identifier == p4.identifier
print("\nIdentifiers in the instance pool")
print(Point.available_identifiers())
print("\nmove p1")
p1.move(5, 7)
print(p1)
# p3 and p4 are not weak references of p1, they are deep copies
print("if p1 moves, p3 and p4 are unaffected")
print(p3)
print(p4)
print("\nunregister p1")
p1.unregister()
print("p1 cannot be cloned because it was unregistered")
try:
Point.clone(p1.identifier)
except InstanceNotAvailable as e:
print(e)
print("but p1 still exists")
print(p1)
# TODO: this behavior might be undesirable
print(
"\nEven if we destroy p2, it's not removed from the instance pool, "
"so if we know the identifier we can still clone it"
)
identifier = deepcopy(p2.identifier)
del p2
print(Point.available_identifiers())
Point.clone(identifier)
print("\nwith a wrong identifier we get a ValueError exception")
wrong_identifier = 123456789
try:
Point.clone(wrong_identifier)
except InstanceNotAvailable as e:
print(e)
print("\nDecorate a new class")
proto = prototype(auto_register=True, debug=True)
StuffDecorated = proto(Stuff)
s1 = StuffDecorated()
StuffDecorated.clone(s1.identifier)
print("\nInstance pools are different for each class")
print("StuffDecorated.available_identifiers")
print(StuffDecorated.available_identifiers())
print("Point.available_identifiers")
print(Point.available_identifiers())
proto = prototype(auto_register=True)
MoreStuffDecorated = proto(MoreStuff)
MoreStuffDecorated()
print("MoreStuffDecorated.available_identifiers")
print(MoreStuffDecorated.available_identifiers())
if __name__ == "__main__":
main()