Change the Flyweight pattern to Pythonic (?) (2)

As with previous, a memo when interpreting the article here.

Mixin (class method) with inheritance

In the original text, it is called Mixin, but honestly, it doesn't come out. Should we think of it as an inheritance-only class that provides a certain function (method) and does not use it alone (= does not instantiate)?

The code itself is almost the same as [Generalization of factory using variadic arguments](http://qiita.com/FGtatsuro/items/9e45ee7a0e4165004dc0#Generalization of factory using variadic arguments). The differences are as follows.

--The functionality provided by FlyweightFactory is used from the Hoge / Piyo class by inheritance. --Use classes as well as variadic arguments as dictionary keys

python


class FlyweightMixin(object):

    _instances = {}

    @classmethod    
    def get_instance(cls, *args, **kwargs):
		# _instance is a class variable and is reused by all classes that inherit from this class.
        #argument(*args, **kwargs)Classes inherited as dictionary keys to distinguish between instances with the same but different classes(cls)Must also be used.
        return cls._instances.setdefault((cls, args, tuple(kwargs.items())), cls(*args, **kwargs))

class Hoge(FlyweightMixin):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

class Piyo(FlyweightMixin):
    def __init__(self, args1, kwargs1):
        self.args1 = args1
        self.kwargs1 = kwargs1


assert Hoge.get_instance(1, kwargs1=2) is Hoge.get_instance(1, kwargs1=2)
assert Hoge.get_instance(1, kwargs1=2) is not Hoge.get_instance(1, kwargs1=3)
assert Piyo.get_instance('a', kwargs1='b') is Piyo.get_instance('a', kwargs1='b')
assert Piyo.get_instance('a', kwargs1='b') is not Piyo.get_instance('a', kwargs1='c')

#Cases with the same arguments but different classes
#Class inherited as a dictionary key(cls)If you are not using, this assertion will fail.
assert Hoge.get_instance('a', kwargs1='b') is not Piyo.get_instance('a', kwargs1='b')

Mixin with inheritance (__new__ method)

In the previous example, "The FlyweightMixin class is inheritance only ", but if you want to create it, you can create an instance of the FlyweightMixin class by doingFlyweightMixin (). In the following example, the process is transferred to the __new__ method that is always called when creating an instance, and an exception is thrown in the __init__ method that is called after the __new__ method. Calling FlyweightMixin () throws an exception, so in order to use the FlyweightMixin class, you must always inherit and override the __init __ method in your subclass.

python


class FlyweightMixin(object):

    _instances = {}

    def __init__(self, *args, **kwargs):
        raise NotImplementedError

    def __new__(cls, *args, **kwargs):
        # Python2,Works with Python 3
        instance = super(type(cls), cls).__new__(cls)
        #Only Python 3 works:Python2 does not support calling super functions with arguments omitted
        # instance = super().__new__(cls)
        #Only Python2 works:Python3 is object.__new__Cannot accept variadic arguments(I don't know why)
        # instance = super(type(cls), cls).__new__(cls, *args, **kwargs)
        return cls._instances.setdefault((cls, args, tuple(kwargs.items())), instance)

class Hoge(FlyweightMixin):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

class Piyo(FlyweightMixin):
    def __init__(self, args1, kwargs1):
        self.args1 = args1
        self.kwargs1 = kwargs1

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') is Piyo('a', kwargs1='b')
assert Piyo('a', kwargs1='b') is not Piyo('a', kwargs1='c')
assert Hoge('a', kwargs1='b') is not Piyo('a', kwargs1='b')

(Supplement) Return value of __new__ method

Normally, the __new__ method returns a new instance of the class given in the first argument. On the other hand, the __new__ method of FlyweightMixin saves the created instance in the _instances dictionary, and returns the saved instance from the second time onward.

Mixin with decorator

In the following example, the logic of the Flyweight pattern that was given to the parent class (FlyweightMixin) in the method using inheritance is given to the child class side using the decorator. The flyweight function substitutes the _instances variable (dictionary) and the __new__ method (function object that defines the logic of the Flyweight pattern) for the class object taken as an argument and returns it.

python


#If you do not make it a class method by the classmethod function, an error will occur in Python2.
# (ref.) 
# http://chikatoike.hatenadiary.jp/entry/2013/07/31/125624
# http://momijiame.tumblr.com/post/67251294770/pythone
# https://docs.python.org/3/whatsnew/3.0.html#operators-and-special-methods
# (2015/02/27 Addendum)
#The staticmethod function was fine too. Looking at the document__new__The static method is more appropriate to put in?
# (ref.) https://docs.python.org/3.3/reference/datamodel.html#object.__new__
@classmethod
def _get_instance(cls, *args, **kwargs):
    instance = super(type(cls), cls).__new__(cls)
    #Each class is a dictionary(cls._instances)Eliminates the need to include a class object in the key
    return cls._instances.setdefault((args, tuple(kwargs.items())), instance)


def flyweight(cls):
    #The function object assigned to the attribute of the class object is
    # Python2: unbound method(=You must specify the instance to bind on call)
    # __new__Is implicitly called when instantiating
    #What is passed is a class object(cls)And an instance of that class(self)is not
    # Python3:Remain as a function object
    cls._instances = {}
    print(_get_instance)
    cls.__new__ = _get_instance
    print(cls.__new__)
    return cls

# Hoge = flyweight(Hoge)Equivalent to
@flyweight
class Hoge(object):
    def __init__(self, args1, kwargs1='test1'):
        self.args1 = args1
        self.kwargs1 = kwargs1

# Piyo = flyweight(Piyo)Equivalent to
@flyweight
class Piyo(object):
    def __init__(self, args1, kwargs1):
        self.args1 = args1
        self.kwargs1 = kwargs1

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') is Piyo('a', kwargs1='b')
assert Piyo('a', kwargs1='b') is not Piyo('a', kwargs1='c')
assert Hoge('a', kwargs1='b') is not Piyo('a', kwargs1='b')

Recommended Posts

Change the Flyweight pattern to Pythonic (?) (3)
Change the Flyweight pattern to Pythonic (?) (2)
Change the Flyweight pattern to Pythonic (?) (1)
[Python] Change the alphabet to numbers
Learn the design pattern "Flyweight" in Python
Script to change the description of fasta
[Python] How to change the date format (display format)
Change the decimal point of logging from, to.
Flyweight pattern in Java
The road to Pythonista
About the Visitor pattern
The road to Djangoist
[Python] Change the Cache-Control of the object uploaded to Cloud Storage
Change the standard output destination to a file in Python
An introduction to object orientation-let's change the internal state of an object
I want to change the Japanese flag to the Palau flag with Numpy
Change the volume of Pepper according to the surrounding environment (sound)
Change the message displayed when logging in to Raspberry Pi
Change the installation destination when --user is added to pip
How to change Jupyter layout
Dot according to the image
The road to download Matplotlib
Change the theme of Jupyter
Change the style of matplotlib
How to change Python version
How to use the decorator
How to increase the axis
How to start the program
How to change the log level of Azure SDK for Python
How to change the color of just the button pressed in Tkinter
Change the Y-axis scale of Matplotlib to exponential notation (10 Nth power notation)
Feel free to change the label of the legend in Seaborn in python
[Go] Create a CLI command to change the extension of the image
Change the bash prompt to a simple color for easy viewing
I summarized how to change the boot parameters of GRUB and GRUB2
Change the active version in Pyenv from anaconda to plain Python