@@ -150,20 +150,17 @@ Implementing Flyweights
150
150
=======================
151
151
152
152
The simplest flyweights are allocated ahead of time.
153
- A simple system for assigning letter grades
153
+ A system for assigning letter grades
154
154
might use flyweights for the grades themselves:
155
155
156
156
.. testcode ::
157
157
158
- class Grade(object):
159
- def __init__(self, minimum, maximum, name):
160
- self.value = value
161
-
162
- _grades = [letter + suffix for letter in 'ABCDF'
163
- for suffix in ('+', '', '-')]
158
+ _grades = [letter + suffix
159
+ for letter in 'ABCD'
160
+ for suffix in ('+', '', '-')] + ['F']
164
161
165
162
def compute_grade(percent):
166
- percent = max(50 , min(99, percent))
163
+ percent = max(59 , min(99, percent))
167
164
return _grades[(99 - percent) * 3 // 10]
168
165
169
166
print(compute_grade(55))
@@ -179,9 +176,8 @@ might use flyweights for the grades themselves:
179
176
Factories that need to build a flyweight population dynamically
180
177
are more complicated:
181
178
they’ll need a dynamic data structure
182
- in which to enroll the flyweights
183
- so they can find them again.
184
- A dictionary is the typical choice:
179
+ in which to enroll the flyweights and find them again later.
180
+ A dictionary is a typical choice:
185
181
186
182
.. testcode ::
187
183
@@ -203,8 +199,94 @@ A dictionary is the typical choice:
203
199
False
204
200
True
205
201
206
- should you keep them all?
202
+ One danger of dynamically allocated flyweights
203
+ is the possibility of eventually exhausting memory,
204
+ if the number of possible values is very large
205
+ and callers might request a large number of unique values
206
+ over a program’s runtime.
207
+ In such cases you might consider using a |WeakValueDictionary |
208
+ from the ``weakref `` module.
209
+
210
+ .. |WeakValueDictionary | replace :: ``WeakValueDictionary ``
211
+ .. _WeakValueDictionary : https://docs.python.org/3/library/weakref.html#weakref.WeakValueDictionary
212
+
213
+ Weak references wouldn’t work in the simple example given above,
214
+ because ``my_intern `` uses each interned string
215
+ not only as a value but also as the corresponding key.
216
+ But it should work fine in the more common case
217
+ where the indexes are simple values
218
+ and the keys more complicated object instances.
219
+
220
+ The Gang of Four define the Flyweight Pattern as using a factory function,
221
+ but Python provides another possibility:
222
+ a class can implement the pattern right in its constructor,
223
+ just like ``bool() `` and ``int() ``.
224
+ Rewriting the above example as a class —
225
+ and, for the sake of example, allocating objects on-demand
226
+ instead of building them ahead of time —
227
+ would produce something like:
228
+
229
+ .. testcode ::
230
+
231
+ class Grade(object):
232
+ _instances = {}
207
233
208
- weakref.WeakValueDictionary
234
+ def __new__(cls, percent):
235
+ percent = max(50, min(99, percent))
236
+ letter = 'FDCBA'[(percent - 50) // 10]
237
+ self = cls._instances.get(letter)
238
+ if self is None:
239
+ self = cls._instances[letter] = object.__new__(Grade)
240
+ self.letter = letter
241
+ return self
242
+
243
+ def __repr__(self):
244
+ return 'Grade {!r}'.format(self.letter)
245
+
246
+
247
+ print(Grade(55), Grade(85), Grade(95), Grade(100))
248
+ print(len(Grade._instances))
249
+ print(Grade(95) is Grade(100))
250
+ print(len(Grade._instances))
251
+
252
+ .. testoutput ::
209
253
210
- either with class __new__
254
+ Grade 'F' Grade 'B' Grade 'A' Grade 'A'
255
+ 3
256
+ True
257
+ 3
258
+
259
+ Once a ``Grade `` object for A has been created,
260
+ all further requests for it receive the same object;
261
+ the instances dictionary does not grow any further.
262
+
263
+ Note that we don’t define a ``__init__() `` method
264
+ in a class like this
265
+ whose ``__new__() `` might return an existing object.
266
+ That’s because Python always calls for initialization
267
+ on the object received back from ``__new__() ``
268
+ (as long as the object is an instance of the class itself),
269
+ which would be useful the first time we returned a new object
270
+ but redundant on all of the subsequent occasions
271
+ when we were simply returning it from the ``_instances `` cache.
272
+ So instead we simply do the work of initialization manually
273
+ right in the middle of ``__new__() ``::
274
+
275
+ self.letter = letter
276
+
277
+ .. TODO mention here “for the same reason as the Singleton” once it’s written
278
+
279
+ Having illustrated this possibility,
280
+ I recommend against it
281
+ because it produces code whose behavior does not match its spelling.
282
+ When a Python programmer sees ``Grade(95) ``,
283
+ they are going to think “new object instance”
284
+ along with all of the consequences,
285
+ unless they are in on the secret that ``__new__() `` has been overridden —
286
+ and even in that case, they might at some point forget
287
+ that the ``Grades `` class is special.
288
+
289
+ Whereas a factory ``get_grade_for_percent() ``
290
+ will be less likely to trigger assumptions
291
+ like “this call always builds a new object”
292
+ and in any case is simpler both to implement and debug.
0 commit comments