데이터 클래스가 속성을 결합하는 방식은 기본 클래스에서 기본값이있는 속성을 사용한 다음 하위 클래스에서 기본값이없는 속성 (위치 속성)을 사용할 수 없도록합니다.
MRO의 맨 아래에서 시작하여 처음 본 순서로 정렬 된 속성 목록을 작성하여 속성이 결합되기 때문입니다. 재 지정은 원래 위치에 유지됩니다. 따라서 기본값이있는 에서 Parent
시작하여 해당 목록의 끝에 추가 합니다 ( 이미 목록에 있음). 즉 , 기본값이 없기 때문에 에 대한 잘못된 인수 목록이 생성됩니다 .['name', 'age', 'ugly']
ugly
Child
['school']
ugly
['name', 'age', 'ugly', 'school']
school
__init__
이것은 상속 아래 PEP-557 데이터 클래스에 문서화되어 있습니다 .
@dataclass
데코레이터에 의해 데이터 클래스가 생성 될 때, 역 MRO (즉,에서 시작)에서 클래스의 모든 기본 클래스 object
를 살펴보고 찾은 각 데이터 클래스에 대해 해당 기본 클래스의 필드를 정렬 된 필드에 추가합니다. 필드 매핑. 모든 기본 클래스 필드가 추가 된 후 정렬 된 매핑에 자체 필드를 추가합니다. 생성 된 모든 메서드는이 결합되고 계산 된 정렬 된 필드 매핑을 사용합니다. 필드가 삽입 순서이므로 파생 클래스가 기본 클래스를 재정의합니다.
그리고 사양에서 :
TypeError
기본값이없는 필드가 기본값이있는 필드 뒤에 오는 경우 발생합니다. 이것은 단일 클래스에서 발생하거나 클래스 상속의 결과로 발생하는 경우에 해당됩니다.
이 문제를 방지하기위한 몇 가지 옵션이 있습니다.
첫 번째 옵션은 별도의 기본 클래스를 사용하여 기본값이있는 필드를 MRO 순서의 나중 위치로 강제하는 것입니다. 어쨌든 기본 클래스로 사용할 클래스에 필드를 직접 설정하지 마십시오 Parent
.
다음 클래스 계층이 작동합니다.
@dataclass
class _ParentBase:
name: str
age: int
@dataclass
class _ParentDefaultsBase:
ugly: bool = False
@dataclass
class _ChildBase(_ParentBase):
school: str
@dataclass
class _ChildDefaultsBase(_ParentDefaultsBase):
ugly: bool = True
@dataclass
class Parent(_ParentDefaultsBase, _ParentBase):
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f"The Name is {self.name} and {self.name} is {self.age} year old")
@dataclass
class Child(Parent, _ChildDefaultsBase, _ChildBase):
pass
기본값이없는 필드와 기본값이있는 필드, 신중하게 선택된 상속 순서 를 사용하여 필드를 별도의 기본 클래스 로 가져 오면 기본값이없는 모든 필드를 기본값이있는 필드보다 먼저 배치하는 MRO를 생성 할 수 있습니다. 에 대한 반전 된 MRO (무시 object
) Child
는 다음과 같습니다.
_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent
주 Parent
, 새로운 필드를 설정하지 않습니다 그래서 그것이 순서를 나열 분야에서 '마지막'끝나는 것을 여기에 문제가되지 않습니다. 기본값 (없는 필드 클래스 _ParentBase
와는 _ChildBase
) 디폴트 (와 필드 클래스를 선행 _ParentDefaultsBase
하고 _ChildDefaultsBase
).
결과는 Parent
하고 Child
있지만, 나이가 제정신이 필드 클래스 Child
아직도의 서브 클래스입니다 Parent
:
>>> from inspect import signature
>>> signature(Parent)
<Signature (name: str, age: int, ugly: bool = False) -> None>
>>> signature(Child)
<Signature (name: str, age: int, school: str, ugly: bool = True) -> None>
>>> issubclass(Child, Parent)
True
따라서 두 클래스의 인스턴스를 만들 수 있습니다.
>>> jack = Parent('jack snr', 32, ugly=True)
>>> jack_son = Child('jack jnr', 12, school='havard', ugly=True)
>>> jack
Parent(name='jack snr', age=32, ugly=True)
>>> jack_son
Child(name='jack jnr', age=12, school='havard', ugly=True)
또 다른 옵션은 기본값이있는 필드 만 사용하는 것입니다. 에서 school
값을 올리면 여전히 오류가 발생하여 값 을 제공하지 않을 수 있습니다 __post_init__
.
_no_default = object()
@dataclass
class Child(Parent):
school: str = _no_default
ugly: bool = True
def __post_init__(self):
if self.school is _no_default:
raise TypeError("__init__ missing 1 required argument: 'school'")
그러나 이것은 수행 필드 순서를 변경하는 행위 school
다음에 끝납니다 ugly
.
<Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> None>
유형 힌트 검사기 는_no_default
문자열이 아니라고 불평 합니다.
당신은 또한 사용할 수있는 attrs
프로젝트 영감을하는 프로젝트였다 dataclasses
. 다른 상속 병합 전략을 사용합니다. 하위 클래스의 재정의 된 필드를 필드 목록의 끝으로 가져 오므로 클래스 ['name', 'age', 'ugly']
에서 Parent
클래스가 ['name', 'age', 'school', 'ugly']
됩니다 Child
. 필드를 기본값으로 attrs
재정의하면 MRO 댄스를 할 필요없이 재정의가 허용됩니다.
attrs
유형 힌트없이 필드 정의를 지원하지만 다음 을 설정 하여 지원되는 유형 힌트 모드 를 고수 할 수 있습니다 auto_attribs=True
.
import attr
@attr.s(auto_attribs=True)
class Parent:
name: str
age: int
ugly: bool = False
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f"The Name is {self.name} and {self.name} is {self.age} year old")
@attr.s(auto_attribs=True)
class Child(Parent):
school: str
ugly: bool = True