Develop

[ Python ] dataclass ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ

proggg 2024. 11. 3. 19:14

Python์˜ Dataclass: ๊ฐœ๋…๊ณผ ํ™œ์šฉ๋ฒ•

Python์˜ dataclass๋Š” ์ฝ”๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ๋งค์šฐ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. Python 3.7๋ถ€ํ„ฐ ๋„์ž…๋œ dataclass๋Š” ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , ๋” ๊ฐ€๋…์„ฑ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” dataclass์˜ ๊ธฐ๋ณธ ๊ฐœ๋…, ํŠน์ง•, ์‚ฌ์šฉ๋ฒ•, ๋‹ค์–‘ํ•œ ์˜ต์…˜์„ ํฌํ•จํ•œ ์‹ฌํ™” ๋‚ด์šฉ๊นŒ์ง€ ๋‹ค๋ฃจ๊ฒ ์Šต๋‹ˆ๋‹ค.


1. Dataclass๋ž€?

dataclass๋Š” ๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•  ๋•Œ ์œ ์šฉํ•œ Python์˜ ๋‚ด์žฅ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ํด๋ž˜์Šค์˜ ์ƒ์„ฑ์ž, __repr__, __eq__ ๋“ฑ ์—ฌ๋Ÿฌ ๋ฉ”์„œ๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด ์คŒ์œผ๋กœ์จ ๋ฐ˜๋ณต์ ์ธ ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , ๊ฐ€๋…์„ฑ์„ ๋†’์—ฌ์ค๋‹ˆ๋‹ค.

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

์œ„์™€ ๊ฐ™์€ Person ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๋ฉด, ์ž๋™์œผ๋กœ ์ƒ์„ฑ์ž __init__๊ณผ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋“ค์ด ์ •์˜๋ฉ๋‹ˆ๋‹ค.

person = Person(name="Alice", age=30)
print(person)  # ์ถœ๋ ฅ: Person(name='Alice', age=30)

2. Dataclass์˜ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

ํ•„์ˆ˜ ํ•„๋“œ ์ •์˜ํ•˜๊ธฐ

dataclass๋Š” ํด๋ž˜์Šค์˜ ํ•„๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋ฏ€๋กœ, ํƒ€์ž… ํžŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์ˆ˜ ํ•„๋“œ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@dataclass
class Book:
    title: str
    author: str
    year: int

book = Book(title="1984", author="George Orwell", year=1949)
print(book)  # ์ถœ๋ ฅ: Book(title='1984', author='George Orwell', year=1949)

๊ธฐ๋ณธ๊ฐ’ ์„ค์ •ํ•˜๊ธฐ

ํ•„๋“œ์— ๊ธฐ๋ณธ๊ฐ’์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋Š” dataclass์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.

@dataclass
class Car:
    make: str
    model: str
    year: int = 2020  # ๊ธฐ๋ณธ๊ฐ’

car = Car(make="Toyota", model="Camry")
print(car)  # ์ถœ๋ ฅ: Car(make='Toyota', model='Camry', year=2020)

๊ธฐ๋ณธ๊ฐ’์ด ์žˆ๋Š” ํ•„๋“œ ์ˆœ์„œ ์ฃผ์˜

๊ธฐ๋ณธ๊ฐ’์ด ์—†๋Š” ํ•„๋“œ๋Š” ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ๋Š” ํ•„๋“œ๋ณด๋‹ค ์•ž์— ์œ„์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด TypeError๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

@dataclass
class Employee:
    name: str
    position: str = "Engineer"
    salary: int
# ์˜ค๋ฅ˜: non-default argument 'salary' follows default argument 'position'

3. Dataclass์˜ ์ฃผ์š” ์˜ต์…˜๋“ค

dataclass๋Š” @dataclass ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ๋‹ค์–‘ํ•œ ์˜ต์…˜์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€ํ‘œ์ ์ธ ์˜ต์…˜์œผ๋กœ๋Š” frozen, order, repr ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

