dataclasses --- Lớp dữ liệu

Source code: Lib/dataclasses.py


Mô-đun này cung cấp một trình trang trí và các chức năng để tự động thêm special methods được tạo như __init__()__repr__() vào các lớp do người dùng xác định. Ban đầu nó được mô tả trong PEP 557.

Các biến thành viên sử dụng trong các phương thức được tạo này được xác định bằng cách sử dụng chú thích loại PEP 526. Ví dụ: mã này:

từ các lớp dữ liệu nhập lớp dữ liệu

@dataclass
lớp InventoryItem:
    """Lớp theo dõi một mặt hàng trong kho."""
    tên: str
    đơn_giá: thả nổi
    số lượng_on_hand: int = 0

    def Total_cost(self) -> float:
        trả về self.unit_price * self.quantity_on_hand

sẽ thêm vào, trong số những thứ khác, một __init__() trông giống như:

def __init__(self, name: str, unit_price: float,quan_on_hand: int = 0):
    self.name = tên
    self.unit_price = đơn_giá
    self.quantity_on_hand = số lượng_on_hand

Lưu ý rằng phương thức này được tự động thêm vào lớp: nó không được chỉ định trực tiếp trong định nghĩa InventoryItem được hiển thị ở trên.

Added in version 3.7.

Nội dung mô-đun

@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)

Hàm này là decorator được sử dụng để thêm special methods được tạo vào các lớp, như được mô tả bên dưới.

Trình trang trí @dataclass kiểm tra lớp để tìm fields. Zz003zz được định nghĩa là một biến lớp có type annotation. Với hai trường hợp ngoại lệ được mô tả bên dưới, không có gì trong @dataclass kiểm tra loại được chỉ định trong chú thích biến.

Thứ tự của các trường trong tất cả các phương thức được tạo là thứ tự chúng xuất hiện trong định nghĩa lớp.

Trình trang trí @dataclass sẽ thêm nhiều phương thức "dunder" khác nhau vào lớp, được mô tả bên dưới. Nếu bất kỳ phương thức bổ sung nào đã tồn tại trong lớp, thì hành vi sẽ phụ thuộc vào tham số, như được ghi lại bên dưới. Trình trang trí trả về cùng lớp mà nó được gọi; không có lớp mới được tạo ra.

Nếu @dataclass được sử dụng như một công cụ trang trí đơn giản không có tham số, thì nó hoạt động như thể nó có các giá trị mặc định được ghi trong chữ ký này. Nghĩa là, ba cách sử dụng @dataclass này là tương đương:

@dataclass
lớp C:
    ...

@dataclass()
lớp C:
    ...

@dataclass(init=True, repr=True, eq=True, order=False, không an toàn_hash=False, Frozen=Sai,
           match_args=Đúng, kw_only=Sai, slot=Sai, yếuref_slot=Sai)
lớp C:
    ...

