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'
-
attrgetter()
のドット記法は正式に対応している機能のようです。https://docs.python.org/ja/3/library/operator.html?highlight=attrgetter#operator.attrgetter ↩