3.1 frozen ์˜ต์…˜

frozen=True๋ฅผ ์„ค์ •ํ•˜๋ฉด ๊ฐ์ฒด๊ฐ€ ๋ถˆ๋ณ€(immutable) ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์ƒ์„ฑ ํ›„ ํ•„๋“œ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ๋ถˆ๋ณ€์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@dataclass(frozen=True)
class Point:
    x: int
    y: int

point = Point(1, 2)
# point.x = 3  # ์˜ค๋ฅ˜: Cannot assign to field 'x' (frozen ์ƒํƒœ)

3.2 order ์˜ต์…˜

order=True ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋น„๊ต ๋ฉ”์„œ๋“œ(<, <=, >, >=)๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ํฌ๊ธฐ ๋น„๊ต๊ฐ€ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@dataclass(order=True)
class Student:
    name: str
    grade: int

s1 = Student("Alice", 85)
s2 = Student("Bob", 90)
print(s1 < s2)  # ์ถœ๋ ฅ: True (85 < 90)

3.3 repr ์˜ต์…˜

repr=False๋กœ ์„ค์ •ํ•˜๋ฉด __repr__ ๋ฉ”์„œ๋“œ์—์„œ ํ•ด๋‹น ํ•„๋“œ๊ฐ€ ์ œ์™ธ๋ฉ๋‹ˆ๋‹ค. ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ํ•„๋“œ์˜ ๊ฒฝ์šฐ ์ด๋ฅผ ์ œ์™ธํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

@dataclass(repr=False)
class Account:
    username: str
    password: str

account = Account("user1", "pass123")
print(account)  # ์ถœ๋ ฅ: Account() (ํ•„๋“œ ํ‘œ์‹œ ์•ˆ ๋จ)

4. ํ•„๋“œ์˜ ๋‹ค์–‘ํ•œ ์„ค์ • - field()

dataclass์˜ ํ•„๋“œ๋Š” field() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด๋‹ค ์„ธ๋ถ€์ ์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜, ํŠน์ • ํ•„๋“œ๋ฅผ ๋น„๊ต์—์„œ ์ œ์™ธํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from dataclasses import dataclass, field

@dataclass
class Movie:
    title: str
    year: int
    rating: float = field(default=5.0)  # ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
    reviews: list = field(default_factory=list)  # ๋ฆฌ์ŠคํŠธ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •

movie = Movie(title="Inception", year=2010)
print(movie)  # ์ถœ๋ ฅ: Movie(title='Inception', year=2010, rating=5.0, reviews=[])

๋น„๊ต์—์„œ ์ œ์™ธ - compare=False

field()์— compare=False๋ฅผ ์ง€์ •ํ•˜๋ฉด ํŠน์ • ํ•„๋“œ๋ฅผ ๋น„๊ต์—์„œ ์ œ์™ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@dataclass
class Product:
    name: str
    price: float
    category: str = field(compare=False)

p1 = Product(name="Laptop", price=1000, category="Electronics")
p2 = Product(name="Laptop", price=1000, category="Computers")

print(p1 == p2)  # ์ถœ๋ ฅ: True (category๋Š” ๋น„๊ต์—์„œ ์ œ์™ธ๋จ)

5. dataclass์—์„œ์˜ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€

dataclass๋Š” ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€๋ฅผ ๋ฐฉํ•ดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž ์ •์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฐ์ดํ„ฐ ์กฐ์ž‘ ๋ฐ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@dataclass
class Rectangle:
    width: float
    height: float

    def area(self) -> float:
        return self.width * self.height

rect = Rectangle(10, 5)
print(rect.area())  # ์ถœ๋ ฅ: 50.0

6. Dataclass์™€ ์ƒ์†

