Cách dataclasses kết hợp các thuộc tính ngăn bạn không thể sử dụng các thuộc tính có giá trị mặc định trong lớp cơ sở và sau đó sử dụng các thuộc tính không có mặc định (thuộc tính vị trí) trong lớp con.
Đó là bởi vì các thuộc tính được kết hợp bằng cách bắt đầu từ cuối MRO và xây dựng một danh sách có thứ tự các thuộc tính theo thứ tự nhìn thấy đầu tiên; ghi đè được giữ ở vị trí ban đầu của chúng. Vì vậy, hãy Parent
bắt đầu với ['name', 'age', 'ugly']
, nơi ugly
có mặc định, và sau đó Child
thêm ['school']
vào cuối danh sách đó (với ugly
đã có trong danh sách). Điều này có nghĩa là bạn kết thúc với ['name', 'age', 'ugly', 'school']
và bởi vì school
không có mặc định, điều này dẫn đến danh sách đối số không hợp lệ cho __init__
.
Điều này được ghi lại trong PEP-557 Dataclasses , dưới sự kế thừa :
Khi Lớp dữ liệu đang được tạo bởi người @dataclass
trang trí, nó sẽ xem xét tất cả các lớp cơ sở của lớp trong MRO ngược lại (nghĩa là bắt đầu từ object
) và đối với mỗi Lớp dữ liệu mà nó tìm thấy, thêm các trường từ lớp cơ sở đó vào một thứ tự lập bản đồ các lĩnh vực. Sau khi tất cả các trường của lớp cơ sở được thêm vào, nó sẽ thêm các trường của chính nó vào ánh xạ có thứ tự. Tất cả các phương thức được tạo sẽ sử dụng ánh xạ có thứ tự được tính toán kết hợp này của các trường. Vì các trường theo thứ tự chèn, các lớp dẫn xuất sẽ ghi đè các lớp cơ sở.
và dưới Đặc điểm kỹ thuật :
TypeError
sẽ được nâng lên nếu một trường không có giá trị mặc định theo sau một trường có giá trị mặc định. Điều này đúng khi điều này xảy ra trong một lớp đơn lẻ hoặc là kết quả của việc kế thừa lớp.
Bạn có một số tùy chọn ở đây để tránh vấn đề này.
Tùy chọn đầu tiên là sử dụng các lớp cơ sở riêng biệt để buộc các trường có giá trị mặc định vào vị trí sau đó trong thứ tự MRO. Bằng mọi giá, hãy tránh đặt các trường trực tiếp trên các lớp sẽ được sử dụng làm lớp cơ sở, chẳng hạn như Parent
.
Hệ thống phân cấp lớp sau hoạt động:
@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
Bằng cách kéo các trường thành các lớp cơ sở riêng biệt với các trường không có giá trị mặc định và các trường có giá trị mặc định và thứ tự kế thừa được lựa chọn cẩn thận, bạn có thể tạo một MRO đặt tất cả các trường không có giá trị mặc định trước những trường có giá trị mặc định. MRO đảo ngược (bỏ qua object
) Child
là:
_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent
Lưu ý rằng điều Parent
đó không thiết lập bất kỳ trường mới nào, vì vậy không quan trọng ở đây nó kết thúc 'cuối cùng' trong thứ tự danh sách trường. Các lớp có trường không có giá trị mặc định ( _ParentBase
và _ChildBase
) đứng trước các lớp có trường có giá trị mặc định ( _ParentDefaultsBase
và _ChildDefaultsBase
).
Kết quả là Parent
và Child
các lớp có trường sane cũ hơn, trong khi Child
vẫn là một lớp con của 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
và do đó bạn có thể tạo các phiên bản của cả hai lớp:
>>> 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)
Một tùy chọn khác là chỉ sử dụng các trường có giá trị mặc định; bạn vẫn có thể mắc lỗi không cung cấp school
giá trị bằng cách tăng một giá trị trong __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'")
nhưng điều này làm thay đổi thứ tự trường; school
kết thúc sau ugly
:
<Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> None>
và trình kiểm tra gợi ý kiểu sẽ phàn nàn về việc _no_default
không phải là một chuỗi.
Bạn cũng có thể sử dụng attrs
dự án , đó là dự án đã truyền cảm hứng dataclasses
. Nó sử dụng một chiến lược hợp nhất thừa kế khác nhau; nó kéo các trường bị ghi đè trong một lớp con đến cuối danh sách các trường, do đó ['name', 'age', 'ugly']
trong Parent
lớp trở thành ['name', 'age', 'school', 'ugly']
trong Child
lớp; bằng cách ghi đè trường bằng một mặc định, attrs
cho phép ghi đè mà không cần thực hiện bước nhảy MRO.
attrs
hỗ trợ xác định các trường mà không có gợi ý loại, nhưng hãy bám vào chế độ gợi ý loại được hỗ trợ bằng cách cài đặt 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