Các tham số cho @dataclass là:

  • init: Nếu đúng (mặc định), phương thức __init__() sẽ được tạo.

    Nếu lớp đã định nghĩa __init__() thì tham số này sẽ bị bỏ qua.

  • repr: Nếu đúng (mặc định), phương thức __repr__() sẽ được tạo. Chuỗi Repr được tạo sẽ có tên lớp, tên và Repr của từng trường, theo thứ tự chúng được xác định trong lớp. Các trường được đánh dấu là bị loại trừ khỏi đại diện sẽ không được đưa vào. Ví dụ: InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10).

    Nếu lớp đã định nghĩa __repr__() thì tham số này sẽ bị bỏ qua.

  • eq: Nếu đúng (mặc định), phương thức __eq__() sẽ được tạo. Phương thức này so sánh lớp như thể nó là một bộ các trường của nó theo thứ tự. Cả hai trường hợp trong so sánh phải cùng loại.

    Nếu lớp đã định nghĩa __eq__() thì tham số này sẽ bị bỏ qua.

  • order: Nếu đúng (mặc định là False), các phương thức __lt__(), __le__(), __gt__()__ge__() sẽ được tạo. Chúng so sánh lớp như thể nó là một bộ các trường của nó theo thứ tự. Cả hai trường hợp trong so sánh phải cùng loại. Nếu order đúng và eq sai thì ValueError sẽ xuất hiện.

    Nếu lớp đã định nghĩa bất kỳ __lt__(), __le__(), __gt__() hoặc __ge__() nào, thì TypeError sẽ được nâng lên.

  • unsafe_hash: Nếu đúng, hãy buộc dataclasses tạo phương thức __hash__(), mặc dù việc đó có thể không an toàn. Nếu không, hãy tạo phương thức __hash__() theo cách đặt eqfrozen. Giá trị mặc định là False.

    __hash__() được sử dụng bởi hash() tích hợp và khi các đối tượng được thêm vào các bộ sưu tập băm như từ điển và bộ. Có __hash__() ngụ ý rằng các phiên bản của lớp là bất biến. Khả năng thay đổi là một thuộc tính phức tạp phụ thuộc vào ý định của người lập trình, sự tồn tại và hoạt động của __eq__() cũng như các giá trị của cờ eqfrozen trong trình trang trí @dataclass.

    Theo mặc định, @dataclass sẽ không ngầm thêm phương thức __hash__() trừ khi việc đó an toàn. Nó cũng sẽ không thêm hoặc thay đổi phương thức __hash__() được xác định rõ ràng hiện có. Việc đặt thuộc tính lớp __hash__ = None có ý nghĩa cụ thể đối với Python, như được mô tả trong tài liệu __hash__().

    Nếu __hash__() không được xác định rõ ràng hoặc nếu nó được đặt thành None thì @dataclass may sẽ thêm một phương thức __hash__() ẩn. Mặc dù không được khuyến nghị nhưng bạn có thể buộc @dataclass tạo phương thức __hash__() bằng unsafe_hash=True. Điều này có thể xảy ra nếu lớp của bạn không thể thay đổi về mặt logic nhưng vẫn có thể bị đột biến. Đây là trường hợp sử dụng chuyên biệt và cần được xem xét cẩn thận.

    Dưới đây là các quy tắc quản lý việc tạo phương thức __hash__() ngầm. Lưu ý rằng bạn không thể vừa có phương thức __hash__() rõ ràng trong lớp dữ liệu của mình vừa đặt unsafe_hash=True; điều này sẽ dẫn đến một TypeError.

    Nếu eqfrozen đều đúng thì theo mặc định, @dataclass sẽ tạo phương thức __hash__() cho bạn. Nếu eq là đúng và frozen là sai, __hash__() sẽ được đặt thành None, đánh dấu nó là không thể băm được (đúng như vậy, vì nó có thể thay đổi). Nếu eq sai, __hash__() sẽ không bị ảnh hưởng, nghĩa là phương thức __hash__() của siêu lớp sẽ được sử dụng (nếu siêu lớp là object, điều này có nghĩa là nó sẽ quay lại băm dựa trên id).

  • frozen: Nếu đúng (mặc định là False), việc gán cho các trường sẽ tạo ra một ngoại lệ. Điều này mô phỏng các trường hợp cố định chỉ đọc. Xem discussion bên dưới.

    Nếu __setattr__() hoặc __delattr__() được xác định trong lớp và frozen là đúng thì TypeError sẽ được nâng lên.

  • match_args: Nếu đúng (mặc định là True), bộ __match_args__ sẽ được tạo từ danh sách các tham số không chỉ từ khóa đến phương thức __init__() được tạo (ngay cả khi __init__() không được tạo, xem ở trên). Nếu sai hoặc nếu __match_args__ đã được xác định trong lớp thì __match_args__ sẽ không được tạo.

Added in version 3.10.

  • kw_only: Nếu đúng (giá trị mặc định là False), thì tất cả các trường sẽ được đánh dấu là chỉ chứa từ khóa. Nếu một trường được đánh dấu là chỉ có từ khóa thì tác dụng duy nhất là tham số __init__() được tạo từ trường chỉ có từ khóa phải được chỉ định bằng từ khóa khi __init__() được gọi. Xem mục thuật ngữ parameter để biết chi tiết. Cũng xem phần KW_ONLY.

    Các trường chỉ chứa từ khóa không được bao gồm trong __match_args__.

Added in version 3.10.

  • slots: Nếu đúng (mặc định là False), thuộc tính __slots__ sẽ được tạo và lớp mới sẽ được trả về thay vì lớp ban đầu. Nếu __slots__ đã được xác định trong lớp thì TypeError sẽ được nâng lên.

Cảnh báo

Truyền tham số cho lớp cơ sở __init_subclass__() khi sử dụng slots=True sẽ dẫn đến TypeError. Sử dụng __init_subclass__ không có tham số hoặc sử dụng các giá trị mặc định làm giải pháp thay thế. Xem gh-91126 để biết chi tiết đầy đủ.