dataclass๋Š” ์ƒ์†๋„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๋ถ€๋ชจ ํด๋ž˜์Šค์˜ dataclass ํ•„๋“œ๋ฅผ ์ž์‹ ํด๋ž˜์Šค๊ฐ€ ์ƒ์†๋ฐ›๊ณ , ์ถ”๊ฐ€์ ์ธ ํ•„๋“œ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@dataclass
class Animal:
    name: str

@dataclass
class Dog(Animal):
    breed: str

dog = Dog(name="Buddy", breed="Golden Retriever")
print(dog)  # ์ถœ๋ ฅ: Dog(name='Buddy', breed='Golden Retriever')

7. dataclass์™€ __post_init__

__post_init__ ๋ฉ”์„œ๋“œ๋Š” dataclass ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋œ ํ›„ ์ถ”๊ฐ€์ ์ธ ์ดˆ๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ์กฐ๊ฑด์— ๋”ฐ๋ผ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@dataclass
class Person:
    name: str
    age: int

    def __post_init__(self):
        if self.age < 0:
            raise ValueError("Age cannot be negative")

person = Person("Alice", -5)  # ์˜ค๋ฅ˜ ๋ฐœ์ƒ: Age cannot be negative

8. Dataclass์˜ ์žฅ๋‹จ์ 

์žฅ์ 

  • ์ฝ”๋“œ ๊ฐ„๊ฒฐ์„ฑ: ๊ธฐ๋ณธ ์ƒ์„ฑ์ž์™€ ๋‹ค์–‘ํ•œ ๋ฉ”์„œ๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์–ด ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง‘๋‹ˆ๋‹ค.
  • ํƒ€์ž… ํžŒํŠธ ์ง€์›: ํ•„๋“œ์˜ ํƒ€์ž…์„ ์ง€์ •ํ•ด ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ž๋™ ๋น„๊ต ์—ฐ์‚ฐ ์ง€์›: order=True ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด ๋น„๊ต ์—ฐ์‚ฐ์ž๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ถˆ๋ณ€์„ฑ ์ง€์›: frozen=True๋กœ ์„ค์ •ํ•˜์—ฌ ๊ฐ์ฒด๋ฅผ ๋ถˆ๋ณ€์œผ๋กœ ๋งŒ๋“ค์–ด ์•ˆ์ •์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ 

  • ๊ฐ€๋ณ€ ๊ธฐ๋ณธ๊ฐ’ ๋ฌธ์ œ: ๋ฆฌ์ŠคํŠธ๋‚˜ ๋”•์…”๋„ˆ๋ฆฌ ๊ฐ™์€ ๊ฐ€๋ณ€ ๊ฐ์ฒด๋ฅผ ํ•„๋“œ์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. default_factory๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด๋ฅผ ๋ชจ๋ฅด๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ƒ์† ์ œ์•ฝ: ๋‹ค์ค‘ ์ƒ์†๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ์ƒ์† ๊ตฌ์กฐ์—์„œ๋Š” ๋‹ค์†Œ ์ œ์•ฝ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„ฑ๋Šฅ: ์ผ๋ฐ˜ ํด๋ž˜์Šค๋ณด๋‹ค ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋‹ค์†Œ ๋” ๋งŽ์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํŠนํžˆ ๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์„ฑ๋Šฅ์— ๋ฏธ์„ธํ•œ ์ฐจ์ด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

Python์˜ dataclass๋Š” ๋ฐ์ดํ„ฐ ์ค‘์‹ฌ์˜ ํด๋ž˜์Šค ์ž‘์„ฑ์— ์žˆ์–ด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œ์ผœ์ฃผ๋Š” ์œ ์šฉํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ  ๋ฐ์ดํ„ฐ ๊ฐ์ฒด๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, field() ์„ค์ •, frozen ๋ถˆ๋ณ€ ๊ฐ์ฒด, order ์ •๋ ฌ ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ์˜ต์…˜์„ ํ†ตํ•ด ๊ณ ๋„์˜ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.