7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Python で複雑な構造の dict オブジェクトをセレクタ的にアクセスできるようにする

Last updated at Posted at 2018-11-22

概要

JSON をロードしたときなど、配列とdictが何重にもネストしたような複雑な構造のdictオブジェクトができます。
このオブジェクトをアクセスしやすくする(とくにエラーまわりを解りやすくする)下記の機能を提供します。

  • (中間でも末端でも)キーがないときにフルパスで解りやすいエラー:
hoge = DataSelector(data)

hoge['aaa']['bbb']['ccc']

# ccc というキーは存在しないときのエラー
# path "ROOT.aaa.bbb.ccc" is not found!

# bbb というキーは存在しないときのエラー
# path "ROOT.aaa.bbb" is not found!

for v in hoge['aaa']['bbb']['ccc']:

# ccc が配列でないときのエラー
# path "ROOT.aaa.bbb.ccc" is not list!

for k, v in hoge['aaa']['bbb']['ccc'].items():

# ccc が dict でないときのエラー
# path "ROOT.aaa.bbb.ccc" is not dict!
  • 値にアクセスしやすくするセレクタ:
hoge = DataSelector(data)

# 値の取得
hoge['aaa']

# 複数階層も可
hoge['aaa.bbb']

# 値がない場合にデフォルト値(通常ならエラーが出る) ※ aaa がない場合でもデフォルト値
hoge.find('aaa.ccc', '')

# dict でループ
for k, v in hoge.items():
for k in hoge.keys():
for v in hoge.values():

# リストとしてループ
for v in hoge:

定義するクラス

class DataSelector:
    def __init__(self, data, parent_path='ROOT'):
        self.parent_path=parent_path
        self.data=data
    def __getitem__(self, path):
        return self.find(path)
    def _check_dict(self):
        if type(self.data) != dict:
            raise Exception(f'path "{self.parent_path}" is not dict!')
    def _check_list(self):
        if type(self.data) != list:
            raise Exception(f'path "{self.parent_path}" is not list!')
    def __iter__(self):
        self._check_list()
        for i, v in enumerate(self.data):
            yield DataSelector(v, f'{self.parent_path}[{i}]')
    def items(self):
        self._check_dict()
        for k, v in self.data.items():
            yield k, DataSelector(v, f'{self.parent_path}.{k}')
    def keys(self):
        self._check_dict()
        return self.data.keys()
    def values(self):
        self._check_dict()
        for k, v in self.data.items():
            yield DataSelector(v, f'{self.parent_path}.{k}')
    def find(self, path, default_val=None):
        paths = path.split(".") if type(path) == str else [path]
        _data = self.data
        _path = self.parent_path
        for p in paths:
            _path = f'{_path}.{p}'
            if p in _data:
                _data = _data[p]
            elif default_val is not None:
                _data = default_val
            else:
                raise Exception(f'path "{_path}" is not found!')
        if type(_data) == list or type(_data) == dict:
            return DataSelector(_data, _path)
        else:
            return _data

使い方

res = list()
root = DataSelector(data)
for k1, v1 in root.items():
    # もしも root が dict でないなら下記のエラーで教えてくれる
    # path "ROOT" is not dict!
    bbb = v1['aaa.bbb']
    # もしも aaa や bbb が存在しないなら下記のエラーで教えてくれる
    # path "ROOT.aaa" is not found!
    # path "ROOT.aaa.bbb" is not found!
    for v2 in bbb:
        # もしも bbb がリストでないなら下記のエラーで教えてくれる
        # path "ROOT.aaa.bbb" is not list!
        res.append(dict(eee=v2['ccc']))
        # もしも ccc が存在しないなら下記のエラーで教えてくれる
        # path "ROOT.aaa.bbb[0].ccc" is not found!
7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?