Python Dict 대 Asdict

Abdullah Bukhari 2023년6월21일
  1. Python의 dataclasses 라이브러리
  2. dictasdict보다 빠른 이유
Python Dict 대 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')

위의 코드에서 주목해야 할 두 가지 사항이 있습니다. 첫째, dataclass 개체는 인수를 수락하고 _init_() 생성자 없이 관련 데이터 멤버에 할당합니다.

이는 dataclass가 내장 _init_() 생성자를 제공하기 때문입니다.

두 번째로 주목해야 할 점은 print 문이 이를 수행하도록 특별히 프로그래밍된 기능 없이 개체에 있는 데이터를 깔끔하게 인쇄한다는 것입니다. 이는 변경된 _repr_() 함수가 있어야 함을 의미합니다.

dictasdict보다 빠른 이유

대부분의 경우 데이터 클래스 없이 dict를 사용했을 경우 확실히 dict를 계속 사용해야 합니다.

그러나 asdict는 복사 호출 중에 사용자의 경우에 유용하지 않을 수 있는 추가 작업을 수행합니다. 이러한 추가 작업에는 피하고 싶은 오버헤드가 있습니다.

공식 문서에 따르면 다음과 같습니다. 각 dataclass 개체는 먼저 해당 필드의 dictname: value 쌍으로 변환됩니다.

그런 다음 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는 해당 필드를 포함하지 않습니다.

마지막으로 문서에서 명시적으로 언급하지 않더라도 asdictdataclass 인스턴스, 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