Python Dict vs Asdict

Abdullah Bukhari 2023年6月21日
  1. Python の dataclasses ライブラリ
  2. dictasdict よりも速い理由
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_() 関数が必要であることを意味します。

dictasdict よりも速い理由

ほとんどの場合、データクラスなしで dict を使用していた場合、引き続き dict を使用する必要があります。

ただし、 asdict は、コピー呼び出し中に余分なタスクを実行しますが、これはあなたの場合には役に立たないかもしれません. これらの余分なタスクには、回避したいオーバーヘッドがあります。

公式ドキュメントによると、これが何をするかです。 各 dataclass オブジェクトは、最初に name: value ペアとしてそのフィールドの dict に変換されます。

次に、dataclassesdicts、リスト、およびタプルが再帰されます。

たとえば、再帰的な 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

関連記事 - Python Dataclass