Help us understand the problem. What is going on with this article?

ちょっといい感じのdictと、その活用方法

Pythonの基本クラスの一つにdictがあります。
Pythonだけでなく、JavaならMap、JavaScriptならobject、Perlなら連想配列として、
大抵の言語には備わっている、キーと値を紐付けて格納する定番のコンテナ型です。

JSONとの親和性の高さから、ちょっとしたパラメタを保存するのに活用している方も少なくないのではないでしょうか?

ちょっと不便なdict

そんなこんなで、出番の多いdictですが、

params = dict(iterations=10000, threshold=0.8, foo='hoge', bar='fuga')

params['iterations']
params.get('iterations')

Pythonの場合、格納された値を参照するためには、['key']とか.get('key')とか記述しなければならず、
括弧とクォートの両方が必要とか割とダルいし、見た目的にも優雅さに欠ける感じがします。

もっとスッキリした構文で参照したくなりませんか?
例えば、JavaScriptのように…

ちょっといい感じのdict

では、さっそく作ってみましょう。

class Map(dict):
    __getattr__ = dict.__getitem__

はい、できました(早)。
今回は参照しか使わないので__getattr__しか定義していませんが、
必要であれば__setattr____delattr__も同じ様に定義してください。

Mapインスタンスの作り方は、(dictを継承しているので当然)dict()と同じです。
値の参照は属性名をドットでつなげて書くだけなので、使い勝手も見た目もシンプルです。

params = Map(iterations=10000, threshold=0.8, foo='hoge', bar='fuga')

params.iterations
# => 10000

いい感じの活用方法

では、Json形式で保存されたパラメタを読み込み、Mapインスタンスに格納してみましょう。
json.loads()の引数object_hookにdictの代わりに使用するクラスを指定します。

import json
params = json.loads('{"iterations": 10000, "one": {"two": {"three": {"four": "five!"}}}}',
                    object_hook=Map)

params.iterations
# => 10000
params.one.two.three.four
# => "five!"

最上位のコンテナだけでなく、入れ子になったコンテナもMapオブジェクトで格納されているので、
属性名をドットでつなぐだけで、一番内側の要素まで一気に参照可能です。
もし、これがdictだったら、

params['one']['two']['three']['four']

のように記述しなければならず、腱鞘炎は避け得ないところでした。

(もっと)いい感じの活用方法

簡単な設定を保存するのにJson形式はシンプルで良い選択肢だと思いますが、

  • コメントが書けない
  • 入れ子になると読みづらい&書きづらい
  • 最後の要素の後ろにコンマを置けないので、行コピペでイラッとする
  • キーをダブルクォートで囲まないといけないので、やっぱり腱鞘炎

など困ることが多いので、最近の私のオススメはToml形式です。

Toml形式から読み込んだ場合も、Mapインスタンスに格納してみましょう。
pip install tomlで、https://pypi.org/project/toml/ をインストールします。
toml.loads()を使い、引数_dictにMapクラスを指定します。

import toml
book = toml.loads('''
title="吾輩は猫である"
[publish]
date=1905
[author]
name="夏目漱石"
birth=1867
''', _dict=Map)

book.title
# => '吾輩は猫である'
book.author.name
# => '夏目漱石'

(さらにもっと)いい感じの活用方法

attrgetter()という関数を知っていますか?
「オブジェクトから属性名で値を取り出す関数」を生成する関数です。
これを、先程のMapインスタンスbookに適用してみます。

from operator import attrgetter
extractor = attrgetter('title', 'author.name', 'publish.date')
extractor(book)
# => ('吾輩は猫である', '夏目漱石', 1905)

なんと1、階層の違いにも関わらず、
イッパツでフラットに取得することができました!(これは便利かもしれん)

属性名ではなく添字(['key'])で値を取り出すitemgetter()という関数もありますが、
「ちょっと不便なdict」では、こうはいきませんね。

from operator import itemgetter
extractor = itemgetter('title', 'author.name', 'publish.date')
extractor(book)  # => KeyError: 'author.name'

  1. attrgetter()のドット記法は正式に対応している機能のようです。https://docs.python.org/ja/3/library/operator.html?highlight=attrgetter#operator.attrgetter 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした