metaclass で immutable
随分と久しぶりな投稿です。
Python 2.x の自作クラスを immutable にする件。
お手軽、かつ多くの記事を見かけるのが namedtuple を使う方法。
import collections Vector = collections.namedtuple('Vector', ('x', 'y')) Vector(0,0)
値を保持するだけのオブジェクトならばこれで十分。
immutable にするクラスの役割はデータ保持が主だから、これで足りる場合が殆どなのかと。
ただ、メソッドを追加したいとなったら?
そこで見つけたのは、tuple を継承する方法。
import operator class Vector(tuple): x = property(operator.itemgetter(0)) y = property(operator.itemgetter(1)) __slots__ = [] def __new__(cls, x, y): return tuple.__new__(cls, (x, y)) Vector(0,0)
メソッドを追加することができるようになりました。
同様な方法として type を使う方法。
import operator Vector = type( 'Vector', (tuple,), { 'x': property(operator.itemgetter(0)), 'y': property(operator.itemgetter(1)), '__slots__': [], '__new__': lambda cls, *args: tuple.__new__(cls, args), }) Vector(0,0)
tuple を継承する方法とやってることは同じだと思っています。
さらに metaclass を使った方法。
metaclass を学習がてら作ってみた、という程度のものなのですが、、、
import operator class ImmutableMeta(type): """immutable クラスを構築するためのメタクラス >>> class Vector(object): ... ... __metaclass__ = ImmutableMeta ... ... properties = 'x y' ... ... @classmethod ... def new(cls, x=0, y=0): ... return x, y >>> v = Vector(1,-1) >>> v (1, -1) >>> v.x 1 >>> v.y -1 >>> Vector() (0, 0) >>> Vector(y=2) (0, 2) """ def __new__(cls, cls_name, bases, attrs): attrs['__slots__'] = [] if 'properties' in attrs: properties = attrs['properties'].split() # property を追加 for i, name in enumerate(properties): attrs[name] = property(operator.itemgetter(i)) # __new__ を置き換え attrs['__new__'] = cls.new_method(attrs, properties) # tuple を継承していなければ、基底クラスに tuple を追加 if not list(c for c in bases if issubclass(c, tuple)): bases = (tuple, ) + bases return type.__new__(cls, cls_name, tuple(bases), attrs) @staticmethod def new_method(attrs, properties): def gen(new_func): def __new__(cls, *args, **kwargs): args = new_func(cls, *args, **kwargs) if len(args) != len(properties): raise TypeError( '__new__() takes {0} arguments ({1} given)'.format( len(properties), len(args))) return tuple.__new__(cls, args) return __new__ if 'new' in attrs: return gen(lambda cls, *args, **kwargs: cls.new(*args, **kwargs)) else: return gen(lambda cls, *args, **kwargs: tuple(args))
metaclass を使った Vector は随分とスッキリしました。
色々な書き方ができますね。
参考: