Skip to content

Commit fc3d072

Browse files
authored
Merge pull request faif#291 from gyermolenko/refactor_flyweight_pattern
Update flyweight pattern
2 parents c960478 + 76bc024 commit fc3d072

File tree

2 files changed

+107
-92
lines changed

2 files changed

+107
-92
lines changed

patterns/structural/flyweight__py3.py

Lines changed: 44 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
2020
*References:
2121
http://codesnipers.com/?q=python-flyweights
22+
https://python-patterns.guide/gang-of-four/flyweight/
23+
24+
*Examples in Python ecosystem:
25+
https://docs.python.org/3/library/sys.html#sys.intern
2226
2327
*TL;DR80
2428
Minimizes memory usage by sharing data with other similar objects.
@@ -27,109 +31,57 @@
2731
import weakref
2832

2933

30-
class FlyweightMeta(type):
31-
def __new__(mcs, name, parents, dct):
32-
"""
33-
Set up object pool
34-
35-
:param name: class name
36-
:param parents: class parents
37-
:param dct: dict: includes class attributes, class methods,
38-
static methods, etc
39-
:return: new class
40-
"""
41-
dct['pool'] = weakref.WeakValueDictionary()
42-
return super(FlyweightMeta, mcs).__new__(mcs, name, parents, dct)
43-
44-
@staticmethod
45-
def _serialize_params(cls, *args, **kwargs):
46-
"""
47-
Serialize input parameters to a key.
48-
Simple implementation is just to serialize it as a string
49-
"""
50-
args_list = list(map(str, args))
51-
args_list.extend([str(kwargs), cls.__name__])
52-
key = ''.join(args_list)
53-
return key
54-
55-
def __call__(cls, *args, **kwargs):
56-
key = FlyweightMeta._serialize_params(cls, *args, **kwargs)
57-
pool = getattr(cls, 'pool', {})
58-
59-
instance = pool.get(key)
60-
if instance is None:
61-
instance = super(FlyweightMeta, cls).__call__(*args, **kwargs)
62-
pool[key] = instance
63-
return instance
64-
65-
6634
class Card(object):
35+
"""The Flyweight"""
6736

68-
"""The object pool. Has builtin reference counting"""
69-
70-
_CardPool = weakref.WeakValueDictionary()
71-
72-
"""Flyweight implementation. If the object exists in the
73-
pool just return it (instead of creating a new one)"""
37+
# Could be a simple dict.
38+
# With WeakValueDictionary garbage collection can reclaim the object
39+
# when there are no other references to it.
40+
_pool = weakref.WeakValueDictionary()
7441

7542
def __new__(cls, value, suit):
76-
obj = Card._CardPool.get(value + suit)
77-
if not obj:
78-
obj = object.__new__(cls)
79-
Card._CardPool[value + suit] = obj
43+
# If the object exists in the pool - just return it
44+
obj = cls._pool.get(value + suit)
45+
# otherwise - create new one (and add it to the pool)
46+
if obj is None:
47+
obj = object.__new__(Card)
48+
cls._pool[value + suit] = obj
49+
# This row does the part we usually see in `__init__`
8050
obj.value, obj.suit = value, suit
8151
return obj
8252

53+
# If you uncomment `__init__` and comment-out `__new__` -
54+
# Card becomes normal (non-flyweight).
8355
# def __init__(self, value, suit):
8456
# self.value, self.suit = value, suit
8557

8658
def __repr__(self):
8759
return "<Card: %s%s>" % (self.value, self.suit)
8860

8961

