import collections
def main():
region1 = Region(0, 0, 1, 1)
print(region1)
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)
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) として定義されているので
再帰的に呼び出されるだけになるし、引数の個数も合わない。
"""
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:
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):
return Class(**self.kwargs)
Class.Builder = Builder
return Class
return decorator
attributes = ('x1', 'y1', 'x2', 'y2', 'is_rectangle', 'is_line', 'is_dot')
@builder(*attributes)
@immutable(*attributes)
class Region(object):
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
is_line = width_0 != height_0
is_dot = width_0 & height_0
self = super_new_immutable(cls, x1, y1, x2, y2,
is_line, is_dot, is_rectangle)
return self
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()