|
19 | 19 |
|
20 | 20 | *References:
|
21 | 21 | 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 |
22 | 26 |
|
23 | 27 | *TL;DR80
|
24 | 28 | Minimizes memory usage by sharing data with other similar objects.
|
|
27 | 31 | import weakref
|
28 | 32 |
|
29 | 33 |
|
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 |
| - |
66 | 34 | class Card(object):
|
| 35 | + """The Flyweight""" |
67 | 36 |
|
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() |
74 | 41 |
|
75 | 42 | 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__` |
80 | 50 | obj.value, obj.suit = value, suit
|
81 | 51 | return obj
|
82 | 52 |
|
| 53 | + # If you uncomment `__init__` and comment-out `__new__` - |
| 54 | + # Card becomes normal (non-flyweight). |
83 | 55 | # def __init__(self, value, suit):
|
84 | 56 | # self.value, self.suit = value, suit
|
85 | 57 |
|
86 | 58 | def __repr__(self):
|
87 | 59 | return "<Card: %s%s>" % (self.value, self.suit)
|
88 | 60 |
|
89 | 61 |
|
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() |
0 commit comments