python 데이터 클래스 __init_ 메서드에서 강제 형식 변환
python 데이터 클래스 __init_ 메서드에서 강제 형식 변환
다음과 같은 매우 간단한 데이터 클래스가 있습니다.
import dataclasses
@dataclasses.dataclass
class Test:
value: int
클래스의 인스턴스를 만들지만 정수 대신 문자열을 사용합니다.
>>> test = Test('1')
>>> type(test.value)
<class 'str'>
내가 실제로 원하는 것은 클래스 정의에 정의된 데이터 유형으로 강제 변환하는 것입니다.
>>> test = Test('1')
>>> type(test.value)
<class 'int'>
방법을 수동으로 작성해야 하나요, 아니면 간단한 방법이 있나요?
데이터 클래스 속성의 유형 힌트는 유형이 시행되거나 검사된다는 점에서 절대 준수되지 않습니다. 와 같은 정적 유형 체커가 대부분 이 작업을 수행할 것으로 예상되며, 파이썬은 런타임에 이 작업을 수행하지 않습니다.
수동 유형 검사 코드를 추가하려면 다음 방법을 사용하십시오.
@dataclasses.dataclass
class Test:
value: int
def __post_init__(self):
if not isinstance(self.value, int):
raise ValueError('value not an int')
# or self.value = int(self.value)
를 사용하여 필드와 유형을 지정하는 개체의 튜플을 가져와 각 필드에 대해 개별적으로 쓰지 않고 자동으로 이 작업을 수행할 수 있습니다.
def __post_init__(self):
for field in dataclasses.fields(self):
value = getattr(self, field.name)
if not isinstance(value, field.type):
raise ValueError(f'Expected {field.name} to be {field.type}, '
f'got {repr(value)}')
# or setattr(self, field.name, field.type(value))
다음 방법을 사용하여 이를 달성할 수 있습니다.
import dataclasses
@dataclasses.dataclass
class Test:
value : int
def __post_init__(self):
self.value = int(self.value)
이 메서드는 메서드를 따라 호출됩니다.
https://docs.python.org/3/library/dataclasses.html#post-init-processing
네, 쉬운 답은 그냥 당신 스스로 변환하는 거예요. 제가 원하는 건 제 물건을 원해서 하는 거예요.
형식 검증을 위해 Pydandic이 한다고 주장하지만, 나는 아직 시도하지 않았다.
를 사용하면 쉽게 달성할 수 있습니다.
데이터 클래스에서 데코레이터를 사용하십시오.
from dataclasses import dataclass
from pydantic import validate_arguments
@validate_arguments
@dataclass
class Test:
value: int
그런 다음 데모를 시도해 보십시오. 'str type' 1이 에서 로 변환됩니다.
>>> test = Test('1')
>>> type(test.value)
<class 'int'>
정말 잘못된 유형을 통과하면 예외가 발생합니다.
>>> test = Test('apple')
Traceback (most recent call last):
...
pydantic.error_wrappers.ValidationError: 1 validation error for Test
value
value is not a valid integer (type=type_error.integer)
파이썬을 사용하면 다른 답변에서 지적한 바와 같이 메소드를 사용할 수 있습니다.
@dataclasses.dataclass
class Test:
value: int
def __post_init__(self):
self.value = int(self.value)
>>> test = Test("42")
>>> type(test.value)
<class 'int'>
또는 패키지를 사용하여 쉽게 설정할 수 있습니다.
@attr.define
class Test:
value: int = attr.field(converter=int)
>>> test = Test("42")
>>> type(test.value)
<class 'int'>
데이터가 매핑에서 가져온 경우 의 유형 주석을 기반으로 변환하는 패키지를 사용할 수 있습니다.
@dataclasses.dataclass
class Test:
value: int
>>> test = cattrs.structure({"value": "42"}, Test)
>>> type(test.value)
<class 'int'>
에서는 의 필드 유형에 따라 자동으로 변환을 수행합니다.
class Test(pydantic.BaseModel):
value: int
>>> test = Test(value="42")
>>> type(test.value)
<class 'int'>
설명자 유형 필드를 사용할 수 있습니다.
class IntConversionDescriptor:
def __set_name__(self, owner, name):
self._name = "_" + name
def __get__(self, instance, owner):
return getattr(instance, self._name)
def __set__(self, instance, value):
setattr(instance, self._name, int(value))
@dataclass
class Test:
value: IntConversionDescriptor = IntConversionDescriptor()
>>> test = Test(value=1)
>>> type(test.value)
<class 'int'>
>>> test = Test(value="12")
>>> type(test.value)
<class 'int'>
test.value = "145"
>>> type(test.value)
<class 'int'>
test.value = 45.12
>>> type(test.value)
<class 'int'>
에 선언된 일반 유형 변환을 사용할 수 있습니다.
import sys
class TypeConv:
__slots__ = (
'_name',
'_default_factory',
)
def __init__(self, default_factory=None):
self._default_factory = default_factory
def __set_name__(self, owner, name):
self._name = "_" + name
if self._default_factory is None:
# determine default factory from the type annotation
tp = owner.__annotations__[name]
if isinstance(tp, str):
# evaluate the forward reference
base_globals = getattr(sys.modules.get(owner.__module__, None), '__dict__', {})
idx_pipe = tp.find('|')
if idx_pipe != -1:
tp = tp[:idx_pipe].rstrip()
tp = eval(tp, base_globals)
# use `__args__` to handle `Union` types
self._default_factory = getattr(tp, '__args__', [tp])[0]
def __get__(self, instance, owner):
return getattr(instance, self._name)
def __set__(self, instance, value):
setattr(instance, self._name, self._default_factory(value))
의 용도는 다음과 같습니다.
from __future__ import annotations
from dataclasses import dataclass
from descriptors import TypeConv
@dataclass
class Test:
value: int | str = TypeConv()
test = Test(value=1)
print(test)
test = Test(value='12')
print(test)
# watch out: the following assignment raises a `ValueError`
try:
test.value = '3.21'
except ValueError as e:
print(e)
출력:
Test(value=1)
Test(value=12)
invalid literal for int() with base 10: '3.21'
이 기능은 다른 단순 유형에서는 작동하지만, 또는 등의 특정 유형에 대한 변환은 일반적으로 예상되는 대로 처리하지 않습니다.
이 작업에 타사 라이브러리를 사용하는 것이 괜찮으시다면, 다음을 호출할 때만 필요에 따라 유형 변환을 수행할 수 있는 (d)serialization 라이브러리를 생각해 냈습니다.
from __future__ import annotations
from dataclasses import dataclass
from dataclass_wizard import JSONWizard
@dataclass
class Test(JSONWizard):
value: int
is_active: bool
test = Test.from_dict({'value': '123', 'is_active': 'no'})
print(repr(test))
assert test.value == 123
assert not test.is_active
test = Test.from_dict({'is_active': 'tRuE', 'value': '3.21'})
print(repr(test))
assert test.value == 3
assert test.is_active
왜 안 써요?
from dataclasses import dataclass, fields
@dataclass()
class Test:
value: int
def __post_init__(self):
for field in fields(self):
setattr(self, field.name, field.type(getattr(self, field.name)))
필요한 결과를 산출합니다.
>>> test = Test('1')
>>> type(test.value)
<class 'int'>