90-
class Card2(metaclass=FlyweightMeta):
91-
def __init__(self, *args, **kwargs):
92-
# print('Init {}: {}'.format(self.__class__, (args, kwargs)))
93-
pass
94-
95-
96-
if __name__ == '__main__':
97-
# comment __new__ and uncomment __init__ to see the difference
98-
c1 = Card('9', 'h')
99-
c2 = Card('9', 'h')
100-
print(c1, c2)
101-
print(c1 == c2, c1 is c2)
102-
print(id(c1), id(c2))
103-
104-
c1.temp = None
105-
c3 = Card('9', 'h')
106-
print(hasattr(c3, 'temp'))
107-
c1 = c2 = c3 = None
108-
c3 = Card('9', 'h')
109-
print(hasattr(c3, 'temp'))
110-
111-
# Tests with metaclass
112-
instances_pool = getattr(Card2, 'pool')
113-
cm1 = Card2('10', 'h', a=1)
114-
cm2 = Card2('10', 'h', a=1)
115-
cm3 = Card2('10', 'h', a=2)
116-
117-
assert (cm1 == cm2) and (cm1 != cm3)
118-
assert (cm1 is cm2) and (cm1 is not cm3)
119-
assert len(instances_pool) == 2
120-
121-
del cm1
122-
assert len(instances_pool) == 2
123-
124-
del cm2
125-
assert len(instances_pool) == 1
126-
127-
del cm3
128-
assert len(instances_pool) == 0
129-
130-
### OUTPUT ###
131-
# (<Card: 9h>, <Card: 9h>)
132-
# (True, True)
133-
# (31903856, 31903856)
134-
# True
135-
# False
62+
def main():
63+
"""
64+
>>> c1 = Card('9', 'h')
65+
>>> c2 = Card('9', 'h')
66+
>>> c1, c2
67+
(<Card: 9h>, <Card: 9h>)
68+
>>> c1 == c2
69+
True
70+
>>> c1 is c2
71+
True
72+
73+
>>> c1.new_attr = 'temp'
74+
>>> c3 = Card('9', 'h')
75+
>>> hasattr(c3, 'new_attr')
76+
True
77+
78+
>>> Card._pool.clear()
79+
>>> c4 = Card('9', 'h')
80+
>>> hasattr(c4, 'new_attr')
81+
False
82+
"""
83+
84+
85+
if __name__ == "__main__":
86+
import doctest
87+
doctest.testmod()
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import weakref
2+
3+
4+
class FlyweightMeta(type):
5+
def __new__(mcs, name, parents, dct):
6+
"""
7+
Set up object pool
8+
9+
:param name: class name
10+
:param parents: class parents
11+
:param dct: dict: includes class attributes, class methods,
12+
static methods, etc
13+
:return: new class
14+
"""
15+
dct['pool'] = weakref.WeakValueDictionary()
16+
return super(FlyweightMeta, mcs).__new__(mcs, name, parents, dct)
17+
18+
@staticmethod
19+
def _serialize_params(cls, *args, **kwargs):
20+
"""
21+
Serialize input parameters to a key.
22+
Simple implementation is just to serialize it as a string
23+
"""
24+
args_list = list(map(str, args))
25+
args_list.extend([str(kwargs), cls.__name__])
26+
key = ''.join(args_list)
27+
return key
28+
29+
def __call__(cls, *args, **kwargs):
30+
key = FlyweightMeta._serialize_params(cls, *args, **kwargs)
31+
pool = getattr(cls, 'pool', {})
32+
33+
instance = pool.get(key)
34+
if instance is None:
35+
instance = super(FlyweightMeta, cls).__call__(*args, **kwargs)
36+
pool[key] = instance
37+
return instance
38+
39+
40+
class Card2(metaclass=FlyweightMeta):
41+
def __init__(self, *args, **kwargs):
42+
# print('Init {}: {}'.format(self.__class__, (args, kwargs)))
43+
pass
44+
45+
46+
if __name__ == '__main__':
47+
instances_pool = getattr(Card2, 'pool')
48+
cm1 = Card2('10', 'h', a=1)
49+
cm2 = Card2('10', 'h', a=1)
50+
cm3 = Card2('10', 'h', a=2)
51+
52+
assert (cm1 == cm2) and (cm1 != cm3)
53+
assert (cm1 is cm2) and (cm1 is not cm3)
54+
assert len(instances_pool) == 2
55+
56+
del cm1
57+
assert len(instances_pool) == 2
58+
59+
del cm2
60+
assert len(instances_pool) == 1
61+
62+
del cm3
63+
assert len(instances_pool) == 0

0 commit comments

Comments
 (0)