[解決済み] ネストしたdictからのPythonデータクラス
質問
3.7の標準ライブラリは、データクラスを再帰的にdictに変換することができます(docsの例)。
from dataclasses import dataclass, asdict
from typing import List
@dataclass
class Point:
x: int
y: int
@dataclass
class C:
mylist: List[Point]
p = Point(10, 20)
assert asdict(p) == {'x': 10, 'y': 20}
c = C([Point(0, 0), Point(10, 4)])
tmp = {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}
assert asdict(c) == tmp
ネストがある場合に、dictをdataclassに戻す方法を探しています。例えば
C(**tmp)
のようなものは、データクラスのフィールドが単純な型であり、それ自体がデータクラスでない場合にのみ動作します。私は[jsonpickle][1]に精通していますが、これは顕著なセキュリティ警告が付属しています。
EDITです。
回答では、以下のようなライブラリが提案されています。
- dacite
- mashumaro (しばらくの間使用していました。うまく機能しますが、すぐにトリッキーなコーナーケースに遭遇しました)
- pydantic (非常によく動作し、優れたドキュメントと少ないコーナーケース) [1]: https://jsonpickle.github.io/
どのように解決するのですか?
以下は、CPythonの実装である
asdict
- 具体的には、内部の再帰的なヘルパー関数である
_asdict_inner
を使用します。
# Source: https://github.com/python/cpython/blob/master/Lib/dataclasses.py
def _asdict_inner(obj, dict_factory):
if _is_dataclass_instance(obj):
result = []
for f in fields(obj):
value = _asdict_inner(getattr(obj, f.name), dict_factory)
result.append((f.name, value))
return dict_factory(result)
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
# [large block of author comments]
return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj])
elif isinstance(obj, (list, tuple)):
# [ditto]
return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
elif isinstance(obj, dict):
return type(obj)((_asdict_inner(k, dict_factory),
_asdict_inner(v, dict_factory))
for k, v in obj.items())
else:
return copy.deepcopy(obj)
asdict
は単に上記をいくつかのアサーション付きで呼び出すだけであり
dict_factory=dict
をデフォルトで呼び出すだけです。
コメントで述べられているように、必要なタイプタグを持つ出力辞書を作成するために、これをどのように適応させることができるでしょうか?
1. 型情報の追加
私が行ったのは
dict
:
class TypeDict(dict):
def __init__(self, t, *args, **kwargs):
super(TypeDict, self).__init__(*args, **kwargs)
if not isinstance(t, type):
raise TypeError("t must be a type")
self._type = t
@property
def type(self):
return self._type
元のコードを見ると、このラッパーを使うために修正する必要があるのは最初の節だけで、他の節では
コンテナ
の
dataclass
-es となります。
# only use dict for now; easy to add back later
def _todict_inner(obj):
if is_dataclass_instance(obj):
result = []
for f in fields(obj):
value = _todict_inner(getattr(obj, f.name))
result.append((f.name, value))
return TypeDict(type(obj), result)
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
return type(obj)(*[_todict_inner(v) for v in obj])
elif isinstance(obj, (list, tuple)):
return type(obj)(_todict_inner(v) for v in obj)
elif isinstance(obj, dict):
return type(obj)((_todict_inner(k), _todict_inner(v))
for k, v in obj.items())
else:
return copy.deepcopy(obj)
輸入品です。
from dataclasses import dataclass, fields, is_dataclass
# thanks to Patrick Haugh
from typing import *
# deepcopy
import copy
使用する関数
# copy of the internal function _is_dataclass_instance
def is_dataclass_instance(obj):
return is_dataclass(obj) and not is_dataclass(obj.type)
# the adapted version of asdict
def todict(obj):
if not is_dataclass_instance(obj):
raise TypeError("todict() should be called on dataclass instances")
return _todict_inner(obj)
サンプルのデータクラスでテストします。
c = C([Point(0, 0), Point(10, 4)])
print(c)
cd = todict(c)
print(cd)
# {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}
print(cd.type)
# <class '__main__.C'>
結果は予想通りです。
2. に変換して戻す。
dataclass
で使用される再帰的ルーチンは
asdict
で使用される再帰ルーチンは、いくつかの比較的小さな変更で、逆の処理に再利用することができます。
def _fromdict_inner(obj):
# reconstruct the dataclass using the type tag
if is_dataclass_dict(obj):
result = {}
for name, data in obj.items():
result[name] = _fromdict_inner(data)
return obj.type(**result)
# exactly the same as before (without the tuple clause)
elif isinstance(obj, (list, tuple)):
return type(obj)(_fromdict_inner(v) for v in obj)
elif isinstance(obj, dict):
return type(obj)((_fromdict_inner(k), _fromdict_inner(v))
for k, v in obj.items())
else:
return copy.deepcopy(obj)
使用する関数
def is_dataclass_dict(obj):
return isinstance(obj, TypeDict)
def fromdict(obj):
if not is_dataclass_dict(obj):
raise TypeError("fromdict() should be called on TypeDict instances")
return _fromdict_inner(obj)
テストします。
c = C([Point(0, 0), Point(10, 4)])
cd = todict(c)
cf = fromdict(cd)
print(c)
# C(mylist=[Point(x=0, y=0), Point(x=10, y=4)])
print(cf)
# C(mylist=[Point(x=0, y=0), Point(x=10, y=4)])
またもや予想通り。
関連
-
[解決済み] Pythonには文字列の'contains'サブストリングメソッドがありますか?
-
[解決済み] Pythonで現在時刻を取得する方法
-
[解決済み] Pythonで2つのリストを連結する方法は?
-
[解決済み] Pythonで例外を手動で発生(スロー)させる
-
[解決済み] Python 3で「1000000000000000 in range(1000000000000001)」はなぜ速いのですか?
-
[解決済み] Pythonの辞書からキーを削除するにはどうしたらいいですか?
-
[解決済み】ネストされたディレクトリを安全に作成するには?
-
[解決済み】Pythonに三項条件演算子はありますか?
-
[解決済み] Pandasのデータフレームでタプルの列を分割するにはどうしたらいいですか?
-
[解決済み] PILでPNG画像を文字列に書き出すには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] pyvenv-3.4 は 0 ではない終了ステータス 1 を返しました。
-
[解決済み] IPythonの終了確認を無効にする
-
[解決済み] Google App EngineのためのFlaskとwebapp2の比較
-
[解決済み] Pythonで文字列が数字で始まるかどうかを判断するには?
-
[解決済み] イテラブルを一定サイズのチャンクに分割する方法
-
[解決済み] SQLAlchemy が db に送る SQL コマンドのデバッグ(表示)
-
[解決済み] Pythonを使ったMicrosoft SQLサーバへの接続
-
[解決済み] 関数が受け取るキーワード引数をリストアップできますか?
-
[解決済み] Pythonのモジュール命名規則
-
[解決済み] リストの各要素に数値を乗じるには?