Added in version 3.10.

Thay đổi trong phiên bản 3.11: Nếu tên trường đã được đưa vào __slots__ của lớp cơ sở, thì tên trường đó sẽ không được đưa vào __slots__ được tạo để ngăn chặn overriding them. Do đó, không sử dụng __slots__ để truy xuất tên trường của lớp dữ liệu. Thay vào đó hãy sử dụng fields(). Để có thể xác định các vị trí được kế thừa, lớp cơ sở __slots__ có thể là bất kỳ lần lặp nào, nhưng not là một trình vòng lặp.

  • weakref_slot: Nếu đúng (mặc định là False), hãy thêm một vị trí có tên "__weakref__", cần thiết để tạo một phiên bản weakref-able. Sẽ là một lỗi khi chỉ định weakref_slot=True mà không chỉ định slots=True.

Added in version 3.11.

fields có thể tùy ý chỉ định một giá trị mặc định, sử dụng cú pháp Python thông thường

@dataclass
lớp C:
    a: int # 'a' không có giá trị mặc định
    b: int = 0 # assign một giá trị mặc định cho 'b'

Trong ví dụ này, cả ab sẽ được bao gồm trong phương thức __init__() được thêm vào, phương thức này sẽ được xác định là:

def __init__(self, a: int, b: int = 0):

TypeError sẽ được tă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 cho dù điều này xảy ra trong một lớp duy nhất hay là kết quả của sự kế thừa lớp.

dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING, doc=None)

Đối với các trường hợp sử dụng thông thường và đơn giản, không cần chức năng nào khác. Tuy nhiên, có một số tính năng lớp dữ liệu yêu cầu thông tin bổ sung cho mỗi trường. Để đáp ứng nhu cầu thông tin bổ sung này, bạn có thể thay thế giá trị trường mặc định bằng lệnh gọi hàm field() được cung cấp. Ví dụ:

@dataclass
lớp C:
    danh sách của tôi: list[int] = field(default_factory=list)

c = C()
c.mylist += [1, 2, 3]

Như được hiển thị ở trên, giá trị MISSING là một đối tượng trọng điểm được sử dụng để phát hiện xem một số tham số có được người dùng cung cấp hay không. Trọng điểm này được sử dụng vì None là giá trị hợp lệ cho một số tham số có ý nghĩa riêng biệt. Không có mã nào được sử dụng trực tiếp giá trị MISSING.

Các tham số cho field() là:

  • default: Nếu được cung cấp, đây sẽ là giá trị mặc định cho trường này. Điều này là cần thiết vì chính lệnh gọi field() sẽ thay thế vị trí bình thường của giá trị mặc định.

  • default_factory: Nếu được cung cấp, nó phải là một đối số có thể gọi bằng 0 và sẽ được gọi khi cần giá trị mặc định cho trường này. Trong số các mục đích khác, điều này có thể được sử dụng để chỉ định các trường có giá trị mặc định có thể thay đổi, như được thảo luận bên dưới. Đó là một lỗi khi chỉ định cả defaultdefault_factory.

  • init: Nếu đúng (mặc định), trường này được đưa vào làm tham số cho phương thức __init__() được tạo.

  • repr: Nếu đúng (mặc định), trường này được bao gồm trong chuỗi được trả về bởi phương thức __repr__() được tạo.

  • hash: Đây có thể là bool hoặc None. Nếu đúng, trường này được bao gồm trong phương thức __hash__() được tạo. Nếu sai, trường này sẽ bị loại khỏi __hash__() được tạo. Nếu None (mặc định), hãy sử dụng giá trị của compare: đây thường là hành vi được mong đợi vì một trường phải được đưa vào hàm băm nếu nó được sử dụng để so sánh. Không khuyến khích đặt giá trị này thành bất kỳ giá trị nào khác ngoài None.

    Một lý do có thể để đặt hash=False nhưng compare=True sẽ là nếu một trường đắt tiền để tính giá trị băm, trường đó cần thiết để kiểm tra tính bằng nhau và có các trường khác đóng góp vào giá trị băm của loại. Ngay cả khi một trường bị loại khỏi hàm băm, nó vẫn sẽ được sử dụng để so sánh.

  • compare: Nếu đúng (mặc định), trường này được bao gồm trong các phương thức so sánh và đẳng thức được tạo (__eq__(), __gt__(), et al.).

  • metadata: Đây có thể là ánh xạ hoặc None. None được coi là một lệnh trống. Giá trị này được gói trong MappingProxyType() để làm cho nó ở chế độ chỉ đọc và hiển thị trên đối tượng Field. Nó hoàn toàn không được các Lớp dữ liệu sử dụng và được cung cấp dưới dạng cơ chế mở rộng của bên thứ ba. Nhiều bên thứ ba có thể có khóa riêng để sử dụng làm không gian tên trong siêu dữ liệu.

  • kw_only: Nếu đúng, trường này sẽ được đánh dấu là chỉ chứa từ khóa. Điều này được sử dụng khi các tham số của phương thức __init__() được tạo được tính toán.

    Các trường chỉ chứa từ khóa cũng không được đưa vào __match_args__.

