A memo that interprets the article taught by the person next to the side.
One factory is defined for one target. The code below is pretty much the same as the original article, with changes to give named arguments.
python
class Hoge(object):
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
class HogeFactory(object):
def __init__(self):
self._instances = {}
def get_instance(self, args1, kwargs1):
if ((args1), (kwargs1)) not in self._instances:
self._instances[((args1), (kwargs1))] = Hoge(args1, kwargs1=kwargs1)
return self._instances[((args1), (kwargs1))]
class Piyo(object):
def __init__(self, args1, kwargs1, kwargs2):
self.args1 = args1
self.kwargs1 = kwargs1
self.kwargs2 = kwargs2
class PiyoFactory(object):
def __init__(self):
self._instances = {}
def get_instance(self, args1, kwargs1='test1', kwargs2='test2'):
if ((args1), (kwargs1, kwargs2)) not in self._instances:
self._instances[((args1), (kwargs1, kwargs2))] = Piyo(args1, kwargs1=kwargs1, kwargs2=kwargs2)
return self._instances[((args1), (kwargs1, kwargs2))]
hogeFactory = HogeFactory()
piyoFactory = PiyoFactory()
assert hogeFactory.get_instance(1, kwargs1=2) is hogeFactory.get_instance(1, kwargs1=2)
assert hogeFactory.get_instance(1, kwargs1=2) is not hogeFactory.get_instance(1, kwargs1=3)
assert piyoFactory.get_instance('a', kwargs1='b', kwargs2='c') is piyoFactory.get_instance('a', kwargs1='b', kwargs2='c')
assert piyoFactory.get_instance('a', kwargs1='b', kwargs2='c') is not piyoFactory.get_instance('a', kwargs1='b', kwargs2='d')
The reason why one factory was needed for one target was that each target's constructor had a different signature = get_instance
method had a different signature.
You can use Python's variadic arguments to absorb differences in signatures.
python
class FlyweightFactory(object):
def __init__(self, cls):
self._instances = {}
self._cls = cls
def get_instance(self, *args, **kwargs):
return self._instances.setdefault((args, tuple(kwargs.items())), self._cls(*args, **kwargs))
class Hoge(object):
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
class Piyo(object):
def __init__(self, args1, kwargs1, kwargs2):
self.args1 = args1
self.kwargs1 = kwargs1
self.kwargs2 = kwargs2
hogeFactory = FlyweightFactory(Hoge)
piyoFactory = FlyweightFactory(Piyo)
assert hogeFactory.get_instance(1, kwargs1=2) is hogeFactory.get_instance(1, kwargs1=2)
assert hogeFactory.get_instance(1, kwargs1=2) is not hogeFactory.get_instance(1, kwargs1=3)
assert piyoFactory.get_instance('a', kwargs1='b', kwargs2='c') is piyoFactory.get_instance('a', kwargs1='b', kwargs2='c')
assert piyoFactory.get_instance('a', kwargs1='b', kwargs2='c') is not piyoFactory.get_instance('a', kwargs1='b', kwargs2='d')
You can write it even simpler by decorating the above FlyweightFactory
.
python
class Flyweight(object):
def __init__(self, cls):
self._instances = {}
self._cls = cls
def __call__(self, *args, **kwargs):
return self._instances.setdefault((args, tuple(kwargs.items())), self._cls(*args, **kwargs))
@Flyweight
class Hoge(object):
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
@Flyweight
class Piyo(object):
def __init__(self, args1, kwargs1, kwargs2):
self.args1 = args1
self.kwargs1 = kwargs1
self.kwargs2 = kwargs2
assert Hoge(1, kwargs1=2) is Hoge(1, kwargs1=2)
assert Hoge(1, kwargs1=2) is not Hoge(1, kwargs1=3)
assert Piyo('a', kwargs1='b', kwargs2='c') is Piyo('a', kwargs1='b', kwargs2='c')
assert Piyo('a', kwargs1='b', kwargs2='c') is not Piyo('a', kwargs1='b', kwargs2='d')
The decorator is called by taking the attached target as an argument. The code equivalent to this process can be written as follows without using a decorator.
Instead of calling get_instance
, there is a difference in reading the Flyweight instance as a function, but you can see that it is not so different from the previous snippet.
(ref. http://docs.python.jp/3.4/reference/datamodel.html#object.call)
python
class Flyweight(object):
def __init__(self, cls):
self._instances = {}
self._cls = cls
def __call__(self, *args, **kwargs):
return self._instances.setdefault((args, tuple(kwargs.items())), self._cls(*args, **kwargs))
class Hoge(object):
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
class Piyo(object):
def __init__(self, args1, kwargs1, kwargs2):
self.args1 = args1
self.kwargs1 = kwargs1
self.kwargs2 = kwargs2
# Hoge,Take Piyo as an argument,Create a Flyweight instance.
Hoge = Flyweight(Hoge)
Piyo = Flyweight(Piyo)
assert Hoge(1, kwargs1=2) is Hoge(1, kwargs1=2)
assert Hoge(1, kwargs1=2) is not Hoge(1, kwargs1=3)
assert Piyo('a', kwargs1='b', kwargs2='c') is Piyo('a', kwargs1='b', kwargs2='c')
assert Piyo('a', kwargs1='b', kwargs2='c') is not Piyo('a', kwargs1='b', kwargs2='d')
Furthermore, the implementation using the following closures does not have to be a class as long as it is callable.
python
def flyweight(cls):
instances = {}
return lambda *args, **kwargs: instances.setdefault((args, tuple(kwargs.items())), cls(*args, **kwargs))
@flyweight
class Hoge(object):
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
@flyweight
class Piyo(object):
def __init__(self, args1, kwargs1, kwargs2):
self.args1 = args1
self.kwargs1 = kwargs1
self.kwargs2 = kwargs2
assert Hoge(1, kwargs1=2) is Hoge(1, kwargs1=2)
assert Hoge(1, kwargs1=2) is not Hoge(1, kwargs1=3)
assert Piyo('a', kwargs1='b', kwargs2='c') is Piyo('a', kwargs1='b', kwargs2='c')
assert Piyo('a', kwargs1='b', kwargs2='c') is not Piyo('a', kwargs1='b', kwargs2='d')
The equivalent code without a decorator looks like this:
python
def flyweight(cls):
instances = {}
return lambda *args, **kwargs: instances.setdefault((args, tuple(kwargs.items())), cls(*args, **kwargs))
class Hoge(object):
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
class Piyo(object):
def __init__(self, args1, kwargs1, kwargs2):
self.args1 = args1
self.kwargs1 = kwargs1
self.kwargs2 = kwargs2
Hoge = flyweight(Hoge)
Piyo = flyweight(Piyo)
assert Hoge(1, kwargs1=2) is Hoge(1, kwargs1=2)
assert Hoge(1, kwargs1=2) is not Hoge(1, kwargs1=3)
assert Piyo('a', kwargs1='b', kwargs2='c') is Piyo('a', kwargs1='b', kwargs2='c')
assert Piyo('a', kwargs1='b', kwargs2='c') is not Piyo('a', kwargs1='b', kwargs2='d')
This method is very smart, but it has the disadvantage that you can't create a subclass of the Hoge
/ Piyo
class.
This is because the Hoge
/ Piyo
is no longer a class, but a function object, as you can see in the code that doesn't use decorators. Therefore, the following code will result in an error.
python
def flyweight(cls):
instances = {}
return lambda *args, **kwargs: instances.setdefault((args, tuple(kwargs.items())), cls(*args, **kwargs))
@flyweight
class Hoge(object):
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
@flyweight
class Piyo(object):
def __init__(self, args1, kwargs1, kwargs2):
self.args1 = args1
self.kwargs1 = kwargs1
self.kwargs2 = kwargs2
#Hoge is no longer a class and cannot be inherited
class Hoge2(Hoge):
def __init__(self, args1, kwargs1='test1'):
super().__init__(args1, kwargs1=kwargs1)
In the sense that the decorator makes Hoge
/ Piyo
no longer a class, it doesn't change when the decorator is represented by a class, but there is an instance of the Flyweight
class that replaces the Hoge
/ Piyo
class. It is the miso that holds the original class.
Therefore, it can be subclassed with the following code.
python
class Flyweight(object):
def __init__(self, cls):
self._instances = {}
self._cls = cls
def __call__(self, *args, **kwargs):
return self._instances.setdefault((args, tuple(kwargs.items())), self._cls(*args, **kwargs))
@Flyweight
class Hoge(object):
def __init__(self, args1, kwargs1='test1'):
self.args1 = args1
self.kwargs1 = kwargs1
@Flyweight
class Piyo(object):
def __init__(self, args1, kwargs1, kwargs2):
self.args1 = args1
self.kwargs1 = kwargs1
self.kwargs2 = kwargs2
# Hoge(The entity is a Flyweight instance)Is an instance variable that holds the original class_Have cls
class Hoge2(Hoge._cls):
def __init__(self, args1, kwargs1='test1'):
super().__init__(args1, kwargs1=kwargs1)
Recommended Posts