일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- ExpansionTile
- freezeD
- 책
- copyWIth
- 코딩
- 개발자
- python
- 도서
- 유데미 러닝크루
- 가상환경
- 명령어
- ListTile
- 유데미 코리아
- 디자인패턴
- 플러터
- 프로그래밍
- 맥
- flutter
- command
- 리버팟
- Code Generation
- 유데미
- linux
- vscode
- 파이썬
- 리눅스
- 개발
- riverpod
- 다트
- dart
- Today
- Total
승상의 코딩 블로그
파이썬 - dataclass(데이터클래스) 본문
파이썬 3.7부터 사용되고 있다. 그 이전버전은 dataclass 라이브러리를 따로 설치해야 한다.
type hint 는 버전마다 조금씩 다르게 사용되므로 참고해주시기 바랍니다. 아래 코드는 파이썬 버전 3.10 으로 작성하였습니다.
기존에는 데이터를 구조화하기 위해서는 아래와 같이 클래스로 작성하는 경우가 많았다.
직원을 구현하는 클래스를 구현해보자.
class Employee:
def __init__(self, firstName, lastName):
self.firstName = firstName
self.lastName = lastName
print(Employee(firstName="Yang", lastName="SeungSang"))
# 출력값: <__main__.Employee object at 0x1031cb040>
dataclass 가 있기 전에는 __init__ 함수의 파라미터를 멤버 변수와 맵핑하는 작업을 해줘야했다.
dataclass 는 __init__ 에서의 맵핑작업 이외에도 반복적인 일들을 줄여준다.
다른 반복적인 일로는 객체 출력값 구현이 있다. 일반적으로 객체를 출력하면 메모리의 주소값이 뜬다.
클래스를 출력할 때 조금 더 의미 있는 데이터를 만들기 위해서는 아래와 같이 __repr__ 함수를 구현해줘야 한다.
class Employee:
def __init__(self, firstName, lastName):
self.firstName = firstName
self.lastName = lastName
def __repr__(self):
return f"Employee is {self.firstName} {self.lastName}"
print(Employee(firstName="Yang", lastName="SeungSang"))
# Employee is Yang SeungSang
DataClass
여러 반복 잡업들을 모두 없애주고 다양한 기능을 쉽게 설정할 수 있는 것이 dataclass 이다.
from dataclasses import dataclass
@dataclass
class Employee:
firstName: str
lastName: str
print(Employee(firstName="Yang", lastName="SeungSang"))
# 출력값: Employee(firstName='Yang', lastName='SeungSang')
클래스에다가 dataclass 데코레이터만 추가해주면 사용할 수 있다.
클래스 변수처럼 필드(변수)를 기입하고 타입만 적어주면 된다. (하지만 클래스 변수처럼 공유되지는 않는다.)
그러면 자동으로 __init__ 에서 수행하던 맵핑을 수행해주고, 멤버 변수를 기반으로 __repr__ 함수도 구현해 준다.
어떻게 사용할 수 있는지 조금더 알아보자.
기본값 설정
dataclass 에 필드가 정의되었지만 객체 생성시 파라미터를 넘겨주지 않으면 에러가 난다.
이런 경우, 기본값을 설정하여 사용할 수 있다.
from dataclasses import dataclass
@dataclass
class Employee:
firstName: str
lastName: str
year: int = 1
print(Employee(firstName="Yang", lastName="SeungSang"))
# 출력값: Employee(firstName='Yang', lastName='SeungSang', year=1)
print(Employee(firstName="Yang", lastName="SeungSang", year=2))
# 출력값: Employee(firstName='Yang', lastName='SeungSang', year=2)
year(근무 년차) 를 Employee 클래스에 추가하고, Default 값은 1로 설정했다.
field(default_factory=list) : 리스트 선언
primitive type 인 int, str 과는 다르게, reference type 인 리스트는 다르게 선언해줘야한다.
(primitive 는 값 자체라고 생각하고, reference 는 주소값 이라고 생각하면 된다.)
reference type은 주소값 사용하기 때문에, 개별 객체가 독립적으로 그 값을 관리하지 못한다.
Employee 의 A, B 객체가 있다면, A에서 값을 바꿀 경우 B의 값도 바뀐다.
이를 해결하기 위해, dataclasses 라이브러리에서는 field 함수를 제공한다.
from dataclasses import dataclass, field
@dataclass
class Employee:
firstName: str
lastName: str
year: int = 1
skills: list[str] = field(default_factory=list)
print(Employee(firstName="Yang", lastName="SeungSang", skills=['MachinLearning']))
# 출력값 : Employee(firstName='Yang', lastName='SeungSang', year=1, skills=['MachinLearning'])
field 함수의 default_factory 파라미터에 list 를 넣어주면, Employee 를 구현한 각 객체는 모두 독립적인 리스트를 가지게 된다.
__post_init__()
코드를 짜다보면, 각 값을 받아서 새로운 데이터를 생성해야 할 때가 있다.
이 때, __post_init__ 함수를 사용한다. 말 그대로 __init__ 함수 이후에 자동으로 호출되는 함수이다.
firstName 과 lastName을 붙여서 fullName을 만들어보자.
from dataclasses import dataclass, field
@dataclass
class Employee:
firstName: str
lastName: str
fullName: str = field(init=False)
year: int = 1
skills: list[str] = field(default_factory=list)
def __post_init__(self):
self.fullName = f"{self.firstName} {self.lastName}"
print(Employee(firstName="Yang", lastName="SeungSang",
skills=['MachinLearning']).fullName)
# 출력값 : Employee(firstName='Yang', lastName='SeungSang', fullName='Yang SeungSang', year=1, skills=['MachinLearning'])
Employee 에 fullName 필드를 추가해줬다.
dataclass 는 초기화할 때, 각 필드의 값을 파라미터로 넘겨줘야한다고 했다.
하지만 fullName 은 다른 필드의 값을 통해 생성되는 값이므로, fullName 파라미터는 받을 필요가 없다.
이를 위해, field 의 init 파라미터를 False 로 설정해준다. 이제 초기화 때 파라미터를 받지 않아도 에러가 나지 않는다.
마지막으로, __post_init__ 에서 firstName 과 lastName 을 조합해서 fullName을 생성한다.
물론, fullName 자체를 필드로 선언하지 않고 __post_init__ 에서 처음 선언해도 된다.
그 경우 출력값에 fullName에 대한 정보가 나오지 않는다. 또한 코딩할 때, 필드가 한곳에 모여있지 않으므로 가독성 면에서도 떨어진다고 생각한다.
field(repr=False)
데이터가 많아진다면 객체 출력시 보고싶지 않은 정보도 존재하게 된다.
__post_init__ 에서 firstName 과 lastName 을 조합해서 fullName 을 정의하였다.
그렇다면, firstName 과 lastName 을 굳이 객체 출력시 보여줄 필요가 없을 것이다.
이 때, field(repr=False) 를 사용한다.
from dataclasses import dataclass, field
@dataclass
class Employee:
firstName: str = field(repr=False)
lastName: str = field(repr=False)
fullName: str = field(init=False)
year: int = 1
skills: list[str] = field(default_factory=list)
def __post_init__(self):
self.fullName = f"{self.firstName} {self.lastName}"
print(Employee(firstName="Yang", lastName="SeungSang",
skills=['MachinLearning']))
# 출력값 : Employee(fullName='Yang SeungSang', year=1, skills=['MachinLearning'])
field(repr=False)를 설정하면, 설정한 필드는 출력값에서 보이지 않게 된다.
@dataclass(Frozen=True)
데이터가 변하는지 변하지 않는지는 매우 중요하다. 변수의 값이 변하는지 고려할 필요가 없으면, 코드가 단순해지기 때문이다.
파이썬은 다른 언어와 달리 const 같은 개념이 없었다. (한번 정의한 변수의 값이 변경가능함)
dataclass 를 사용하면 const 용도로도 사용할 수 있다.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Employee:
firstName: str = field(repr=False)
lastName: str = field(repr=False)
fullName: str = field(init=False)
year: int = 1
skills: list[str] = field(default_factory=list)
def __post_init__(self):
self.fullName = f"{self.firstName} {self.lastName}" # << Error
employee = Employee(firstName="Yang", lastName="SeungSang",
skills=['MachinLearning'])
그러나, 위의 예제의 경우에는 @dataclass(frozen=True) 를 할 경우, __post_init__ 에서 fullName 을 정의하면서 에러가 발생한다.
fullName 변수를 초기화 이후(__init__) 할당하지 않는다면 코드는 정상적으로 동작한다.
팩토리 메소드 패턴
위의 상황을 해결하기 위해서 팩토리 메소드 패턴 개념을 활용하면 됩니다.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Employee:
firstName: str = field(repr=False)
lastName: str = field(repr=False)
fullName: str
year: int
skills: list[str] = field(default_factory=list)
@classmethod
def hireEmployee(cls, firstName, lastName, year=1, skills=None):
fullName = f"{firstName} {lastName}"
if skills is None:
skills = []
return cls(firstName=firstName, lastName=lastName, fullName=fullName, year=year, skills=skills)
employee = Employee.hireEmployee(firstName="Yang", lastName="SeungSang",
skills=['MachinLearning'])
print(employee)
# Employee(fullName='Yang SeungSang', year=1, skills=['MachinLearning'])
이해를 위해, 간단히만 적었습니다.
일단, Employee 에 hireEmployee라는 클래스함수를 만들어 객체 생성을 담당하게 합니다.
fullName 은 이때 만들어지며, 실제 객체가 생성될 때, fullName 을 함께 넘겨줍니다. fullName 의 fileld(init=False) 를 삭제합니다.
추가. @dataclass(kw_only=True)
간편한 기능으로 kw_only 기능이 있습니다. 이 기능을 선언하면, 객체 생성시에 keyword를 꼭 입력해야합니다.
명시적으로 keyword 를 사용하게 하면서, 사용시 오류를 줄이도록 강제할 수 있습니다.
from dataclasses import dataclass, field
@dataclass(kw_only=True)
class Employee:
firstName: str = field(repr=False)
lastName: str = field(repr=False)
# employee = Employee("Yang","SeungSang") : Error
employee = Employee(firstName="Yang", lastName="SeungSang")
파이썬에서 정말 유용한 기능이라고 생각합니다. 잘 사용해보시기 바랍니다.
'Python (파이썬)' 카테고리의 다른 글
디자인 패턴 - 팩토리 패턴(Factory Pattern) with 파이썬(python) (2) | 2023.05.29 |
---|---|
self 의 역할 (함수의 메모리) (0) | 2023.01.23 |
파이썬 - Type Hint(or Type Annotation) 를 사용하는 이유 (2) | 2022.12.25 |
파이썬 - Enum (0) | 2022.12.22 |
파이썬 - 왈러스 연산자(Walrus Operator) := (0) | 2022.09.25 |