PEP 681 – 데이터 클래스 변환
- Author:
- Erik De Bonte <erikd at microsoft.com>, Eric Traut <erictr at microsoft.com>
- Sponsor:
- Jelle Zijlstra <jelle.zijlstra at gmail.com>
- Discussions-To:
- Typing-SIG thread
- Status:
- Final
- Type:
- Standards Track
- Topic:
- Typing
- Created:
- 02-Dec-2021
- Python-Version:
- 3.11
- Post-History:
- 24-Apr-2021, 13-Dec-2021, 22-Feb-2022
- Resolution:
- Python-Dev message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
PEP 557은 Python 표준 라이브러리에 dataclass를 도입했습니다. 널리 쓰이는 여러 라이브러리가 데이터 클래스와 비슷하게 동작하지만, 이러한 동작은 표준 타입 어노테이션으로 기술할 수 없습니다. 이런 프로젝트로는 attrs, pydantic, 그리고 SQLAlchemy나 Django 같은 객체 관계 매퍼(ORM) 패키지가 있습니다.
대부분의 타입 검사기, 린터, 언어 서버는 데이터 클래스를 완전히 지원합니다. 이 제안의 목표는 이 기능을 일반화하여, 서드파티 라이브러리가 특정 데코레이터 함수, 클래스, 메타클래스가 데이터 클래스와 비슷한 동작을 제공한다는 것을 나타낼 방법을 제공하는 것입니다.
이러한 동작에는 다음이 포함됩니다:
- 선언된 데이터 필드를 바탕으로
__init__메서드를 합성합니다. - 선택적으로
__eq__,__ne__,__lt__,__le__,__gt__,__ge__메서드를 합성합니다. - 정적 타입 검사 중에 불변성을 강제하는 방법인 “frozen” 클래스를 지원합니다.
- 필드에 기본값이 제공되는지 여부처럼, 정적 타입 검사기가 알아야 하는 개별 필드의 속성을 기술하는 “필드 지정자(field specifier)”를 지원합니다.
표준 라이브러리 데이터 클래스의 전체 동작은 파이썬 문서에 설명되어 있습니다.
이 제안은 typing.py에 dataclass_transform 데코레이터를 추가하는 것 외에는 CPython에 직접적인 영향을 주지 않습니다.
동기
데이터 클래스와 비슷한 의미 체계를 가진 라이브러리가 자신의 동작을 타입 검사기에 선언할 수 있는 표준 방법은 현재 없습니다. 이 한계를 우회하기 위해 이러한 라이브러리 중 상당수를 위한 Mypy 사용자 정의 플러그인이 개발되었지만, 이 플러그인들은 다른 타입 검사기, 린터, 언어 서버에서는 동작하지 않습니다. 또한 이러한 플러그인은 라이브러리 작성자가 유지 관리하는 데 비용이 많이 들고, Python 개발자가 이 플러그인의 존재를 알고 자신의 환경에 직접 내려받아 설정해야 합니다.
근거
이 제안의 의도는 데이터 클래스와 비슷한 의미 체계를 가진 모든 라이브러리의 모든 기능을 지원하는 것이 아니라, 이러한 라이브러리의 가장 일반적인 기능을 정적 타입 검사와 호환되는 방식으로 사용할 수 있게 하는 것입니다. 사용자가 이러한 라이브러리와 정적 타입 검사를 모두 중요하게 여긴다면, 특정 기능의 사용을 피하거나 그 기능을 사용하는 방식을 조금 조정해야 할 수도 있습니다. 이는 데이터 클래스와 비슷한 모든 라이브러리의 모든 기능을 지원하지는 않는 Mypy 사용자 정의 플러그인에서도 이미 마찬가지입니다.
앞으로 데이터 클래스에 새 기능이 추가되면, 적절한 경우 dataclass_transform에서도 그 기능을 지원할 계획입니다. 이 두 기능 집합을 동기화된 상태로 유지하면 데이터 클래스 사용자가 dataclass_transform을 이해하고 사용하기가 더 쉬워지고, 타입 검사기에서 데이터 클래스 지원을 유지 보수하는 일도 단순해집니다.
또한 여러 서드파티 라이브러리가 채택했지만 데이터 클래스에서는 지원하지 않는 기능에 대해서도 앞으로 dataclass_transform 지원을 추가하는 것을 검토할 것입니다.
명세
dataclass_transform 데코레이터
이 명세는 typing 모듈에 dataclass_transform이라는 새 데코레이터 함수를 도입합니다. 이 데코레이터는 그 자체로 데코레이터인 함수, 클래스, 메타클래스 중 어디에나 적용할 수 있습니다. dataclass_transform이 있으면 정적 타입 검사기는 데코레이트된 함수, 클래스, 메타클래스가 런타임에 클래스를 변환하여 데이터 클래스와 비슷한 동작을 부여하는 “마법”을 수행한다는 것을 알게 됩니다.
dataclass_transform이 함수에 적용되면, 데코레이트된 함수를 데코레이터로 사용할 때 데이터클래스와 유사한 의미 체계가 적용되는 것으로 간주됩니다. 함수에 오버로드가 있으면 dataclass_transform 데코레이터를 함수의 구현이나 오버로드 중 어느 하나에 적용할 수 있지만, 둘 이상의 오버로드에 적용할 수는 없습니다. 오버로드에 적용하더라도 dataclass_transform 데코레이터는 여전히 해당 함수의 모든 사용에 영향을 줍니다.
dataclass_transform이 클래스에 적용되면, 데코레이트된 클래스로부터 직접 또는 간접적으로 파생되거나 데코레이트된 클래스를 메타클래스로 사용하는 모든 클래스에 데이터클래스와 유사한 의미 체계가 적용되는 것으로 간주됩니다. 데코레이트된 클래스와 그 베이스 클래스에 있는 속성은 필드로 간주되지 않습니다.
각 접근 방식의 예시는 다음 절들에서 보여 드립니다. 각 예시는 데이터클래스와 유사한 의미 체계를 갖는 CustomerModel 클래스를 생성합니다. 간결함을 위해 데코레이트된 객체의 구현은 생략하지만, 이 객체들이 다음과 같은 방식으로 클래스를 수정한다고 가정합니다:
- 클래스와 그 부모 클래스 안에 선언된 데이터 필드를 사용하여
__init__메서드를 합성합니다. __eq__및__ne__메서드를 합성합니다.
이 PEP를 지원하는 타입 검사기는 합성된 __init__ 메서드를 사용하여 CustomerModel 클래스를 인스턴스화할 수 있음을 인식합니다:
# Using positional arguments
c1 = CustomerModel(327, "John Smith")
# Using keyword arguments
c2 = CustomerModel(id=327, name="John Smith")
# These calls will generate runtime errors and should be flagged as
# errors by a static type checker.
c3 = CustomerModel()
c4 = CustomerModel(327, first_name="John")
c5 = CustomerModel(327, "John Smith", 0)
데코레이터 함수 예시
_T = TypeVar("_T")
# The ``create_model`` decorator is defined by a library.
# This could be in a type stub or inline.
@typing.dataclass_transform()
def create_model(cls: Type[_T]) -> Type[_T]:
cls.__init__ = ...
cls.__eq__ = ...
cls.__ne__ = ...
return cls
# The ``create_model`` decorator can now be used to create new model
# classes, like this:
@create_model
class CustomerModel:
id: int
name: str
클래스 예시
# The ``ModelBase`` class is defined by a library. This could be in
# a type stub or inline.
@typing.dataclass_transform()
class ModelBase: ...
# The ``ModelBase`` class can now be used to create new model
# subclasses, like this:
class CustomerModel(ModelBase):
id: int
name: str
메타클래스 예시
# The ``ModelMeta`` metaclass and ``ModelBase`` class are defined by
# a library. This could be in a type stub or inline.
@typing.dataclass_transform()
class ModelMeta(type): ...
class ModelBase(metaclass=ModelMeta): ...
# The ``ModelBase`` class can now be used to create new model
# subclasses, like this:
class CustomerModel(ModelBase):
id: int
name: str
데코레이터 함수 및 클래스/메타클래스 매개변수
데이터클래스와 유사한 기능을 제공하는 데코레이터 함수, 클래스 또는 메타클래스는 특정 동작을 수정하는 매개변수를 받을 수 있습니다. 이 명세는 데이터클래스 변환에서 사용될 경우 정적 타입 검사기가 반드시 따라야 하는 다음 매개변수들을 정의합니다. 이 매개변수들은 각각 bool 인자를 받으며, 그 bool 값(True 또는 False)은 정적으로 평가될 수 있어야 합니다.
eq,order,frozen,init,unsafe_hash는 표준 라이브러리 dataclass에서 지원하는 매개변수이며, 그 의미는 PEP 557에 정의되어 있습니다.kw_only,match_args,slots는 표준 라이브러리 dataclass에서 지원하는 매개변수로, Python 3.10에서 처음 도입되었습니다.
dataclass_transform 매개변수
dataclass_transform의 매개변수를 사용하면 기본 동작을 몇 가지 기본적인 수준에서 사용자 정의할 수 있습니다:
_T = TypeVar("_T")
def dataclass_transform(
*,
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_specifiers: tuple[type | Callable[..., Any], ...] = (),
**kwargs: Any,
) -> Callable[[_T], _T]: ...
eq_default는 호출자가eq매개변수를 생략했을 때 이를 True로 가정할지 False로 가정할지를 나타냅니다. 지정하지 않으면eq_default의 기본값은 True입니다(dataclass의 기본 가정).order_default는 호출자가order매개변수를 생략했을 때 이를 True로 가정할지 False로 가정할지를 나타냅니다. 지정하지 않으면order_default의 기본값은 False입니다(dataclass의 기본 가정).kw_only_default는 호출자가kw_only매개변수를 생략했을 때 이를 True로 가정할지 False로 가정할지를 나타냅니다. 지정하지 않으면kw_only_default의 기본값은 False입니다(dataclass의 기본 가정).field_specifiers는 필드를 기술하는 지원 클래스들의 정적 목록을 지정합니다. 일부 라이브러리는 필드 지정자의 인스턴스를 할당하는 함수도 제공하며, 이러한 함수도 이 튜플에 지정할 수 있습니다. 지정하지 않으면field_specifiers의 기본값은 빈 튜플입니다(필드 지정자를 지원하지 않음). 표준 dataclass 동작은Field라는 한 가지 유형의 필드 지정자와 이 클래스를 인스턴스화하는 헬퍼 함수(field)만 지원하므로, 표준 라이브러리 dataclass 동작을 기술한다면 튜플 인자(dataclasses.Field, dataclasses.field)를 제공하게 됩니다.kwargs를 사용하면dataclass_transform에 임의의 추가 키워드 인자를 전달할 수 있습니다. 이를 통해 타입 검사기는typing.py의 변경을 기다릴 필요 없이 실험적인 매개변수를 자유롭게 지원할 수 있습니다. 타입 검사기는 인식하지 못하는 매개변수에 대해 오류를 보고해야 합니다.
앞으로 사용자 코드의 일반적인 동작을 지원하기 위해 필요에 따라 dataclass_transform에 매개변수를 추가할 수 있습니다. 이러한 추가는 별도의 PEP를 거치지 않고 typing-sig에서 합의에 도달한 후 이루어집니다.
다음 절에서는 이러한 매개변수가 어떻게 사용되는지 보여 주는 추가 예제를 제공합니다.
데코레이터 함수 예제
# Indicate that the ``create_model`` function assumes keyword-only
# parameters for the synthesized ``__init__`` method unless it is
# invoked with ``kw_only=False``. It always synthesizes order-related
# methods and provides no way to override this behavior.
@typing.dataclass_transform(kw_only_default=True, order_default=True)
def create_model(
*,
frozen: bool = False,
kw_only: bool = True,
) -> Callable[[Type[_T]], Type[_T]]: ...
# Example of how this decorator would be used by code that imports
# from this library:
@create_model(frozen=True, kw_only=False)
class CustomerModel:
id: int
name: str
클래스 예제
# Indicate that classes that derive from this class default to
# synthesizing comparison methods.
@typing.dataclass_transform(eq_default=True, order_default=True)
class ModelBase:
def __init_subclass__(
cls,
*,
init: bool = True,
frozen: bool = False,
eq: bool = True,
order: bool = True,
):
...
# Example of how this class would be used by code that imports
# from this library:
class CustomerModel(
ModelBase,
init=False,
frozen=True,
eq=False,
order=False,
):
id: int
name: str
메타클래스 예제
# Indicate that classes that use this metaclass default to
# synthesizing comparison methods.
@typing.dataclass_transform(eq_default=True, order_default=True)
class ModelMeta(type):
def __new__(
cls,
name,
bases,
namespace,
*,
init: bool = True,
frozen: bool = False,
eq: bool = True,
order: bool = True,
):
...
class ModelBase(metaclass=ModelMeta):
...
# Example of how this class would be used by code that imports
# from this library:
class CustomerModel(
ModelBase,
init=False,
frozen=True,
eq=False,
order=False,
):
id: int
name: str
필드 지정자
데이터클래스와 유사한 의미 체계를 지원하는 대부분의 라이브러리는 클래스 정의에서 클래스의 각 필드에 대한 추가 메타데이터를 제공할 수 있도록 하나 이상의 “필드 지정자” 타입을 제공합니다. 예를 들어 이 메타데이터는 기본값을 기술하거나, 해당 필드를 합성된 __init__ 메서드에 포함할지 여부를 나타낼 수 있습니다.
추가 메타데이터가 필요하지 않은 경우에는 필드 지정자를 생략할 수 있습니다:
@dataclass
class Employee:
# Field with no specifier
name: str
# Field that uses field specifier class instance
age: Optional[int] = field(default=None, init=False)
# Field with type annotation and simple initializer to
# describe default value
is_paid_hourly: bool = True
# Not a field (but rather a class variable) because type
# annotation is not provided.
office_number = "unassigned"
필드 지정자 매개변수
데이터클래스와 유사한 의미 체계를 지원하고 필드 지정자 클래스를 지원하는 라이브러리는 일반적으로 공통된 매개변수 이름을 사용하여 필드 지정자를 생성합니다. 이 명세는 정적 타입 검사기가 이해해야 하는 매개변수의 이름과 의미를 공식화합니다. 이러한 표준화된 매개변수는 키워드 전용이어야 합니다.
이 매개변수들은 dataclasses.field가 지원하는 매개변수의 상위 집합이며, compare와 hash처럼 타입 검사에 영향을 주지 않는 매개변수는 제외합니다.
필드 지정자 클래스는 생성자에서 다른 매개변수를 사용할 수 있으며, 그러한 매개변수는 위치 매개변수일 수 있고 다른 이름을 사용할 수도 있습니다.
init은 해당 필드를 합성된__init__메서드에 포함할지 여부를 나타내는 선택적 bool 매개변수입니다. 지정하지 않으면init의 기본값은 True입니다. 필드 지정자 함수는 리터럴 bool 값 타입(Literal[False]또는Literal[True])을 사용하여init의 값을 암묵적으로 지정하는 오버로드를 사용할 수 있습니다.default는 필드의 기본값을 제공하는 선택적 매개변수입니다.default_factory는 필드의 기본값을 반환하는 런타임 콜백을 제공하는 선택적 매개변수입니다.default와default_factory가 모두 지정되지 않으면 해당 필드에는 기본값이 없는 것으로 간주되며, 클래스를 인스턴스화할 때 값을 반드시 제공해야 합니다.factory는default_factory의 별칭입니다. 표준 라이브러리의 dataclasses는default_factory라는 이름을 사용하지만, attrs는 여러 상황에서factory라는 이름을 사용하므로 attrs를 지원하려면 이 별칭이 필요합니다.kw_only는 해당 필드를 키워드 전용으로 표시할지 여부를 나타내는 선택적 bool 매개변수입니다. 참이면 해당 필드는 키워드 전용이 됩니다. 거짓이면 키워드 전용이 되지 않습니다. 지정하지 않으면dataclass_transform으로 데코레이트된 객체에 지정된kw_only매개변수의 값이 사용되며, 그 값도 지정되지 않았다면dataclass_transform의kw_only_default값이 사용됩니다.alias는 필드의 대체 이름을 제공하는 선택적 str 매개변수입니다. 이 대체 이름은 합성된__init__메서드에서 사용됩니다.
default, default_factory, factory 중 둘 이상을 지정하는 것은 오류입니다.
다음 예제는 위 내용을 보여 줍니다:
# Library code (within type stub or inline)
# In this library, passing a resolver means that init must be False,
# and the overload with Literal[False] enforces that.
@overload
def model_field(
*,
default: Optional[Any] = ...,
resolver: Callable[[], Any],
init: Literal[False] = False,
) -> Any: ...
@overload
def model_field(
*,
default: Optional[Any] = ...,
resolver: None = None,
init: bool = True,
) -> Any: ...
@typing.dataclass_transform(
kw_only_default=True,
field_specifiers=(model_field, ))
def create_model(
*,
init: bool = True,
) -> Callable[[Type[_T]], Type[_T]]: ...
# Code that imports this library:
@create_model(init=False)
class CustomerModel:
id: int = model_field(resolver=lambda : 0)
name: str
런타임 동작
런타임에 dataclass_transform 데코레이터가 하는 유일한 일은 인트로스펙션을 지원하기 위해 데코레이트된 함수나 클래스에 __dataclass_transform__이라는 속성을 설정하는 것입니다. 이 속성의 값은 dataclass_transform 매개변수의 이름을 그 값에 매핑하는 dict여야 합니다.
예를 들면 다음과 같습니다:
{
"eq_default": True,
"order_default": False,
"kw_only_default": False,
"field_specifiers": (),
"kwargs": {}
}
데이터클래스 의미론
이 PEP에서 달리 명시하지 않는 한, dataclass_transform으로 데코레이트된 클래스를 상속하거나 dataclass_transform으로 데코레이트된 함수로 데코레이트되어 dataclass_transform의 영향을 받는 클래스는 표준 라이브러리의 dataclass처럼 동작한다고 가정합니다.
여기에는 다음 의미론이 포함되며, 이에 국한되지 않습니다:
- 동결된(frozen) 데이터클래스는 동결되지 않은 데이터클래스를 상속할 수 없습니다.
dataclass_transform으로 데코레이트된 클래스는 동결된 것으로도, 동결되지 않은 것으로도 간주되지 않으므로 동결된 클래스가 이를 상속할 수 있습니다. 마찬가지로,dataclass_transform으로 데코레이트된 메타클래스를 직접 지정하는 클래스도 동결된 것으로도, 동결되지 않은 것으로도 간주되지 않습니다.다음 클래스 예제를 살펴보십시오:
# ModelBase is not considered either "frozen" or "non-frozen" # because it is decorated with ``dataclass_transform`` @typing.dataclass_transform() class ModelBase(): ... # Vehicle is considered non-frozen because it does not specify # "frozen=True". class Vehicle(ModelBase): name: str # Car is a frozen class that derives from Vehicle, which is a # non-frozen class. This is an error. class Car(Vehicle, frozen=True): wheel_count: int
그리고 다음과 비슷한 메타클래스 예제도 살펴보십시오:
@typing.dataclass_transform() class ModelMeta(type): ... # ModelBase is not considered either "frozen" or "non-frozen" # because it directly specifies ModelMeta as its metaclass. class ModelBase(metaclass=ModelMeta): ... # Vehicle is considered non-frozen because it does not specify # "frozen=True". class Vehicle(ModelBase): name: str # Car is a frozen class that derives from Vehicle, which is a # non-frozen class. This is an error. class Car(Vehicle, frozen=True): wheel_count: int
- 필드 순서와 상속은 557에 명시된 규칙을 따른다고 가정합니다. 여기에는 오버라이드(부모 클래스에 이미 정의된 필드를 자식 클래스에서 다시 정의하는 것)의 효과도 포함됩니다.
- PEP 557에 따르면 기본값이 없는 모든 필드는 기본값이 있는 필드보다 앞에 나와야 합니다. PEP 557에 명시적으로 언급되어 있지는 않지만 이 규칙은
init=False일 때 무시되며, 이 명세도 그러한 상황에서는 마찬가지로 이 요구 사항을 무시합니다. 마찬가지로,__init__에 키워드 전용 매개변수를 사용할 때는 이 순서를 강제할 필요가 없으므로kw_only의미론이 적용되는 경우에는 이 규칙을 강제하지 않습니다. dataclass와 마찬가지로, 메서드 합성이 클래스 안에 명시적으로 선언된 메서드를 덮어쓰게 되는 경우에는 합성을 건너뜁니다. 베이스 클래스에 있는 메서드 선언 때문에 메서드 합성을 건너뛰지는 않습니다.예를 들어, 클래스가
__init__메서드를 명시적으로 선언하면 그 클래스에 대해서는__init__메서드가 합성되지 않습니다.- KW_ONLY 센티널 값은 파이썬 문서와 bpo-43532에 설명된 대로 지원됩니다.
- ClassVar 속성은 데이터클래스 필드로 간주되지 않으며 데이터클래스 메커니즘에서 무시됩니다.
정의되지 않은 동작
단일 함수(오버로드 포함), 단일 클래스 또는 클래스 계층 구조 안에서 dataclass_transform 데코레이터가 여러 개 발견되면 그 결과 동작은 정의되지 않습니다. 라이브러리 작성자는 이러한 상황을 피해야 합니다.
참조 구현
Pyright에는 dataclass_transform에 대한 타입 검사기 지원의 참조 구현이 포함되어 있습니다. Pyright의 dataClasses.ts 소스 파일은 구현을 이해하기 위한 좋은 출발점이 될 것입니다.
attrs와 pydantic 라이브러리는 dataclass_transform을 사용하고 있으며, 그 사용법을 보여 주는 실제 사례입니다.
기각된 아이디어
auto_attribs 매개변수
attrs 라이브러리는 PEP 526 변수 어노테이션이 붙었지만 할당이 없는 클래스 멤버를 데이터 필드로 취급할지 여부를 나타내는 auto_attribs 매개변수를 지원합니다.
auto_attribs와 이에 대응하는 auto_attribs_default 매개변수를 지원하는 것을 고려했지만, attrs에만 해당하는 것이므로 지원하지 않기로 했습니다.
Django는 타입 어노테이션만으로 필드를 선언하는 것을 지원하지 않으므로, dataclass_transform을 활용하는 Django 사용자는 항상 값을 할당해야 한다는 점에 유의해야 합니다.
cmp 매개변수
attrs 라이브러리는 eq와 order를 모두 True로 설정하는 것과 동등한 bool 매개변수 cmp를 지원합니다. cmp 매개변수는 attrs에만 적용되므로 지원하지 않기로 했습니다. 사용자는 대신 eq와 order 매개변수 이름을 사용하여 cmp의 동작을 흉내 낼 수 있습니다.
자동 필드 이름 별칭
attrs 라이브러리는 밑줄 하나로 시작하는 필드 이름에 대해 자동 별칭 부여를 수행하여, 대응하는 __init__ 매개변수의 이름에서 밑줄을 제거합니다.
이 동작은 attrs에만 해당하므로 이 제안에서는 생략합니다. 사용자는 alias 매개변수를 사용하여 이러한 필드에 수동으로 별칭을 지정할 수 있습니다.
대체 필드 순서 결정 알고리즘
attrs 라이브러리는 현재 클래스 내 필드의 순서를 정하는 두 가지 방식을 지원합니다.
- 데이터클래스 순서: 데이터클래스에서 사용하는 것과 같은 순서입니다. 이는 이전 API(예:
attr.s)의 기본 동작입니다. - 메서드 결정 순서(MRO): 이는 새로운 API(예: define, mutable, frozen)의 기본 동작입니다. 이전 API(예:
attr.s)는collect_by_mro=True를 지정하여 이 동작을 선택할 수 있습니다.
특정한 다이아몬드 형태의 다중 상속 시나리오에서는 결과로 나오는 필드 순서가 달라질 수 있습니다.
단순성을 위해, 이 제안은 데이터클래스에서 사용하는 순서 외의 다른 필드 순서는 지원하지 않습니다.
서브클래스에서 재선언된 필드
attrs 라이브러리는 서브클래스에서 재선언된 상속 필드를 처리하는 방식이 표준 라이브러리의 데이터클래스와 다릅니다. 데이터클래스 명세는 원래 순서를 유지하지만, attrs는 서브클래스를 기준으로 새로운 순서를 정합니다.
단순성을 위해, 데이터클래스의 동작만 지원하기로 했습니다. attrs 고유의 순서에 의존하는 attrs 사용자는 합성된 __init__ 메서드에서 기대한 매개변수 순서를 얻지 못합니다.
Django의 기본 키와 외래 키
Django는 기본 키와 외래 키에 대한 추가 로직을 적용합니다. 예를 들어, 기본 키로 지정된 필드가 없으면 id 필드(및 __init__ 매개변수)를 자동으로 추가합니다.
이는 데이터클래스 라이브러리 전반에 널리 적용되는 것이 아니므로 이 제안에서는 이 추가 로직을 수용하지 않으며, 따라서 Django 사용자는 id 필드를 명시적으로 선언해야 합니다.
클래스 전체 기본값
SQLAlchemy는 변환된 클래스의 모든 필드의 기본값이 None임을 지정하는 방법을 제공해 달라고 요청했습니다. SQLAlchemy 필드는 일반적으로 모두 선택 사항이며, None은 필드가 설정되지 않았음을 나타냅니다.
이 기능은 SQLAlchemy에 특화된 것이므로 지원하지 않기로 했습니다. 대신 사용자가 이러한 필드에 default=None을 직접 설정할 수 있습니다.
디스크립터 타입 필드 지원
SQLAlchemy에서 흔히 쓰이는 디스크립터 타입 필드를 더 잘 지원하기 위해 dataclass_transform에 불리언 매개변수를 추가하는 방안을 검토했습니다. 이 매개변수를 활성화하면, 합성된 __init__ 메서드에서 디스크립터 타입 필드에 대응하는 각 매개변수의 타입은 디스크립터 타입 자체가 아니라 디스크립터의 __set__ 메서드가 받는 value 매개변수의 타입이 됩니다. 마찬가지로 필드를 설정할 때는 __set__의 value 타입이 요구됩니다. 그리고 필드의 값을 가져올 때는 그 타입이 __get__의 반환 타입과 일치할 것으로 예상됩니다.
이 아이디어는 dataclass가 디스크립터 타입 필드를 제대로 지원하지 않는다는 생각에서 출발했습니다. 실제로는 지원하지만, 타입 검사기(적어도 mypy와 pyright)가 런타임 동작을 반영하지 않았기 때문에 저희가 잘못 이해했던 것입니다. 자세한 내용은 Pyright 버그를 참고하십시오.
converter 필드 지정자 매개변수
attrs 라이브러리는 converter 필드 지정자 매개변수를 지원합니다. 이 매개변수는 생성된 __init__ 메서드가 전달받은 값을 원하는 다른 값으로 변환하기 위해 호출하는 Callable입니다. 합성된 __init__ 메서드의 매개변수 타입은 변환되지 않은 값을 받아야 하지만, 결과 필드의 타입은 변환기의 출력에 따라 정해지기 때문에 이를 지원하기가 까다롭습니다.
이 문제의 몇 가지 측면은 Pyright 논의에 자세히 설명되어 있습니다.
입력 매개변수의 타입을 도출하기에 충분한 정보가 없으므로 이를 지원할 좋은 방법이 없을 수도 있습니다. 한 가지 가능한 해결책은 converter 필드 지정자 매개변수를 지원하되, __init__ 메서드의 해당 매개변수에는 Any 타입을 사용하는 것입니다.
참고 문헌
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.