diff --git a/patterns/structural/flyweight__py3.py b/patterns/structural/flyweight__py3.py index af2e939c..53225fdb 100644 --- a/patterns/structural/flyweight__py3.py +++ b/patterns/structural/flyweight__py3.py @@ -19,6 +19,10 @@ *References: http://codesnipers.com/?q=python-flyweights +https://python-patterns.guide/gang-of-four/flyweight/ + +*Examples in Python ecosystem: +https://docs.python.org/3/library/sys.html#sys.intern *TL;DR80 Minimizes memory usage by sharing data with other similar objects. @@ -27,59 +31,27 @@ import weakref -class FlyweightMeta(type): - def __new__(mcs, name, parents, dct): - """ - Set up object pool - - :param name: class name - :param parents: class parents - :param dct: dict: includes class attributes, class methods, - static methods, etc - :return: new class - """ - dct['pool'] = weakref.WeakValueDictionary() - return super(FlyweightMeta, mcs).__new__(mcs, name, parents, dct) - - @staticmethod - def _serialize_params(cls, *args, **kwargs): - """ - Serialize input parameters to a key. - Simple implementation is just to serialize it as a string - """ - args_list = list(map(str, args)) - args_list.extend([str(kwargs), cls.__name__]) - key = ''.join(args_list) - return key - - def __call__(cls, *args, **kwargs): - key = FlyweightMeta._serialize_params(cls, *args, **kwargs) - pool = getattr(cls, 'pool', {}) - - instance = pool.get(key) - if instance is None: - instance = super(FlyweightMeta, cls).__call__(*args, **kwargs) - pool[key] = instance - return instance - - class Card(object): + """The Flyweight""" - """The object pool. Has builtin reference counting""" - - _CardPool = weakref.WeakValueDictionary() - - """Flyweight implementation. If the object exists in the - pool just return it (instead of creating a new one)""" + # Could be a simple dict. + # With WeakValueDictionary garbage collection can reclaim the object + # when there are no other references to it. + _pool = weakref.WeakValueDictionary() def __new__(cls, value, suit): - obj = Card._CardPool.get(value + suit) - if not obj: - obj = object.__new__(cls) - Card._CardPool[value + suit] = obj + # If the object exists in the pool - just return it + obj = cls._pool.get(value + suit) + # otherwise - create new one (and add it to the pool) + if obj is None: + obj = object.__new__(Card) + cls._pool[value + suit] = obj + # This row does the part we usually see in `__init__` obj.value, obj.suit = value, suit return obj + # If you uncomment `__init__` and comment-out `__new__` - + # Card becomes normal (non-flyweight). # def __init__(self, value, suit): # self.value, self.suit = value, suit @@ -87,49 +59,29 @@ def __repr__(self): return "" % (self.value, self.suit) -class Card2(metaclass=FlyweightMeta): - def __init__(self, *args, **kwargs): - # print('Init {}: {}'.format(self.__class__, (args, kwargs))) - pass - - -if __name__ == '__main__': - # comment __new__ and uncomment __init__ to see the difference - c1 = Card('9', 'h') - c2 = Card('9', 'h') - print(c1, c2) - print(c1 == c2, c1 is c2) - print(id(c1), id(c2)) - - c1.temp = None - c3 = Card('9', 'h') - print(hasattr(c3, 'temp')) - c1 = c2 = c3 = None - c3 = Card('9', 'h') - print(hasattr(c3, 'temp')) - - # Tests with metaclass - instances_pool = getattr(Card2, 'pool') - cm1 = Card2('10', 'h', a=1) - cm2 = Card2('10', 'h', a=1) - cm3 = Card2('10', 'h', a=2) - - assert (cm1 == cm2) and (cm1 != cm3) - assert (cm1 is cm2) and (cm1 is not cm3) - assert len(instances_pool) == 2 - - del cm1 - assert len(instances_pool) == 2 - - del cm2 - assert len(instances_pool) == 1 - - del cm3 - assert len(instances_pool) == 0 - -### OUTPUT ### -# (, ) -# (True, True) -# (31903856, 31903856) -# True -# False +def main(): + """ + >>> c1 = Card('9', 'h') + >>> c2 = Card('9', 'h') + >>> c1, c2 + (, ) + >>> c1 == c2 + True + >>> c1 is c2 + True + + >>> c1.new_attr = 'temp' + >>> c3 = Card('9', 'h') + >>> hasattr(c3, 'new_attr') + True + + >>> Card._pool.clear() + >>> c4 = Card('9', 'h') + >>> hasattr(c4, 'new_attr') + False + """ + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/patterns/structural/flyweight_with_metaclass__py3.py b/patterns/structural/flyweight_with_metaclass__py3.py new file mode 100644 index 00000000..33536b3d --- /dev/null +++ b/patterns/structural/flyweight_with_metaclass__py3.py @@ -0,0 +1,63 @@ +import weakref + + +class FlyweightMeta(type): + def __new__(mcs, name, parents, dct): + """ + Set up object pool + + :param name: class name + :param parents: class parents + :param dct: dict: includes class attributes, class methods, + static methods, etc + :return: new class + """ + dct['pool'] = weakref.WeakValueDictionary() + return super(FlyweightMeta, mcs).__new__(mcs, name, parents, dct) + + @staticmethod + def _serialize_params(cls, *args, **kwargs): + """ + Serialize input parameters to a key. + Simple implementation is just to serialize it as a string + """ + args_list = list(map(str, args)) + args_list.extend([str(kwargs), cls.__name__]) + key = ''.join(args_list) + return key + + def __call__(cls, *args, **kwargs): + key = FlyweightMeta._serialize_params(cls, *args, **kwargs) + pool = getattr(cls, 'pool', {}) + + instance = pool.get(key) + if instance is None: + instance = super(FlyweightMeta, cls).__call__(*args, **kwargs) + pool[key] = instance + return instance + + +class Card2(metaclass=FlyweightMeta): + def __init__(self, *args, **kwargs): + # print('Init {}: {}'.format(self.__class__, (args, kwargs))) + pass + + +if __name__ == '__main__': + instances_pool = getattr(Card2, 'pool') + cm1 = Card2('10', 'h', a=1) + cm2 = Card2('10', 'h', a=1) + cm3 = Card2('10', 'h', a=2) + + assert (cm1 == cm2) and (cm1 != cm3) + assert (cm1 is cm2) and (cm1 is not cm3) + assert len(instances_pool) == 2 + + del cm1 + assert len(instances_pool) == 2 + + del cm2 + assert len(instances_pool) == 1 + + del cm3 + assert len(instances_pool) == 0