Added in version 3.10.

  • doc: chuỗi tài liệu tùy chọn cho trường này.

Added in version 3.14.

Nếu giá trị mặc định của một trường được chỉ định bằng lệnh gọi tới field() thì thuộc tính lớp cho trường này sẽ được thay thế bằng giá trị default đã chỉ định. Nếu default không được cung cấp thì thuộc tính lớp sẽ bị xóa. Mục đích là sau khi trình trang trí @dataclass chạy, tất cả các thuộc tính lớp sẽ chứa các giá trị mặc định cho các trường, giống như thể chính giá trị mặc định đó đã được chỉ định. Ví dụ: sau:

@dataclass
lớp C:
    x: int
    y: int = field(repr=False)
    z: int = field(repr=False, default=10)
    t: int = 20

Thuộc tính lớp C.z sẽ là 10, thuộc tính lớp C.t sẽ là 20 và các thuộc tính lớp C.xC.y sẽ không được đặt.

class dataclasses.Field

Các đối tượng Field mô tả từng trường được xác định. Các đối tượng này được tạo nội bộ và được trả về bằng phương thức cấp mô-đun fields() (xem bên dưới). Người dùng không bao giờ nên khởi tạo trực tiếp đối tượng Field. Các thuộc tính được ghi lại của nó là:

  • name: Tên trường.

  • type: Loại trường.

  • default, default_factory, init, repr, hash, compare, metadatakw_only có ý nghĩa và giá trị giống hệt như trong hàm field().

Các thuộc tính khác có thể tồn tại nhưng chúng là riêng tư và không được kiểm tra hoặc dựa vào.

class dataclasses.InitVar

Chú thích loại InitVar[T] mô tả các biến init-only. Các trường được chú thích bằng InitVar được coi là trường giả và do đó không được hàm fields() trả về cũng như không được sử dụng theo bất kỳ cách nào ngoại trừ việc thêm chúng làm tham số cho __init__()__post_init__() tùy chọn.

dataclasses.fields(class_or_instance)

Trả về một bộ đối tượng Field xác định các trường cho lớp dữ liệu này. Chấp nhận một lớp dữ liệu hoặc một thể hiện của lớp dữ liệu. Tăng TypeError nếu không vượt qua được một lớp dữ liệu hoặc phiên bản của một lớp dữ liệu. Không trả về các trường giả là ClassVar hoặc InitVar.

dataclasses.asdict(obj, *, dict_factory=dict)

Chuyển đổi lớp dữ liệu obj thành dict (bằng cách sử dụng hàm dict_factory). Mỗi lớp dữ liệu được chuyển đổi thành một lệnh của các trường của nó, dưới dạng cặp name: value. các lớp dữ liệu, ký tự, danh sách và bộ dữ liệu được đệ quy vào. Các đối tượng khác được sao chép bằng copy.deepcopy().

Ví dụ về việc sử dụng asdict() trên các lớp dữ liệu lồng nhau:

@dataclass
Điểm lớp:
     x: int
     y: int

@dataclass
lớp C:
     danh sách của tôi: danh sách[Điểm]

p = Điểm(10, 20)
khẳng định asdict(p) == {'x': 10, 'y': 20}

c = C([Điểm(0, 0), Điểm(10, 4)])
khẳng định asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}

Để tạo một bản sao nông, có thể sử dụng cách giải quyết sau:

{field.name: getattr(obj, field.name) cho trường trong trường (obj)}

asdict() tăng TypeError nếu obj không phải là một phiên bản lớp dữ liệu.

dataclasses.astuple(obj, *, tuple_factory=tuple)

