HOWTO · Python

Python의 데이터 클래스 상속

이 문서에서는 dataclass 필드, 상속, 여러 단계의 상속, 기본값이 있는 필드와 없는 필드의 동작을 설명합니다. 예제를 통해 생성된 초기화 메서드에서 필드가 어떻게 결합되는지 살펴봅니다.

이 페이지의 내용

Python dataclass는 주석이 있는 클래스 속성을 필드로 사용하고 __init__, __repr__ 같은 메서드를 생성합니다. 한 dataclass가 다른 dataclass를 상속하면 생성된 초기화 메서드에는 하위 클래스에서 선언한 필드뿐 아니라 상속된 필드도 포함됩니다. 결과 필드 순서를 이해하면 생성자 인수를 정하고 클래스를 확장할 때 TypeError를 진단하는 데 도움이 됩니다.

dataclass 상속에서 필드가 결합되는 방식

각 클래스에서 선언한 필드를 생성된 dataclass 동작에 포함하려면 기본 클래스와 파생 클래스 모두에 @dataclass를 적용합니다. 기본 클래스의 필드가 먼저 오고, 그 뒤에 하위 클래스에서 새로 선언한 필드가 옵니다. 하위 클래스는 상속된 필드를 재정의할 수도 있습니다. 이때 필드의 위치는 유지되고 기본값처럼 필드의 정의가 바뀝니다. 다음 예제는 생성된 초기화 메서드에 상속된 필드가 하위 클래스의 필드보다 앞서 포함되는 모습을 보여 줍니다.

이 예제를 실행해 보세요.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

from dataclasses import dataclass


@dataclass
class Person:
    name: str
    age: int


@dataclass
class Student(Person):
    school: str


student = Student("Ada Lovelace", 17, "North School")
print(student.name)
print(f"{student.name}: {student.age}, school={student.school}")
Ada Lovelace
Ada Lovelace: 17, school=North School

dataclass는 dataclass 기본 클래스들을 메서드 결정 순서의 역순으로 살펴 필드를 모은 다음, 현재 클래스의 필드를 추가하거나 기존 필드를 대체합니다. 필드를 재정의해도 끝으로 이동하지 않습니다. 이 순서는 생성된 초기화 메서드의 매개변수 순서와 필수 필드가 기본값이 있는 필드 뒤에 오는지를 결정하므로 중요합니다.

여러 단계에 걸쳐 상속된 필드 사용하기

일반 클래스도 여러 단계의 계층을 이룰 수 있지만, 그것만으로 dataclass가 되거나 dataclass 필드가 생성되지는 않습니다. 일반 클래스 계층에서 super()는 클래스의 메서드 결정 순서(MRO)에 따라 다음 구현을 호출합니다. 계층의 나머지 부분과 협력하도록 작성된 각 메서드는 호환되는 인수를 받아야 하며 super()를 호출해야 합니다. 이 방식은 생성자나 다른 메서드에서 유용합니다. 컴파일러가 부모 메서드를 거슬러 올라가는 동작은 아닙니다.

여기서는 각 show 메서드가 다음 구현으로 값을 넘기기 전에 1을 더합니다. 실행 흐름에서 MRO 순서를 확인할 수 있습니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

class Root:
    def show(self, value):
        print("Root:", value)


class Middle(Root):
    def show(self, value):
        print("Middle:", value)
        super().show(value + 1)


class Leaf(Middle):
    def show(self, value):
        print("Leaf:", value)
        super().show(value + 1)


Leaf().show(10)
Leaf: 10
Middle: 11
Root: 12

이 예제는 일반 클래스를 사용해 협력하는 메서드 호출과 dataclass 필드 수집을 분리해서 보여 줍니다. dataclass 상속도 클래스 관계에는 동일한 Python MRO를 따르지만, 필드 수집과 초기화 메서드 생성에는 별도의 규칙이 적용됩니다.

dataclass 필드 순서에서 TypeError가 발생하는 이유

생성된 초기화 메서드에는 기본값이 있는 위치 매개변수 뒤에 필수 위치 매개변수를 둘 수 없습니다. 이 제한은 dataclass 하나 안에서뿐 아니라 상속을 거쳐 필드를 결합할 때도 적용됩니다. 예를 들어 기본 클래스가 필수 필드 name 다음에 active: bool = False를 선언하고, dataclass 하위 클래스가 필수 필드 school을 추가한다고 해 보겠습니다. 필드 순서는 name, active, school이 됩니다. 생성된 시그니처에서 필수 school이 기본값이 있는 매개변수 뒤에 오므로 클래스 생성 시 TypeError: non-default argument ‘school’ follows default argument가 발생합니다.

active를 다른 기본값으로 재정의해도 원래 위치는 바뀌지 않습니다. 따라서 기본값이 있는 필드가 school 뒤로 이동하지 않습니다. 이 오류가 나타나면 하위 클래스 본문만 보지 말고 상속된 필드의 전체 순서를 확인하세요. dataclass 기본 클래스 중 하나의 기본값이 원인일 수 있습니다. 클래스에 실제로 @dataclass가 적용되어 있는지도 확인하세요. 주석이 있는 일반 속성만으로는 dataclass 필드 처리에 참여하지 않습니다.

안전한 해결 방법을 선택하고 생성된 초기화 메서드 확인하기

원하는 API에 맞는 해결 방법을 선택하세요. 하위 클래스의 필수 필드가 위치 인수가 아니어야 한다면 field(kw_only=True)로 키워드 전용 필드로 만드세요. 필수 값은 그대로 유지하면서 위치 매개변수 순서에서는 제외할 수 있습니다. 새 필드에 의미 있는 기본값을 주거나 앞선 필드를 키워드 전용으로 만드는 방법도 있습니다. 필드 순서만 바꾸려고 관련 없는 기본 클래스를 재배치하지는 마세요. 계층을 이해하고 유지하기 어려워집니다.

필드를 바꾼 뒤에는 inspect.signature로 공개 초기화 메서드를 확인하세요. 이 예제에서 school은 여전히 필수지만, 호출할 때 이름을 지정해야 합니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

from dataclasses import dataclass, field
from inspect import signature


@dataclass
class Person:
    name: str
    age: int
    active: bool = False


@dataclass
class Student(Person):
    school: str = field(kw_only=True)
    active: bool = True


print(signature(Student))
print(Student("Mina", 20, school="North School"))
(name: str, age: int, active: bool = True, *, school: str) -> None
Student(name='Mina', age=20, active=True, school='North School')

시그니처를 보면 active는 상속된 위치를 유지하며 하위 클래스의 기본값을 받습니다. school은 * 뒤에 오는 필수 매개변수이므로 키워드로 전달해야 합니다. 시그니처가 의도한 API와 다르면 복잡한 기본 클래스 순서 조정에 의존하지 말고, 상속된 주석과 기본값을 검토한 뒤 dataclass 필드를 조정하세요.