Python小技: 辞書をプロパティ風にアクセス可能にする機能的ラッパー

foo=dict(bar=42)がある場合、いちいちfoo['bar']など書かず、JavaScriptのようにfoo.barと書きたい。
pythonはこういうことを簡単にさせてくれる:


class dotdict(dict):
__getattr__ = dict.__getitem_
http://odiak.net/blog/post/1618
あるいは

class Struct:
def __init__(self, **entries): self.__dict__.update(entries)
http://norvig.com/python-iaq.html

でも上記のやりかただと、この機能は一つの辞書で終ってしまい、オブジェクトグラフをdot表記で辿ることはできない。
foo=dict(bar=dict(baz=42))とある場合、foo.bar.bazとアクセスするにはもうちょっと工夫が必要だ。そこで:


class DotAccessible(object):
"""オブジェクトグラフ内の辞書要素をプロパティ風にアクセスすることを可能にするラッパー。
DotAccessible( { 'foo' : 42 } ).foo==42

メンバーを帰納的にワップすることによりこの挙動を下層オブジェクトにも与える。
DotAccessible( { 'lst' : [ { 'foo' : 42 } ] } ).lst[0].foo==42
"""

def __init__(self, obj):
self.obj=obj

def __repr__(self):
return "DotAccessible(%s)" % repr(self.obj)

def __getitem__(self, i):
"""リストメンバーをラップ"""
return self.wrap(self.obj[i])

def __getslice__(self, i, j):
"""リストメンバーをラップ"""

return map(self.wrap, self.obj.__getslice__(i,j))

def __getattr__(self, key):
"""辞書メンバーをプロパティとしてアクセス可能にする。
辞書キーと同じ名のプロパティはアクセス不可になる。
"""

if isinstance(self.obj, dict):
try:
v=self.obj[key]
except KeyError:
v=self.obj.__getattribute__(key)
else:
v=self.obj.__getattribute__(key)

return self.wrap(v)

def wrap(self, v):
"""要素をラップするためのヘルパー"""

if isinstance(v, (dict,list,tuple)): # xx add set
return self.__class__(v)
return v

if __name__=='__main__':

これだと芋蔓式に.accessができて便利だ。

辞書の中にリストがあってその中にまた辞書があったりするオブジェクト


da=DotAccessible(dict(foo=42,
dct=dict(foo=42),
lst=[dict(foo=42)],
tpl=[dict(foo=42)]))
print da
DotAccessible({'lst': [{'foo': 42}],
'tpl': [{'foo': 42}],
'bar': {'dct': 89},
'foo': 42})

辞書要素をプロパティとしてアクセス


assert da.foo==42

もともとあるプロパティは以前のようにアクセスできる


print 'items:', da.items()

ラップされたオブジェクト下の辞書もラップされている…


assert isinstance(da.dct, DotAccessible)

これにより連鎖的に.アクセスが可能になる


assert da.dct.foo==42

リストもラップされている


assert isinstance(da.lst, DotAccessible)

ので、リスト内の辞書要素も.アクセスが可能。


da.lst[0].foo==42
da.lst[:3][0].foo==42

tupleも同じく。


da.tpl[0].foo==42

doc stringの実例のテスト


assert DotAccessible( { 'lst' : [ { 'foo' : 42 } ] } ).lst[0].foo==42
assert DotAccessible( { 'foo' : 42 } ).foo==42

ユーザ定義クラスでも


class Foo(object):
def __init__(self):
self.dct=dict(foo=42)

assert DotAccessible(Foo()).dct.foo==42

dictのサブクラスを使うことを強制されないので、コントロール外のコードから来た辞書でも.アクセスができるようになるのがラッパーの利点だ。ラップして思う存分.アクセスした後はまた元のオブジェクトを関数にわたしたり返したりできるので、__getattr__などをオーバライドしていることによる弊害も.アクセスを使いたい自分のコードだけに限定できて安全だ。

何でも.アクセスできるようになってpythonを使っていて唯一javascriptが羨ましくなる問題が解消できた。