Chuyển đổi lớp dữ liệu obj thành một bộ dữ liệu (bằng cách sử dụng hàm xuất xưởng tuple_factory). Mỗi lớp dữ liệu được chuyển đổi thành một bộ giá trị trường của nó. các lớp dữ liệu, ký tự, danh sách và bộ dữ liệu được đệ quy vào. Các đối tượng khác được sao chép bằng copy.deepcopy().

Tiếp tục từ ví dụ trước:

khẳng định astuple(p) == (10, 20)
khẳng định astuple(c) == ([(0, 0), (10, 4)],)

Để tạo một bản sao nông, có thể sử dụng cách giải quyết sau:

tuple(getattr(obj, field.name) cho trường trong dataclasses.fields(obj))

astuple() tăng TypeError nếu obj không phải là một phiên bản lớp dữ liệu.

dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None, decorator=dataclass)

Tạo một lớp dữ liệu mới có tên cls_name, các trường như được xác định trong fields, các lớp cơ sở như được cung cấp trong bases và được khởi tạo với một không gian tên như được cung cấp trong namespace. fields là một iterable có các phần tử là name, (name, type) hoặc (name, type, Field). Nếu chỉ cung cấp name thì typing.Any sẽ được sử dụng cho type. Các giá trị của init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slotsweakref_slot có cùng ý nghĩa như trong @dataclass.

Nếu module được xác định, thuộc tính __module__ của lớp dữ liệu được đặt thành giá trị đó. Theo mặc định, nó được đặt thành tên mô-đun của người gọi.

Tham số decorator là một tham số có thể gọi được và sẽ được sử dụng để tạo lớp dữ liệu. Nó phải lấy đối tượng lớp làm đối số đầu tiên và các đối số từ khóa giống như @dataclass. Theo mặc định, chức năng @dataclass được sử dụng.

Hàm này không bắt buộc phải có vì bất kỳ cơ chế Python nào để tạo một lớp mới với __annotations__ đều có thể áp dụng hàm @dataclass để chuyển đổi lớp đó thành một lớp dữ liệu. Chức năng này được cung cấp như một sự thuận tiện. Ví dụ:

C = make_dataclass('C',
                   [('x', int),
                     'y',
                    ('z', int, trường (mặc định=5))],
                   namespace={'add_one': lambda self: self.x + 1})

Tương đương với:

@dataclass
lớp C:
    x: int
    y: 'gõ.Bất kỳ'
    z: int = 5

    def add_one(tự):
        trả về self.x + 1

Added in version 3.14: Đã thêm tham số decorator.

dataclasses.replace(obj, /, **changes)

Tạo một đối tượng mới cùng loại với obj, thay thế các trường bằng các giá trị từ changes. Nếu obj không phải là Lớp dữ liệu, hãy tăng TypeError. Nếu các khóa trong changes không phải là tên trường của lớp dữ liệu đã cho, hãy tăng TypeError.

Đối tượng mới được trả về được tạo bằng cách gọi phương thức __init__() của lớp dữ liệu. Điều này đảm bảo rằng __post_init__(), nếu có, cũng được gọi.

Các biến chỉ ban đầu không có giá trị mặc định, nếu có, phải được chỉ định trong lệnh gọi tới replace() để chúng có thể được chuyển tới __init__()__post_init__().

Đó là lỗi khi changes chứa bất kỳ trường nào được xác định là có init=False. Một ValueError sẽ được nâng lên trong trường hợp này.

Được cảnh báo trước về cách hoạt động của các trường init=False trong cuộc gọi tới replace(). Chúng không được sao chép từ đối tượng nguồn mà được khởi tạo trong __post_init__(), nếu chúng hoàn toàn được khởi tạo. Dự kiến ​​các trường init=False sẽ hiếm khi được sử dụng một cách thận trọng. Nếu chúng được sử dụng, có lẽ nên có các hàm tạo lớp thay thế hoặc có lẽ là một phương thức replace() (hoặc có tên tương tự) tùy chỉnh để xử lý việc sao chép cá thể.

Các phiên bản lớp dữ liệu cũng được hỗ trợ bởi hàm chung copy.replace().

dataclasses.is_dataclass(obj)

Trả về True nếu tham số của nó là một lớp dữ liệu (bao gồm các lớp con của một lớp dữ liệu) hoặc một phiên bản của một lớp dữ liệu, nếu không thì trả về False.

Nếu bạn cần biết liệu một lớp có phải là một phiên bản của một lớp dữ liệu hay không (chứ không phải chính nó là một lớp dữ liệu), thì hãy thêm một kiểm tra bổ sung cho not isinstance(obj, type):

