home changes contents help options

126:コレクションのような性質を持つクラスを作る

特殊メソッドを実装

自作のクラスに __getitem__(self, key) メソッドを定義すると、 組み込みのdict や list の様に myclass[egg]? で値を取り出せます。

class AnyIs1(object):
    def __getitem__(self, key):
        return 1

a = AnyIs1()
print a[0] # => 1
print a["aaaa"] # => 1
print a[range(10)] # => 1

次のような特殊メソッドがあります

詳細については リファレンスマニュアル 3.4 特殊メソッド名 を参照してください。

既存のコレクションに委譲する

コレクション風のクラスを作るときは、0から作るのではなく、 既存のコレクションを利用するのが一つの方法です。

class MultiDict(object):
    """ 辞書風のコレクション。一つのキーに複数の値を持つ。 """
    def __init__(self):
        self._dict = {} # <= 既存の辞書を属性に持つ
    
    def __setitem__(self, key, value):
        t = self._dict.get(key, ())
        self._dict[key] = t + (value,)
    
    # 新たに定義するのは __setitem__のみ
    # 他は全て self._dictに委譲するだけ
    
    def __getitem__(self, key):
        return self._dict[key]
    
    def __delitem__(self, key):
        del self._dict[key]
    
    def __iter__(self):
        return iter(self._dict)
    
    def __len__(self):
        return len(self._dict)
    
    def __contains__(self, item):
        return item in self._dict
        
    def __str__(self):
        return str(self._dict)

m = MultiDict()
m[1] = 1
m[1] = 2
m[1] = 3

m[2] = 5
m[2] = 4
m[2] = 3

print(1, m[1]) # => 1 (1, 2, 3)
print(2, m[2]) # => 2 (5, 4, 3)
print(m)       # => {1: (1, 2, 3), 2: (5, 4, 3)}

collections のクラスを継承する

特殊メソッドで定義されるのは、コレクションとしては最低限の機能です。 たとえば、組み込みdictには他にも .iteritems() .update() といったメソッドもあります。

もちろん、.iteritems() .update() を自分で定義する事も出来ます。 しかし、collectionsの抽象基底クラスを継承すると、 これらのメソッドを自動的に追加する事ができます。

class MultiDict(MutableMapping):
    """ 辞書風のコレクション。一つのキーに複数の値を持つ。 """
    def __init__(self):
        self._dict = {} # <= 既存の辞書を属性に持つ
    
    def __setitem__(self, key, value):
        t = self._dict.get(key, ())
        self._dict[key] = t + (value,)
    
    def __getitem__(self, key):
        return self._dict[key]
    
    def __delitem__(self, key):
        del self._dict[key]
    
    def __iter__(self):
        return iter(self._dict)
    
    def __len__(self):
        return len(self._dict)
    
    def __contains__(self, item):
        return item in self._dict
        
    def __str__(self):
        return str(self._dict)

m = MultiDict()
m[1] = 1
m[1] = 2
m[1] = 3
m[2] = 1
print m.keys() #=> [1, 2]
print m.items() #=> [(1, (1, 2, 3)), (2, (1,))]

MutableMapping? は dict風のクラスを作るための抽象基底クラスです。 他にもlist風のMutableSequence、set風のMutableSetがあります。 詳しくは`Python documentation collections`をご覧ください。

複数の抽象基底クラスを継承する事もできます。

class FrozenMap(Mapping, Hashable):
    """変更不可能、辞書の様に値を取り出せて、辞書のキーに使用できる"""
    def __init__(self, adict):
        self._dict = dict(adict)
        self._tuple = tuple(adict.iteritems())
    
    def __getitem__(self, key):
        return self._dict[key]
    
    def __iter__(self):
        return iter(self._dict)
    
    def __len__(self):
        return len(self._dict)
        
    def __eq__(self, other):
        return self._tuple == other._tuple
    
    def __hash__(self):
        return hash(self._tuple)

残念ながら、これらの抽象基底クラスは、Python 2.5以前では使えません。 2.5以前では、代わりにUserDict、UserListなどのモジュールを使用します。 詳しくは`ライブラリリファレンス 5.12 UserDict?`、 ライブラリリファレンス 5.13 UserList? をご覧ください。

組み込みのコレクションを継承する

組み込みのコレクションを継承して、新たなコレクションを作る事もできます。

class MultiDict(dict):
    """一つのキーに複数の値 (辞書を継承)"""
    def __setitem__(self, key, value):
        t = dict.get(self, key, ())
        dict.__setitem__(self, key, t + (value,))