builder も作った..。

import collections


def main():

    # @immutable('x1', 'y1', 'x2', 'y2', 'is_line', 'is_dot')
    # class Region(object):
    region1 = Region(0, 0, 1, 1)
    print(region1)
    # def __init__ の代替もできてる。
    #   self.is_rectangle = value とはできない。self は immutable だから
    print(region1.is_rectangle)
    print(region1.is_line)
    print(region1.is_dot)

    region2 = Region(0, 0, 0, 0)
    print(region2)
    print(region2.is_rectangle)
    print(region2.is_line)
    print(region2.is_dot)

    # メソッドもちゃんと動作する。
    print(region1 == region2)

    # 代入はエラーになる。
    try:
        region1.x2 = 100
    except AttributeError as attribute_error:
        print(AttributeError, attribute_error)

    #
    # Builder パターンから生成
    #

    # @builder('x1', 'y1', 'x2', 'y2', 'is_line', 'is_dot')
    # @immutable('x1', 'y1', 'x2', 'y2', 'is_line', 'is_dot')
    # class Region(object):
    print(Region(1, 1, 2, 2))
    print(Region.Builder(1, 1, 2, 2).build())
    print(Region.Builder(y1=1, x1=1).x2(2).y2(2).build())


def immutable(*attributes):
    def decorator(ClassCallables):
        if ClassCallables.__bases__ != (object, ):
            raise Exception(
                'An inheritance is prohibited except object class, ' +
                'to avoid multiple inheritance.')
        # 属性だけのクラスを作成
        ClassImmutableAttributes \
            = collections.namedtuple(
                'ImmutableAttributes' + ClassCallables.__name__,
                attributes)
        # 属性だけのクラスを親にして継承する。
        # 合わせて子クラスの属性を受け取る。
        ImmutableClass \
            = type(
                'Immutable' + ClassCallables.__name__,
                (ClassImmutableAttributes, ),
                dict(ClassCallables.__dict__))
        return ImmutableClass
    return decorator


def super_new_immutable(ImmutableClass, *values):
    """Create immutable object.

    __new__ 関数の中で属性に代入する *values が整ったら
    この関数を呼び出してオブジェクトの生成と属性への代入を行う。

    1) オブジェクトの生成、属性への代入は親クラスの __new__ に担当してもらう。
        __init__ 関数の中で
        self.x1, self.y1, self.y2... = x1, y1 ,y2... とする代わりに
        親クラスの __new__ を呼び出して代入を行なっている。

        親クラスというのは immutable デコレータの中で
        ClassImmutableAttributes と記されたクラスである。

        ClassImmutableAttributes の __new__ 関数は
        def __new__(cls, x1, y1, x2, y2, is_line, is_dot) として定義されている。

    2) できないこと
        self = ImmutableClass(x1, y1, x2, y2, is_line, is_dot)
        return serl

        ImmutableClass の __new__ 関数が
        def __new__(cls, x1, y1, x2, y2) として定義されているので
        再帰的に呼び出されるだけになるし、引数の個数も合わない。
    """
    # super
    # https://docs.python.jp/3/library/functions.html#super
    return super().__new__(ImmutableClass, values)
    SuperClassImmutableAttributes = super(ImmutableClass.__bases__[0], ImmutableClass)
    self = SuperClassImmutableAttributes.__new__(ImmutableClass, values)
    return self

    """
    # another implementation
    ClassImmutableAttributes = ImmutableClass.__bases__[0]
    self = ClassImmutableAttributes.__new__(ImmutableClass, *values)
    return self
    """


def builder(*attributes):
    """Builder クラスを Class.Builder に設定します.

    Effective Java の Builder パターン
    http://promamo.com/?p=4176

    ###動作
    Class.Builder = Builder をしているだけ

    ###使い方
    @builder(*attributes)
    class Class:

    Class = bulder(*attributes)(Class)
    Class = decorator(Class)

    ###注意点 immutable デコレータと一緒に使うとき..
    OK
        @builder(*attributes)
        @immutable(*attributes)
        class Region(object):

    NG
        @immutable(*attributes)
        @builder(*attributes)
        class Region(object):

    ポイント
        immutable デコレータ 新しいクラスを返している。
        builder デコレータ クラスの属性にサブクラスを追加するだけ。

    理由
        Region* = builder(*attributes)(Region)
        Region* = decorator(Region)
        Region*

        Region*.Builder().build() メソッドの
            Class には Region が代入されいてる。

        Region** = immutable(*attributes)(Region*)
        Region** = decorator(Region*)
        Region**

        Region*.Builder().build() メソッドの
            Class には Region が代入されたまま。

    ###もし2度 attribute を宣言することが煩雑なら..
    somenamedtuple._fields を参照するようにすれば
    builder デコレータは attribute を宣言しなくてもよくなるかもしれない。
    immutable デコレーの宣言を削除はできない。NG にしないといけない。
    https://docs.python.jp/3/library/collections.html#collections.somenamedtuple._fields

    でも別々の機能を提供してるし、分けててもいかなと..

    """
    def decorator(Class):

        # 関数定義の処理を記述
        # クラスにサブクラスを追加する。
        class Builder(object):
            def __init__(self, *args, **kwargs):
                for attribute in attributes:
                    # 属性にメソッドを指定している。
                    # @immutable('x', 'y')
                    # class Sample:
                    #     pass
                    # sample = Sample().x(0).y(1)
                    setattr(self, attribute, self.setter(attribute))

                self.kwargs = kwargs
                for k, v in zip(attributes, args):
                    self.kwargs[k] = v

            def setter(self, key):
                def set_value(value):
                    self.kwargs[key] = value
                    return self
                return set_value

            def build(self):
                # こいつが..
                # Class -> Builder -> Class
                # ImmutableClass -> Builder -> Class
                return Class(**self.kwargs)

        Class.Builder = Builder

        return Class
    return decorator


attributes = ('x1', 'y1', 'x2', 'y2', 'is_rectangle', 'is_line', 'is_dot')


# ポイント1. 属性はデコレータに文字列で与える。
@builder(*attributes)
@immutable(*attributes)
class Region(object):
    # ポイント2. __init__ ではなく、__new__ を使う。
    #   何故なら __init__ だと immutable なオブジェクトが
    #   生成されてしまい代入できないから。
    def __new__(cls, x1, y1, x2, y2):
        """オブジェクトが生成される前に呼び出される."""
        width_0 = (x1 - x2 == 0)
        height_0 = (y1 - y2 == 0)

        is_rectangle = not width_0 and not height_0  # 0 0
        is_line = width_0 != height_0  # 0 1 or 1 0; xor
        is_dot = width_0 & height_0  # 1 1

        # ポイント3. 必要なオブジェクトが揃ったところで、オブジェクトを生成する。
        self = super_new_immutable(cls, x1, y1, x2, y2,
                                   is_line, is_dot, is_rectangle)

        # 注意: self の属性に値を代入すると immutable でなくなるのでしないこと。
        # self.valule = new_value

        # ポイント4. 生成したオブジェクトを return
        return self

    # def __init__(self, x1, y1, x2, y2):
    #    """オブジェクトが生成された後に呼び出される."""
    #    # self.x1 = x1 とは代入できない。self は immutable だから。
    #    pass

    def __iter__(self):
        return iter((self.x1, self.x2, self.y1, self.y2))

    def __eq__(self, other):
        return all([a == b for a, b in zip(self, other)])


if __name__ == '__main__':
    main()
Remove all ads