def is_dataclass_instance(obj):
    trả về is_dataclass(obj) chứ không phải isinstance(obj, type)
dataclasses.MISSING

Giá trị trọng điểm biểu thị thiếu default hoặc default_factory.

dataclasses.KW_ONLY

Giá trị trọng điểm được sử dụng làm chú thích kiểu. Bất kỳ trường nào sau trường giả có loại KW_ONLY đều được đánh dấu là trường chỉ có từ khóa. Lưu ý rằng trường giả thuộc loại KW_ONLY hoàn toàn bị bỏ qua. Điều này bao gồm tên của một trường như vậy. Theo quy ước, tên _ được sử dụng cho trường KW_ONLY. Các trường chỉ có từ khóa biểu thị các tham số __init__() phải được chỉ định làm từ khóa khi lớp được khởi tạo.

Trong ví dụ này, các trường yz sẽ được đánh dấu là trường chỉ có từ khóa:

@dataclass
Điểm lớp:
    x: nổi
    _: KW_ONLY
    y: trôi nổi
    z: nổi

p = Điểm(0, y=1,5, z=2,0)

Trong một lớp dữ liệu, sẽ xảy ra lỗi khi chỉ định nhiều trường có loại là KW_ONLY.

Added in version 3.10.

exception dataclasses.FrozenInstanceError

Xảy ra khi một __setattr__() hoặc __delattr__() được xác định ngầm được gọi trên một lớp dữ liệu được xác định bằng frozen=True. Nó là một lớp con của AttributeError.

Xử lý sau khi bắt đầu

dataclasses.__post_init__()

Khi được định nghĩa trên lớp, nó sẽ được gọi bởi __init__() được tạo, thông thường là self.__post_init__(). Tuy nhiên, nếu bất kỳ trường InitVar nào được xác định, chúng cũng sẽ được chuyển đến __post_init__() theo thứ tự chúng được xác định trong lớp. Nếu không có phương thức __init__() nào được tạo thì __post_init__() sẽ không tự động được gọi.

Trong số các mục đích sử dụng khác, điều này cho phép khởi tạo các giá trị trường phụ thuộc vào một hoặc nhiều trường khác. Ví dụ:

@dataclass
lớp C:
    a: trôi nổi
    b: phao
    c: float = field(init=False)

    def __post_init__(tự):
        self.c = self.a + self.b

Phương thức __init__() được tạo bởi @dataclass không gọi các phương thức __init__() của lớp cơ sở. Nếu lớp cơ sở có một phương thức __init__() phải được gọi, thì người ta thường gọi phương thức này trong phương thức __post_init__()

lớp Hình chữ nhật:
    def __init__(bản thân, chiều cao, chiều rộng):
        self.height = chiều cao
        self.width = chiều rộng

@dataclass
lớp Hình vuông (Hình chữ nhật):
    bên: phao

    def __post_init__(tự):
        super().__init__(self.side, self.side)

Tuy nhiên, lưu ý rằng nói chung không cần phải gọi các phương thức __init__() do lớp dữ liệu tạo ra, vì lớp dữ liệu dẫn xuất sẽ đảm nhiệm việc khởi tạo tất cả các trường của bất kỳ lớp cơ sở nào vốn là một lớp dữ liệu.

Xem phần bên dưới về các biến chỉ init để biết cách truyền tham số cho __post_init__(). Đồng thời xem cảnh báo về cách replace() xử lý các trường init=False.

Biến lớp

Một trong số ít nơi @dataclass thực sự kiểm tra loại trường là xác định xem trường đó có phải là biến lớp như được định nghĩa trong PEP 526 hay không. Nó thực hiện điều này bằng cách kiểm tra xem loại trường có phải là typing.ClassVar hay không. Nếu một trường là ClassVar, thì trường đó sẽ bị loại khỏi việc xem xét dưới dạng trường và bị cơ chế lớp dữ liệu bỏ qua. Các trường giả ClassVar như vậy không được hàm fields() cấp mô-đun trả về.

Các biến chỉ ban đầu

Một nơi khác mà @dataclass kiểm tra chú thích loại là xác định xem một trường có phải là biến chỉ init hay không. Nó thực hiện điều này bằng cách xem loại trường có thuộc loại InitVar hay không. Nếu một trường là InitVar, nó được coi là trường giả được gọi là trường chỉ init. Vì đây không phải là trường đúng nên hàm fields() cấp mô-đun không trả về trường này. Các trường chỉ ban đầu được thêm làm tham số cho phương thức __init__() được tạo và được chuyển sang phương thức __post_init__() tùy chọn. Chúng không được sử dụng bởi các lớp dữ liệu.

