Python의 @dataclass 데코레이터

Python의 @dataclass 데코레이터

Python
Python의 @dataclass 데코레이터
Author

gabriel yang

Published

September 23, 2024


@dataclass는 Python에서 클래스를 더 간단하고 가독성 있게 작성할 수 있게 해주는 데코레이터입니다. 일반적인 클래스에서는 __init__, __repr__, __eq__ 같은 메서드를 직접 구현해야 하지만, @dataclass는 이를 자동으로 생성해줍니다. 특히, 데이터 중심 클래스에 유용하게 사용할 수 있습니다.

Python에서 클래스를 작성할 때, 우리는 종종 많은 보일러플레이트 코드를 작성하게 됩니다. 예를 들어, 객체를 초기화하기 위한 __init__ 메서드, 객체를 사람이 읽기 좋은 형식으로 출력하는 __repr__, 객체 간 동등성 비교를 위한 __eq__ 등을 매번 작성하는 것은 번거로울 수 있습니다.

이런 상황에서 Python의 @dataclass 데코레이터는 매우 유용한 도구입니다. 이 데코레이터는 클래스 정의를 간단하게 하고, 필요한 메서드들을 자동으로 생성해 줍니다.

@dataclass란?

@dataclass는 Python 3.7에서 도입된 데코레이터로, 데이터를 저장하기 위한 클래스에서 기본적으로 필요한 기능들을 자동으로 생성해 줍니다. 이를 통해 클래스 정의가 더 간결해지고 유지보수가 쉬워집니다.

다음 예시 코드를 통해 @dataclass의 기본 사용법을 살펴보겠습니다.

기본 사용법

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

# Person 객체 생성
p1 = Person(name="Alice", age=30)
p2 = Person(name="Bob", age=25)

print(p1)  # Person(name='Alice', age=30)
print(p2)  # Person(name='Bob', age=25)
Person(name='Alice', age=30)
Person(name='Bob', age=25)

위 코드를 보면 __init__, __repr__ 같은 메서드를 명시적으로 작성하지 않았음에도 불구하고, Person 클래스의 인스턴스를 쉽게 생성하고 출력할 수 있습니다.

자동 생성되는 메서드

@dataclass 데코레이터를 사용하면 Python은 몇 가지 메서드를 자동으로 생성해 줍니다:

  1. __init__: 클래스 변수를 인자로 받아 객체를 초기화하는 메서드를 자동으로 생성합니다.
  2. __repr__: 객체를 사람이 읽기 좋은 문자열로 반환합니다.
  3. __eq__: 두 객체가 같은지 비교할 수 있게 해줍니다.

필드 기본값 설정

클래스 필드에 기본값을 설정하고 싶다면, 이를 명시적으로 지정할 수 있습니다. 기본값이 있는 필드는 항상 기본값이 없는 필드 뒤에 와야 합니다.

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int = 25  # 기본값 설정

p1 = Person(name="Alice")
p2 = Person(name="Bob", age=30)

print(p1)  # Person(name='Alice', age=25)
print(p2)  # Person(name='Bob', age=30)
Person(name='Alice', age=25)
Person(name='Bob', age=30)

field() 함수 사용

@dataclassfield() 함수를 사용하여 보다 세부적으로 필드를 설정할 수 있습니다. 이를 통해 기본값 설정이나, 필드의 기본 동작을 커스터마이징할 수 있습니다.

from dataclasses import dataclass, field

@dataclass
class Person:
    name: str
    age: int = field(default=25)

p = Person(name="Alice")
print(p)  # Person(name='Alice', age=25)
Person(name='Alice', age=25)

field() 함수는 Python의 dataclasses 모듈에서 사용되는 도구로, @dataclass 데코레이터를 사용할 때 클래스 필드의 기본 동작을 세밀하게 제어할 수 있도록 도와줍니다. 이를 통해 필드에 대한 기본값, 초기화 방식, 메타데이터 등의 설정을 할 수 있습니다.

field()는 특히 기본값이나 default_factory를 사용하여 가변형 데이터(예: 리스트, 딕셔너리 등)의 기본값을 설정할 때 많이 사용되며, 필드가 자동으로 __init__에 포함되는지 여부를 조절할 수 있는 다양한 옵션도 제공합니다.

field() 함수 사용법

다음은 field()의 주요 인자와 그 사용 예시입니다.

1. default: 기본값 설정

default 인자를 사용하면 필드의 기본값을 설정할 수 있습니다. 이는 @dataclass에서 일반적인 필드 기본값 지정 방법과 동일하지만, field()를 사용하면 더 세밀한 설정이 가능합니다.

from dataclasses import dataclass, field

@dataclass
class Person:
    name: str
    age: int = field(default=25)  # 기본값 25 설정

p = Person(name="Alice")
print(p)  # Person(name='Alice', age=25)
Person(name='Alice', age=25)

2. default_factory: 가변형 데이터 타입의 기본값 설정

