dataclass

codakcodak·2023년 10월 29일
0

OOP python

목록 보기
17/19
post-thumbnail
post-custom-banner

dataclass

  • 쉽고 간편한 자료형인 내장 자료구조들은 실행중 오류가 발생할 수 있다.
  • 클래스를 직접 선언하고 데이터를 담아둔다면 type-safe하다.

기존방식

  • 객체 참조
class User:
    def __init__(self, name: str, age: int, password: str):
        self.name = name
        self.age = age
        self.password = password

    # __str__로 구현해도 되지만 객체 자체를 표현하는 것이 중점이기 때문에 __repr__를 사용하였다.+print는 __str__메서드가 없다면
    def __repr__(self):
        return (
            self.__class__.__name__
            + f"(name={self.name},age={self.age},password={self.password})"
        )


user1 = User("steve", 20, "secret")
#기존의 id가 출력되는 것에서 객체가 가진 속성들을 표현하기위해 __repr__을 재정의
print(user1)
User(name=steve,age=20,password=secret)
  • 동등성비교
class User:
    def __init__(self, name: str, age: int, password: str):
        self.name = name
        self.age = age
        self.password = password

    # ==를 호출하면 내부적으로 __eq__메서드를 호출하여 비교한다.
    def __eq__(self, other: object) -> bool:
        if self.__class__ == other.__class__:
            if (
                self.name == other.name
                and self.age == other.age
                and self.password == other.password
            ):
                return True
        return False


user1 = User("steve", 20, "secret")
user2 = User("steve", 20, "secret")
user3 = User("david", 20, "secret")

print(user1 == user2)
print(user2 == user3)
True
False
  • 객체를 참조하는데 있어서 init을 통해 속성을 설정하고
    repr를 통해 객체를 쉽게 설명하고 eq을 통해 동등성을 수정해주었다.이는 dataclass를 사용하면 쉽게 기능들을 사용할 수 있다.

dataclass 사용

  • 데코레이터를 선언한다.
  • init(), repr(), eq()등과 같은 매직 메서드를 자동으로 생성해준다.
from dataclasses import dataclass


@dataclass
class User:
    name: str
    age: int
    password: str


user1 = User("steve", 20, "secret")
user2 = User("steve", 20, "secret")
user3 = User("david", 20, "secret")

print(user1)

print(user1 == user2)
print(user2 == user3)
User(name='steve', age=20, password='secret')
True
False
  • 값의 불변성을 설정할 수 있다.
from dataclasses import dataclass


@dataclass(frozen=True)
class User:
    name: str
    age: int
    password: str


user1 = User("steve", 20, "secret")
user1.name = "david"
dataclasses.FrozenInstanceError: cannot assign to field 'name'
  • dataclass는 기본적으로 unhashable하다.
from dataclasses import dataclass


@dataclass
class User:
    name: str
    age: int
    password: str


user1 = User("steve", 20, "secret")
user2 = User("david", 23, "secret")
set1 = set([user1, user1, user2, user2])
print(set1)
TypeError: unhashable type: 'User'
  • unsafe_hash옵션을 사용하면 hashable하게 변경된다.
from dataclasses import dataclass


@dataclass(unsafe_hash=True)
class User:
    name: str
    age: int
    password: str


user1 = User("steve", 20, "secret")
user2 = User("david", 23, "secret")
set1 = set([user1, user1, user2, user2])
print(set1)
{User(name='steve', age=20, password='secret'), User(name='david', age=23, password='secret')}

oop적용 dataclass

from dataclasses import dataclass


@dataclass
class User:
    __slots__ = ["_name", "_age", "_password"]
    _name: str  #클래스속성들은 __로 private 설정이 될 수 없어 완벽한 캡슐화를 하지 못했다.(_name로 접근이 가능한 상태이다.)
    _age: int
    _password: str

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name: str):
        self._name = name

    @property
    def age(self):
        return self._name

    @age.setter
    def age(self, age: int):
        self._age = age

    @property
    def password(self):
        return self.password

    @password.setter
    def password(self, password: str):
        self._password = password


user1 = User("steve", 20, "secret")
print(user1.name)
user1.name = "david"
print(user1.name)

user2 = User("david", 20, "secret")
print(user1 == user2)
user3 = User("john", 20, "secret")
print(user1 == user3)
steve
david
True
False

slot을 적용한 dataclass 성능

  • slot을 적용한 dataclass가 메모리와 실행시간이 유리하다.
from dataclasses import dataclass
from memory_profiler import profile
import datetime
import time


@dataclass
class NoSlotUser:
    _name: str
    _age: int
    _password: str

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name: str):
        self._name = name

    @property
    def age(self):
        return self._name

    @age.setter
    def age(self, age: int):
        self._age = age

    @property
    def password(self):
        return self.password

    @password.setter
    def password(self, password: str):
        self._password = password


@dataclass
class WithSlotUser:
    __slots__ = ["_name", "_age", "_password"]
    _name: str
    _age: int
    _password: str

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name: str):
        self._name = name

    @property
    def age(self):
        return self._name

    @age.setter
    def age(self, age: int):
        self._age = age

    @property
    def password(self):
        return self.password

    @password.setter
    def password(self, password: str):
        self._password = password


loop = 1000000

print("==============withSlot=========== 메모리")


@profile
def func1():
    obList1 = [WithSlotUser("test", 10, "test_password") for i in range(loop)]
    del obList1


func1()
print("==============NoSlot=========== 메모리")


@profile
def func2():
    obList2 = [NoSlotUser("test", 10, "test_password") for i in range(loop)]
    del obList2


func2()


print("==============withSlot=========== 실행시간")


def func3():
    for i in range(loop):
        obList3 = WithSlotUser("test", 10, "test_password")
        del obList3


start = time.time()
func3()
end = time.time()
sec = end - start
result = datetime.timedelta(seconds=sec)
print(result)

print("==============noSlot===========  실행시간")


def func4():
    for i in range(loop):
        obList4 = NoSlotUser("test", 10, "test_password")
        del obList4


start = time.time()
func4()
end = time.time()
sec = end - start
result = datetime.timedelta(seconds=sec)
print(result)
==============withSlot=========== 메모리

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    75     20.7 MiB     20.7 MiB           1   @profile
    76                                         def func1():
    77     89.6 MiB     68.9 MiB     1000003       obList1 = [WithSlotUser("test", 10, "test_password") for i in range(loop)]
    78     31.1 MiB    -58.5 MiB           1       del obList1


==============NoSlot=========== 메모리

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    85     31.1 MiB     31.1 MiB           1   @profile
    86                                         def func2():
    87    188.7 MiB    157.6 MiB     1000003       obList2 = [NoSlotUser("test", 10, "test_password") for i in range(loop)]
    88     38.2 MiB   -150.5 MiB           1       del obList2


==============withSlot=========== 실행시간
0:00:00.137285
==============noSlot===========  실행시간
0:00:00.154017
profile
숲을 보는 코더
post-custom-banner

0개의 댓글