Ví dụ: giả sử một trường sẽ được khởi tạo từ cơ sở dữ liệu, nếu giá trị không được cung cấp khi tạo lớp

@dataclass
lớp C:
    tôi: int
    j: int | Không  = Không 
     sở dữ liệu: InitVar[DatabaseType | Không ] = Không 

    def __post_init__(tự,  sở dữ liệu):
        nếu self.j  Không   sở dữ liệu không phải  Không:
            self.j =  sở dữ liệu.lookup('j')

c = C(10,  sở dữ liệu=my_database)

Trong trường hợp này, fields() sẽ trả về các đối tượng Field cho ij, nhưng không trả về database.

Trường hợp đông lạnh

Không thể tạo các đối tượng Python thực sự bất biến. Tuy nhiên, bằng cách chuyển frozen=True tới trình trang trí @dataclass, bạn có thể mô phỏng tính bất biến. Trong trường hợp đó, các lớp dữ liệu sẽ thêm các phương thức __setattr__()__delattr__() vào lớp. Các phương thức này sẽ tăng FrozenInstanceError khi được gọi.

Có một hình phạt nhỏ về hiệu suất khi sử dụng frozen=True: __init__() không thể sử dụng phép gán đơn giản để khởi tạo các trường và phải sử dụng object.__setattr__().

Kế thừa

