Python Dict vs Asdict
dataclasses
ライブラリ は Python 3.7 で導入され、データ ストレージ専用の構造化クラスを作成できるようになりました。 これらのクラスには、データとその描写を処理するための特定のプロパティとメソッドがあります。
Python の dataclasses
ライブラリ
dataclasses
ライブラリをインストールするには、以下のコマンドを使用します。
pip install dataclasses
Python の通常のクラスとは異なり、dataclasses
はクラスの @dataclass
デコレータを使用して実装されます。 また、属性の宣言は、dataclass
で属性のデータ型を指定するタイプ ヒントを使用して行われます。
以下は、この概念を実践するためのコード スニペットです。
# A bare-bones Data Class
# Don't forget to import the dataclass module
from dataclasses import dataclass
@dataclass
class Student:
"""A class which holds a students data"""
# Declaring attributes
# Making use of type hints
name: str
id: int
section: str
classname: str
fatherName: str
motherName: str
# Below is a dataclass instance
student = Student("Muhammad", 1432, "Red", "0-1", "Ali", "Marie")
print(student)
出力:
Student(name='Muhammad', id=1432, section='Red', classname='0-1', fatherName='Ali', motherName='Marie')
上記のコードには、注意すべき点が 2つあります。 まず、dataclass
オブジェクトは引数を受け取り、それらを _init_()
コンストラクターなしで関連するデータ メンバーに割り当てます。
これは、dataclass
が組み込みの _init_()
コンストラクターを提供するためです。
注意すべき 2 番目のポイントは、print
ステートメントは、オブジェクトに存在するデータを、これを行うように特別にプログラムされた関数なしできれいに印刷することです。 これは、変更された _repr_()
関数が必要であることを意味します。
dict
が asdict
よりも速い理由
ほとんどの場合、データクラスなしで dict
を使用していた場合、引き続き dict
を使用する必要があります。
ただし、 asdict
は、コピー呼び出し中に余分なタスクを実行しますが、これはあなたの場合には役に立たないかもしれません. これらの余分なタスクには、回避したいオーバーヘッドがあります。
公式ドキュメントによると、これが何をするかです。 各 dataclass
オブジェクトは、最初に name: value
ペアとしてそのフィールドの dict
に変換されます。
次に、dataclasses
、dicts
、リスト、およびタプルが再帰されます。
たとえば、再帰的な dataclass
ディクテーションが必要な場合は、asdict
を使用します。 そうしないと、それを提供するためのすべての余分な作業が無駄になります。
特に asdict
を使用する場合、含まれているオブジェクトの実装を変更して dataclass
を使用すると、外側のオブジェクトに対する asdict
の結果が変更されます。
from dataclasses import dataclass, asdict
from typing import List
@dataclass
class APoint:
x1: int
y1: int
@dataclass
class C:
aList: List[APoint]
point_instance = APoint(10, 20)
assert asdict(point_instance) == {"x1": 10, "y1": 20}
c = C([APoint(30, 40), APoint(50, 60)])
assert asdict(c) == {"aList": [{"x1": 30, "y1": 40}, {"x1": 50, "y1": 60}]}
さらに、再帰的なビジネス ロジックは、循環参照をまったく処理できません。 dataclasses
を使用して、たとえば、グラフや循環参照を含むその他のデータ構造を表す場合、asdict
は確実にクラッシュします。
@dataclasses.dataclass
class GraphNode:
name: str
neighbors: list["GraphNode"]
x = GraphNode("x", [])
y = GraphNode("y", [])
x.neighbors.append(y)
y.neighbors.append(x)
dataclasses.asdict(x)
# The code will crash here as
# the max allowed recursion depth would have exceeded
# while calling the python object
# in case you're running this on jupyter notebook notice
# that the kernel will restart as the code crashed
さらに、asdict
は新しい dict
を構築しますが、__dict__
はオブジェクトの dict
属性に直接アクセスします。
asdict
の戻り値は、元のオブジェクトの属性の再割り当てによって決して影響を受けないことに注意することが重要です。
また、宣言されたフィールドにマップされない属性を dataclass
オブジェクトに追加する場合、asdict
はフィールドを使用することを考慮すると、asdict
はそれらを含めません。
最後に、ドキュメントでは明示的に言及されていませんが、asdict
は、dataclass
インスタンス、dict
、リスト、またはタプル以外のすべてに対してディープ コピーを呼び出します。
return copy.deepcopy(instance) # a very costly operation !
Dataclass
インスタンス、dicts
、リスト、およびタプルは、再帰的ロジックを通過し、再帰的ディクテーションが適用されたコピーをさらに構築します。
オブジェクト指向のパラダイムにかなり精通している場合は、すべてのオブジェクトを検査してコピーする必要があるものを確認するため、ディープ コピー自体がコストのかかる操作であることを知っているでしょう。 メモ処理がないということは、基本的に asdict
が重要なオブジェクト グラフで共有オブジェクトの複数のコピーを作成する可能性が高いことを意味します。
このようなシナリオに注意してください:
from dataclasses import dataclass, asdict
@dataclass
class PointClass:
x1: object
y1: object
obj_instance = object()
var1 = PointClass(obj_instance, obj_instance)
var2 = asdict(var1)
print(var1.x1 is var1.y1) # prints true
print(var2["x1"] is var2["y1"]) # prints false
print(var2["x1"] is var1.x1) # prints false
出力:
True
False
False