효율적인 Python 데이터 클래스를 작성하는 방법


작성자별 이미지
# 소개
표준 Python 객체는 인스턴스 사전에 속성을 저장합니다. 해싱을 수동으로 구현하지 않는 한 해시 가능하지 않으며 기본적으로 모든 속성을 비교합니다. 이 기본 동작은 합리적이지만 많은 인스턴스를 생성하거나 객체를 캐시 키로 필요로 하는 애플리케이션에는 최적화되지 않습니다.
데이터 클래스 사용자 지정 코드가 아닌 구성을 통해 이러한 제한 사항을 해결하세요. 매개변수를 사용하여 인스턴스의 동작 방식과 사용하는 메모리 양을 변경할 수 있습니다. 또한 필드 수준 설정을 사용하면 비교에서 속성을 제외하거나 변경 가능한 값에 대한 안전한 기본값을 정의하거나 초기화 작동 방식을 제어할 수 있습니다.
이 기사에서는 복잡성을 추가하지 않고도 효율성과 유지 관리성을 향상시키는 주요 데이터 클래스 기능에 중점을 둡니다.
GitHub에서 코드를 찾을 수 있습니다..
# 1. 해시 가능성 및 안전성을 위한 동결된 데이터 클래스
데이터 클래스를 불변으로 만들면 해시 가능성이 제공됩니다. 이를 통해 아래와 같이 인스턴스를 사전 키로 사용하거나 세트에 저장할 수 있습니다.
from dataclasses import dataclass
@dataclass(frozen=True)
class CacheKey:
user_id: int
resource_type: str
timestamp: int
cache = {}
key = CacheKey(user_id=42, resource_type="profile", timestamp=1698345600)
cache[key] = {"data": "expensive_computation_result"}
그만큼 frozen=True 매개변수는 초기화 후 모든 필드를 변경할 수 없도록 만들고 자동으로 구현합니다. __hash__(). 그것이 없으면 당신은 TypeError 인스턴스를 사전 키로 사용하려고 할 때.
이 패턴은 캐싱 계층, 중복 제거 논리 또는 해시 가능 유형이 필요한 모든 데이터 구조를 구축하는 데 필수적입니다. 불변성은 또한 상태가 예기치 않게 수정되는 전체 버그 범주를 방지합니다.
# 2. 메모리 효율성을 위한 슬롯
수천 개의 개체를 인스턴스화하면 메모리 오버헤드가 빠르게 증가합니다. 예는 다음과 같습니다.
from dataclasses import dataclass
@dataclass(slots=True)
class Measurement:
sensor_id: int
temperature: float
humidity: float
그만큼 slots=True 매개변수는 인스턴스별을 제거합니다. __dict__ Python이 일반적으로 생성하는 것입니다. 슬롯은 속성을 사전에 저장하는 대신 보다 컴팩트한 고정 크기 배열을 사용합니다.
이와 같은 간단한 데이터 클래스의 경우 인스턴스당 몇 바이트를 절약하고 더 빠르게 속성에 액세스할 수 있습니다.. 단점은 새 속성을 동적으로 추가할 수 없다는 것입니다.
# 3. 필드 매개변수를 사용한 사용자 정의 동일성
동등성 검사에 참여하기 위해 모든 필드가 필요한 것은 아닙니다. 이는 다음 예와 같이 메타데이터 또는 타임스탬프를 처리할 때 특히 그렇습니다.
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class User:
user_id: int
email: str
last_login: datetime = field(compare=False)
login_count: int = field(compare=False, default=0)
user1 = User(1, "[email protected]", datetime.now(), 5)
user2 = User(1, "[email protected]", datetime.now(), 10)
print(user1 == user2)
산출:
그만큼 compare=False 필드의 매개변수가 자동 생성된 항목에서 제외됩니다. __eq__() 방법.
여기서는 두 사용자가 언제 로그인했는지, 몇 번 로그인했는지에 관계없이 동일한 ID와 이메일을 공유하는 경우 동일한 사용자로 간주됩니다. 이렇게 하면 동일한 논리적 엔터티를 나타내지만 추적 메타데이터가 다른 개체를 비교할 때 가짜 불평등을 방지할 수 있습니다.
# 4. 기본 팩토리를 사용한 팩토리 기능
함수 서명에 변경 가능한 기본값을 사용하는 것은 Python 문제. 데이터 클래스는 깔끔한 솔루션을 제공합니다.
from dataclasses import dataclass, field
@dataclass
class ShoppingCart:
user_id: int
items: list[str] = field(default_factory=list)
metadata: dict = field(default_factory=dict)
cart1 = ShoppingCart(user_id=1)
cart2 = ShoppingCart(user_id=2)
cart1.items.append("laptop")
print(cart2.items)
그만큼 default_factory 매개변수는 각 인스턴스에 대해 새로운 기본값을 생성하는 콜러블을 사용합니다. 그것 없이는 items: list = [] 모든 인스턴스에 걸쳐 단일 공유 목록을 생성합니다. 고전적인 변경 가능한 기본 문제입니다!
이 패턴은 목록, 사전, 세트 또는 변경 가능한 유형에 작동합니다. 더 복잡한 초기화 논리를 위해 사용자 정의 팩토리 함수를 전달할 수도 있습니다.
# 5. 초기화 후 처리
자동 생성된 후 필드를 파생하거나 데이터의 유효성을 검사해야 하는 경우가 있습니다. __init__ 달린다. 다음을 사용하여 이를 달성하는 방법은 다음과 같습니다. post_init 후크:
from dataclasses import dataclass, field
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False)
def __post_init__(self):
self.area = self.width * self.height
if self.width
그만큼 __post_init__ 메서드는 생성된 직후에 실행됩니다. __init__ 완료합니다. 그만큼 init=False 영역에 대한 매개변수는 영역이 __init__ 매개변수.
이 패턴은 계산된 필드, 유효성 검사 논리 또는 입력 데이터 정규화에 적합합니다. 또한 이를 사용하여 필드를 변환하거나 여러 필드에 의존하는 불변성을 설정할 수도 있습니다.
# 6. Order Parameter를 이용한 주문
때로는 데이터 클래스 인스턴스를 정렬할 수 있어야 합니다. 예는 다음과 같습니다.
from dataclasses import dataclass
@dataclass(order=True)
class Task:
priority: int
name: str
tasks = [
Task(priority=3, name="Low priority task"),
Task(priority=1, name="Critical bug fix"),
Task(priority=2, name="Feature request")
]
sorted_tasks = sorted(tasks)
for task in sorted_tasks:
print(f"{task.priority}: {task.name}")
산출:
1: Critical bug fix
2: Feature request
3: Low priority task
그만큼 order=True 매개변수는 비교 방법을 생성합니다(__lt__, __le__, __gt__, __ge__) 필드 순서를 기준으로 합니다. 필드는 왼쪽에서 오른쪽으로 비교되므로 이 예에서는 이름보다 우선순위가 우선합니다.
이 기능을 사용하면 사용자 지정 비교 논리나 주요 기능을 작성하지 않고도 컬렉션을 자연스럽게 정렬할 수 있습니다.
# 7. 필드 순서 지정 및 InitVar
초기화 로직에 인스턴스 속성이 되어서는 안 되는 값이 필요한 경우 다음을 사용할 수 있습니다. InitVar아래와 같이:
from dataclasses import dataclass, field, InitVar
@dataclass
class DatabaseConnection:
host: str
port: int
ssl: InitVar[bool] = True
connection_string: str = field(init=False)
def __post_init__(self, ssl: bool):
protocol = "https" if ssl else "http"
self.connection_string = f"{protocol}://{self.host}:{self.port}"
conn = DatabaseConnection("localhost", 5432, ssl=True)
print(conn.connection_string)
print(hasattr(conn, 'ssl'))
산출:
False
그만큼 InitVar 유형 힌트는 전달되는 매개변수를 표시합니다. __init__ 그리고 __post_init__ 하지만 필드가 되지는 않습니다. 이렇게 하면 복잡한 초기화 로직을 허용하면서도 인스턴스를 깨끗하게 유지합니다. 그만큼 ssl 플래그는 연결 문자열을 작성하는 방법에 영향을 주지만 나중에 유지할 필요는 없습니다.
# 데이터 클래스를 사용하지 말아야 할 경우
데이터 클래스가 항상 올바른 도구는 아닙니다. 다음과 같은 경우에는 데이터 클래스를 사용하지 마십시오.
- 사용자 정의가 포함된 복잡한 상속 계층이 필요합니다.
__init__여러 수준에 걸친 논리 - 중요한 동작과 메서드를 갖춘 클래스를 구축하고 있습니다(도메인 개체에는 일반 클래스 사용).
- 라이브러리와 같은 검증, 직렬화 또는 구문 분석 기능이 필요합니다. 피단틱 또는 속성 제공하다
- 복잡한 상태 관리 또는 수명 주기 요구 사항이 있는 클래스를 사용하고 있습니다.
데이터 클래스는 모든 기능을 갖춘 도메인 개체보다는 경량 데이터 컨테이너로 가장 잘 작동합니다.
# 결론
효율적인 데이터 클래스를 작성하는 것은 옵션을 모두 기억하는 것이 아니라 옵션이 어떻게 상호 작용하는지 이해하는 것입니다. 앎 언제 그리고 왜 각 기능을 사용하는 것이 모든 매개변수를 기억하는 것보다 더 중요합니다.
기사에서 설명한 대로 불변성, 슬롯, 필드 사용자 정의 및 초기화 후 후크와 같은 기능을 사용하면 간결하고 예측 가능하며 안전한 Python 객체를 작성할 수 있습니다. 이러한 패턴은 복잡성을 추가하지 않고도 버그를 방지하고 메모리 오버헤드를 줄이는 데 도움이 됩니다.
이러한 접근 방식을 사용하면 데이터 클래스를 사용하여 깔끔하고 효율적이며 유지 관리 가능한 코드를 작성할 수 있습니다. 즐거운 코딩하세요!
소녀 프리야 C 인도 출신의 개발자이자 기술 작가입니다. 그녀는 수학, 프로그래밍, 데이터 과학, 콘텐츠 제작의 교차점에서 일하는 것을 좋아합니다. 그녀의 관심 분야와 전문 분야에는 DevOps, 데이터 과학, 자연어 처리가 포함됩니다. 그녀는 읽기, 쓰기, 코딩, 커피를 즐깁니다! 현재 그녀는 튜토리얼, 방법 가이드, 의견 등을 작성하여 개발자 커뮤니티에서 자신의 지식을 학습하고 공유하는 데 힘쓰고 있습니다. Bala는 또한 매력적인 리소스 개요와 코딩 튜토리얼을 만듭니다.



Post Comment