Khi lớp dữ liệu đang được tạo bởi trình trang trí @dataclass, nó sẽ xem qua tất cả các lớp cơ sở của lớp theo chiều ngược lại MRO (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, nó sẽ thêm các trường từ lớp cơ sở đó vào một ánh xạ có thứ tự của các trường. 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ác trường theo thứ tự được tính toán và kết hợp này. Vì các trường được sắp xếp theo thứ tự chèn nên các lớp dẫn xuất sẽ ghi đè các lớp cơ sở. Một ví dụ:

@dataclass
lớp  sở:
    x: Bất kỳ = 15,0
    y: int = 0

@dataclass
lớp C ( sở):
    z: int = 10
    x: int = 15

Danh sách các trường cuối cùng theo thứ tự là x, y, z. Loại cuối cùng của xint, như được chỉ định trong lớp C.

Phương thức __init__() được tạo cho C sẽ có dạng:

def __init__(self, x: int = 15, y: int = 0, z: int = 10):

Sắp xếp lại các tham số chỉ từ khóa trong __init__()

Sau khi các tham số cần thiết cho __init__() được tính toán, mọi tham số chỉ từ khóa sẽ được chuyển đến sau tất cả các tham số thông thường (không chỉ từ khóa). Đây là yêu cầu về cách triển khai các tham số chỉ từ khóa trong Python: chúng phải đặt sau các tham số không chỉ từ khóa.

Trong ví dụ này, Base.y, Base.wD.t là các trường chỉ có từ khóa và Base.xD.z là các trường thông thường:

@dataclass
lớp  sở:
    x: Bất kỳ = 15,0
    _: KW_ONLY
    y: int = 0
    w: int = 1

@dataclass
lớp D ( sở):
    z: int = 10
    t: int = field(kw_only=True, default=0)

Phương thức __init__() được tạo cho D sẽ có dạng:

def __init__(self, x: Any = 15.0, z: int = 10, *, y: int = 0, w: int = 1, t: int = 0):

Lưu ý rằng các tham số đã được sắp xếp lại theo cách chúng xuất hiện trong danh sách các trường: các tham số bắt nguồn từ các trường thông thường được theo sau bởi các tham số bắt nguồn từ các trường chỉ có từ khóa.

Thứ tự tương đối của các tham số chỉ từ khóa được duy trì trong danh sách tham số __init__() được sắp xếp lại.

Chức năng mặc định của nhà máy

Nếu field() chỉ định default_factory, nó sẽ được gọi với đối số bằng 0 khi cần giá trị mặc định cho trường. Ví dụ: để tạo một phiên bản mới của danh sách, hãy sử dụng:

danh sách của tôi: list = field(default_factory=list)

Nếu một trường bị loại trừ khỏi __init__() (sử dụng init=False) và trường đó cũng chỉ định default_factory, thì hàm xuất xưởng mặc định sẽ luôn được gọi từ hàm __init__() được tạo. Điều này xảy ra vì không có cách nào khác để cung cấp cho trường một giá trị ban đầu.

Giá trị mặc định có thể thay đổi

Python lưu trữ các giá trị biến thành viên mặc định trong thuộc tính lớp. Hãy xem xét ví dụ này, không sử dụng dataclasses:

lớp C:
    x = []
    def add(self, element):
        self.x.append(element)

o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
khẳng định o1.x == [1, 2]
khẳng định o1.x  o2.x

Lưu ý rằng hai phiên bản của lớp C có chung biến lớp x, như mong đợi.

Sử dụng các lớp dữ liệu, if mã này hợp lệ

@dataclass
lớp D:
    x: list = []  # This tăng ValueError
    def add(self, element):
        self.x.append(element)

nó sẽ tạo mã tương tự như

lớp D:
    x = []
    định nghĩa __init__(tự, x=x):
        tự.x = x
    def add(self, element):
        self.x.append(element)

khẳng định D().x  D().x

Điều này có cùng vấn đề với ví dụ ban đầu sử dụng lớp C. Nghĩa là, hai phiên bản của lớp D không chỉ định giá trị cho x khi tạo một phiên bản lớp sẽ chia sẻ cùng một bản sao của x. Bởi vì các lớp dữ liệu chỉ sử dụng việc tạo lớp Python thông thường nên chúng cũng có chung hành vi này. Không có cách chung nào để Lớp dữ liệu phát hiện tình trạng này. Thay vào đó, trình trang trí @dataclass sẽ đưa ra ValueError nếu nó phát hiện một tham số mặc định không thể băm được. Giả định là nếu một giá trị không thể băm được thì nó có thể thay đổi được. Đây chỉ là giải pháp một phần nhưng có tác dụng bảo vệ khỏi nhiều lỗi phổ biến.

Sử dụng các hàm mặc định của nhà máy là một cách để tạo các phiên bản mới của các loại có thể thay đổi làm giá trị mặc định cho các trường

@dataclass
lớp D:
    x: danh sách = trường (default_factory=list)

khẳng định D().x không phải  D().x

Thay đổi trong phiên bản 3.11: Thay vì tìm kiếm và không cho phép các đối tượng thuộc loại list, dict hoặc set, các đối tượng không thể băm hiện không được phép làm giá trị mặc định. Tính không thể xóa được sử dụng để tính gần đúng khả năng biến đổi.

Các trường được gõ mô tả

Các trường được gán descriptor objects làm giá trị mặc định có các hành vi đặc biệt sau:

  • Giá trị của trường được chuyển tới phương thức __init__() của lớp dữ liệu được chuyển đến phương thức __set__() của bộ mô tả thay vì ghi đè đối tượng bộ mô tả.

  • Tương tự, khi nhận hoặc thiết lập trường, phương thức __get__() hoặc __set__() của bộ mô tả sẽ được gọi thay vì trả về hoặc ghi đè đối tượng bộ mô tả.

  • Để xác định xem một trường có chứa giá trị mặc định hay không, @dataclass sẽ gọi phương thức __get__() của bộ mô tả bằng cách sử dụng biểu mẫu truy cập lớp của nó: descriptor.__get__(obj=None, type=cls). Nếu bộ mô tả trả về một giá trị trong trường hợp này, nó sẽ được sử dụng làm giá trị mặc định của trường. Mặt khác, nếu bộ mô tả tăng AttributeError trong tình huống này thì sẽ không có giá trị mặc định nào được cung cấp cho trường.

lớp IntConversionDescriptor:
    def __init__(self, *, mặc định):
        self._default = mặc định

    def __set_name__(bản thân, chủ sở hữu, tên):
        self._name = "_" + tên

    def __get__(self, obj, type):
        nếu obj  Không:
            tự trả về._default

        trả về getattr(obj, self._name, self._default)

    def __set__(self, obj, value):
        setattr(obj, self._name, int(value))

@dataclass
lớp InventoryItem:
    lượng_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)

i = InventoryItem()
in(i.quantity_on_hand) # 100
i.quantity_on_hand = 2,5 # calls __set__ với 2,5
print(i.quantity_on_hand) # 2

Lưu ý rằng nếu một trường được chú thích bằng loại bộ mô tả nhưng không được gán đối tượng mô tả làm giá trị mặc định thì trường đó sẽ hoạt động giống như một trường bình thường.