가변형 데이터(리스트, 딕셔너리 등)의 기본값을 설정할 때는 default_factory를 사용해야 합니다. 이는 각 객체가 고유의 가변 데이터를 가질 수 있게 해줍니다.

from dataclasses import dataclass, field

@dataclass
class ShoppingCart:
    items: list = field(default_factory=list)  # 각 인스턴스에서 새로운 리스트 생성

cart1 = ShoppingCart()
cart2 = ShoppingCart()

cart1.items.append("Apple")
cart2.items.append("Banana")

print(cart1.items)  # ['Apple']
print(cart2.items)  # ['Banana']
['Apple']
['Banana']

여기서 default_factory=listShoppingCart의 각 인스턴스가 독립된 리스트를 가지게 하여, cart1cart2의 아이템이 서로 영향을 주지 않게 합니다.

Note

만약 default를 사용해 리스트 같은 가변형 타입의 기본값을 설정하면 모든 인스턴스가 같은 리스트를 공유하게 되는 문제가 발생할 수 있습니다.

3. init: __init__ 메서드에 포함 여부 제어

init=False로 설정하면 해당 필드가 __init__ 메서드의 인자로 포함되지 않도록 할 수 있습니다. 즉, 이 필드는 객체 생성 시 외부에서 값을 넘길 수 없고, 클래스 내부에서만 값을 설정해야 합니다.

from dataclasses import dataclass, field

@dataclass
class Person:
    name: str
    age: int = field(init=False)  # __init__ 메서드에 포함되지 않음

    def __post_init__(self):
        self.age = 30  # 초기화 이후에 값 설정

p = Person(name="Alice")
print(p)  # Person(name='Alice', age=30)
Person(name='Alice', age=30)

위 예시에서 age 필드는 init=False로 설정되어 __init__에서 직접 초기화되지 않으며, __post_init__ 메서드에서 값을 설정합니다.

4. repr: 출력 여부 제어

repr=False로 설정하면 __repr__ 메서드에서 해당 필드를 생략할 수 있습니다. 이는 클래스의 출력 형식을 커스터마이징할 때 유용합니다.

from dataclasses import dataclass, field

@dataclass
class Person:
    name: str
    age: int = field(repr=False)  # __repr__ 메서드에서 출력하지 않음

p = Person(name="Alice", age=30)
print(p)  # Person(name='Alice')
Person(name='Alice')

위 코드에서 age 필드는 repr=False로 설정되어 print() 시 출력되지 않습니다.

5. compare: 비교 메서드에서 사용 여부 제어

compare=False로 설정하면 해당 필드가 __eq____lt__, __gt__ 등 비교 메서드에서 제외됩니다. 이를 통해 특정 필드를 무시한 객체 간 비교가 가능합니다.

from dataclasses import dataclass, field

@dataclass
class Person:
    name: str
    age: int = field(compare=False)  # 비교 메서드에서 제외

p1 = Person(name="Alice", age=30)
p2 = Person(name="Alice", age=40)

print(p1 == p2)  # True, 나이를 비교하지 않음
True

위 코드에서 age 필드는 compare=False로 설정되어 두 객체를 비교할 때 무시됩니다.

6. metadata: 추가 정보 저장

metadata는 필드에 메타데이터를 저장할 수 있는 선택적인 인자입니다. 이 데이터는 주로 필드에 대한 추가 설명이나 정보를 제공하는 데 사용되며, 실제 동작에 영향을 미치지 않습니다. metadata는 딕셔너리 형태로 사용됩니다.

from dataclasses import dataclass, field

@dataclass
class Person:
    name: str
    age: int = field(metadata={"info": "Age of the person"})

p = Person(name="Alice", age=30)
print(p)  # Person(name='Alice', age=30)
print(p.__dataclass_fields__['age'].metadata)  # {'info': 'Age of the person'}
Person(name='Alice', age=30)
{'info': 'Age of the person'}

위 예시에서 metadata는 필드에 대한 설명을 추가하는 용도로 사용되며, 필드 동작에는 영향을 주지 않습니다.

__post_init__ 메서드

__init__ 메서드가 실행된 후에 추가적인 초기화를 하고 싶다면 __post_init__ 메서드를 사용할 수 있습니다. 이 메서드는 모든 필드가 초기화된 후에 호출됩니다.

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

    def __post_init__(self):
        self.age += 1  # 나이를 1살 더함

p = Person(name="Alice", age=30)
print(p)  # Person(name='Alice', age=31)
Person(name='Alice', age=31)

요약

@dataclass는 Python에서 클래스를 정의할 때 불필요한 보일러플레이트 코드를 줄여주는 강력한 도구입니다. 특히, 데이터를 저장하기 위한 단순한 클래스에서 매우 유용하게 사용할 수 있습니다. 이를 통해 코드의 가독성을 높이고 유지보수를 쉽게 할 수 있습니다.