Pythonで辞書データをたくさん扱うコードを書いていると、辞書の要素をオブジェクトの属性みたいに参照したくなることがあります。
Javascriptなんかだと、辞書でも
data = {
'name': 'value'
}
alert(data.name)
のように、辞書.名前
で参照できますが、Pythonの場合は
data = {
'name': 'value'
}
print(data['name'])
となり、数が多いとちょっと面倒になります。
辞書をオブジェクト風にアクセスする手法としては、__getattr__()
を使ったカスタマイズがまず思いつきます。
class DictWrapper:
def __init__(self, d):
self.dict = d
def __getattr__(self, name):
return self.dict[name]
data = {
'name': 'value'
}
dictobj = DictWrapper(data)
print("data['name'] は", dictobj.name, "です")
しかし、この程度の実装だと、現実のアプリケーションではあんまり役に立ちません。普通、Webサービスなどで取得するようなデータは辞書の要素として他の辞書やリストなどを含んだ、複雑な構造になっています。たとえば、GithubのAPIで リポジトリのイベントを取得 すると、次のような辞書が返ってきます。
import requests
requests.get('https://api.github.com/repos/sojin-project/jashin/events', params={'per_page':1}).json()
この辞書を、先程のクラスでラップして見るとこんな感じになります。
data = requests.get('https://api.github.com/repos/sojin-project/jashin/events', params={'per_page':1}).json()
dictobj = DictWrapper(data[0])
print("id は", dictobj.id, "です")
print("created_at は", dictobj.created_at, "です")
print("actor は", dictobj.actor, "です")
dictobj.id
はいい感じに参照できていますが、dictobj.actor
i.itertmは別の辞書をそのまま持ってきてしまうので、あんまり便利ではありません。結局辞書を参照する羽目になるのが残念です。
また、この例のようにAPIとして定義されているような辞書は、要素の名前やデータ型がちゃんと分かるようにクラスを定義しておきたいものです。
ということで、辞書の要素を参照するクラスを定義する jashin.dictattr というモジュールを作成しました。jashin.dictattr
は、
pip3 install jashin
でインストールできます。
jashin.dictattr
を使うと、次のようにクラスを定義できます・
from jashin.dictattr import ItemAttr, DictModel
class GithubEvent(DictModel):
id = ItemAttr()
created_at = ItemAttr()
event = GithubEvent(data[0])
print("id は", event.id, "です")
print("created_at は", event.created_at, "です")
ItemAttr
でクラス属性を作成すると、同じ名前のアイテムを辞書から取得するようになっています。
辞書の値を参照・設定するときの変換関数も指定できるので、created_at
のような日付データを参照するときには、文字列データを datetime
型に変換するように指定できます。
from datetime import datetime, timezone
from dateutil.parser import parse as dateparse
def load_date(s: str) -> datetime:
return dateparse(s)
def dump_date(d: datetime) -> str:
return d.isoformat()
class GithubEvent(DictModel):
id = ItemAttr()
created_at = ItemAttr(load_date, dump_date)
event = GithubEvent(data[0])
print("created_at は", event.created_at, "です")
create_at
に値を設定すると、dump_date()
でdatetime
型を文字に変換した結果を辞書に格納します。
print("変更前:", data[0]['created_at'])
event.created_at = datetime(9999, 1, 1, tzinfo=timezone.utc)
print("変更前:", data[0]['created_at'])
上記の actor
のように、辞書の中にまた別の辞書がある場合は、内部の辞書にもクラスを定義して参照できます。まず、actor
を、次のように定義します。
class Actor(DictModel):
login = ItemAttr()
url = ItemAttr()
そして、class GithubEvent
クラスでは、actor
の変換関数としてこの Actor
を指定します。
class GithubEvent(DictModel):
id = ItemAttr()
created_at = ItemAttr(load_date, dump_date)
actor = ItemAttr(Actor)
これで、event.actor
という形式で参照できるようになりました。更新も可能です。
event = GithubEvent(data[0])
print("id は", event.id, "です")
print("actor は", event.actor.login, "です")
event.actor.login = "XXXXXX"
print("更新後の actor は", event.actor.login, "です")
ItemAttr
は ジェネリック型 ですので、次のように型を指定できます。
class Actor(DictModel):
login = ItemAttr[str]()
url = ItemAttr[str]()
型を指定しない場合でも、変換関数を指定していれば、変換関数から推論して型チェックが行われます。例えば、上記の
def load_date(s: str) -> datetime:
return dateparse(s)
def dump_date(d: datetime) -> str:
return d.isoformat()
class GithubEvent(DictModel):
id = ItemAttr()
created_at = ItemAttr(load_date, dump_date)
では、load_date()
の戻り値が datetime
と宣言されていますので、ここから GithubEvent.created_at
も datetime
型として認識されます。
Copyright © 2020 Atsuo Ishimoto
Powered by miyadaiku