Các lớp dữ liệu là gì và chúng khác với các lớp phổ biến như thế nào?


140

Với các lớp dữ liệu PEP 557 được đưa vào thư viện chuẩn python.

Họ sử dụng trình @dataclasstrang trí và họ được coi là "các tên được thay đổi theo mặc định" nhưng tôi không thực sự chắc chắn tôi hiểu điều này thực sự có nghĩa là gì và chúng khác với các lớp thông thường như thế nào.

Chính xác các lớp dữ liệu python là gì và khi nào là tốt nhất để sử dụng chúng?


8
Với nội dung phong phú của PEP, bạn còn muốn biết gì nữa không? namedtuples là bất biến và không thể có giá trị mặc định cho các thuộc tính, trong khi các lớp dữ liệu có thể thay đổi và có thể có chúng.
jonrsharpe

28
@jonrsharpe Có vẻ hợp lý với tôi rằng nên có một luồng stackoverflow về chủ đề này. Stackoverflow có nghĩa là một cuốn bách khoa toàn thư ở định dạng Q & A, phải không? Câu trả lời là không bao giờ "chỉ cần nhìn vào trang web khác này." Không nên có downvote ở đây.
Luke Davis

10
Có năm chủ đề về cách nối một mục vào danh sách. Một câu hỏi trên @dataclasssẽ không làm cho trang web tan rã.
eric

2
@jonrsharpe namedtuplesCÓ THỂ có các giá trị mặc định. Hãy xem tại đây: stackoverflow.com/questions/11351032/
Kẻ

Câu trả lời:


151

Các lớp dữ liệu chỉ là các lớp thông thường hướng đến trạng thái lưu trữ, nhiều hơn là chứa rất nhiều logic. Mỗi khi bạn tạo một lớp chủ yếu bao gồm các thuộc tính bạn đã tạo một lớp dữ liệu.

Những gì dataclassesmô-đun làm là làm cho nó dễ dàng hơn để tạo các lớp dữ liệu. Nó chăm sóc rất nhiều tấm nồi hơi cho bạn.

Điều này đặc biệt quan trọng khi lớp dữ liệu của bạn phải được băm; điều này đòi hỏi một __hash__phương pháp cũng như một __eq__phương pháp. Nếu bạn thêm một __repr__phương thức tùy chỉnh để dễ gỡ lỗi, điều đó có thể trở nên khá dài dòng:

class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def __init__(
            self, 
            name: str, 
            unit_price: float,
            quantity_on_hand: int = 0
        ) -> None:
        self.name = name
        self.unit_price = unit_price
        self.quantity_on_hand = quantity_on_hand

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

    def __repr__(self) -> str:
        return (
            'InventoryItem('
            f'name={self.name!r}, unit_price={self.unit_price!r}, '
            f'quantity_on_hand={self.quantity_on_hand!r})'

    def __hash__(self) -> int:
        return hash((self.name, self.unit_price, self.quantity_on_hand))

    def __eq__(self, other) -> bool:
        if not isinstance(other, InventoryItem):
            return NotImplemented
        return (
            (self.name, self.unit_price, self.quantity_on_hand) == 
            (other.name, other.unit_price, other.quantity_on_hand))

Với dataclassesbạn có thể giảm nó thành:

from dataclasses import dataclass

@dataclass(unsafe_hash=True)
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

Lớp trang trí tương tự cũng có thể tạo ra các phương pháp so sánh ( __lt__, __gt__, vv) và xử lý bất biến.

namedtuplecác lớp cũng là các lớp dữ liệu, nhưng theo mặc định là bất biến (cũng như là các chuỗi). dataclasseslinh hoạt hơn nhiều về vấn đề này và có thể dễ dàng được cấu trúc sao cho chúng có thể đóng vai trò giống như một namedtuplelớp .

PEP được lấy cảm hứng từ attrsdự án , có thể làm được nhiều hơn (bao gồm các vị trí, trình xác nhận, trình chuyển đổi, siêu dữ liệu, v.v.).

Nếu bạn muốn xem một số ví dụ, gần đây tôi đã sử dụng dataclassescho một số giải pháp Advent of Code của mình , hãy xem các giải pháp cho ngày 7 , ngày 8 , ngày 11ngày 20 .

Nếu bạn muốn sử dụng dataclassesmô-đun trong các phiên bản Python <3.7, thì bạn có thể cài đặt mô-đun backported (yêu cầu 3.6) hoặc sử dụng attrsdự án được đề cập ở trên.


2
Trong ví dụ đầu tiên, bạn có cố tình che giấu các thành viên lớp với các thành viên cùng tên không? Xin hãy giúp hiểu thành ngữ này.
VladimirLenin

4
@VladimirLenin: không có thuộc tính lớp, chỉ có chú thích kiểu. Xem PEP 526 , cụ thể là phần chú thích biến lớp và thể hiện .
Martijn Pieters

1
@Bananach: việc @dataclasstạo gần như cùng một __init__phương thức, với một quantity_on_handđối số từ khóa có giá trị mặc định. Khi bạn tạo một cá thể, nó sẽ luôn đặt quantity_on_handthuộc tính cá thể. Vì vậy , ví dụ đầu tiên , phi dữ liệu của tôi sử dụng cùng một mẫu để lặp lại những gì mà mã dữ liệu được tạo sẽ làm.
Martijn Pieters

1
@Bananach: như vậy trong ví dụ đầu tiên, chúng tôi có thể có thể bỏ qua thiết lập một thuộc tính ví dụ và không bóng thuộc tính lớp, nó là thiết lập dự phòng nó anyway trong ý nghĩa đó, nhưng dataclasses làm thiết lập nó.
Martijn Pieters

1
@ user2853437 trường hợp sử dụng của bạn không thực sự được hỗ trợ bởi dataclass; có lẽ bạn sẽ tốt hơn khi sử dụng anh em họ lớn hơn của dataclass, attrs . Dự án đó hỗ trợ các trình chuyển đổi trên mỗi trường cho phép bạn bình thường hóa các giá trị trường. Nếu bạn muốn gắn bó với dataclass, thì có, thực hiện chuẩn hóa trong __post_init__phương thức.
Martijn Pieters

62

Tổng quat

Câu hỏi đã được giải quyết. Tuy nhiên, câu trả lời này bổ sung một số ví dụ thực tế để hỗ trợ cho sự hiểu biết cơ bản về dataclass.

Chính xác các lớp dữ liệu python là gì và khi nào là tốt nhất để sử dụng chúng?

  1. trình tạo mã : tạo mã soạn sẵn; bạn có thể chọn thực hiện các phương thức đặc biệt trong một lớp thông thường hoặc có một lớp dữ liệu tự động thực hiện chúng.
  2. bộ chứa dữ liệu : các cấu trúc chứa dữ liệu (ví dụ: tuples và dicts), thường có các truy cập thuộc tính rải rác, như các lớp namedtuplevà các cấu trúc khác .

"tên biến đổi có thể thay đổi với [s]" mặc định

Đây là những gì cụm từ sau có nghĩa là:

  • mutable : theo mặc định, các thuộc tính dataclass có thể được gán lại. Bạn có thể tùy ý biến chúng thành bất biến (xem ví dụ bên dưới).
  • nametuple : bạn đã truy cập, truy cập thuộc tính như một namedtuplehoặc một lớp thông thường.
  • mặc định : bạn có thể gán giá trị mặc định cho các thuộc tính.

So với các lớp phổ biến, bạn chủ yếu tiết kiệm khi nhập mã soạn sẵn.


Đặc trưng

Đây là tổng quan về các tính năng của bảng dữ liệu (TL; DR? Xem Bảng Tóm tắt trong phần tiếp theo).

Những gì bạn nhận được

Dưới đây là các tính năng bạn nhận được theo mặc định từ dataclass.

Các thuộc tính + Đại diện + So sánh

import dataclasses


@dataclasses.dataclass
#@dataclasses.dataclass()                                       # alternative
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

Những mặc định này được cung cấp bằng cách tự động đặt các từ khóa sau thành True:

@dataclasses.dataclass(init=True, repr=True, eq=True)

Những gì bạn có thể bật

Các tính năng bổ sung có sẵn nếu các từ khóa thích hợp được đặt thành True.

Đặt hàng

@dataclasses.dataclass(order=True)
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

Các phương thức đặt hàng hiện đang được triển khai (các toán tử nạp chồng < > <= >=:), tương tự như functools.total_orderingvới các phép thử đẳng thức mạnh hơn.

Có thể băm, có thể thay đổi

@dataclasses.dataclass(unsafe_hash=True)                        # override base `__hash__`
class Color:
    ...

Mặc dù đối tượng có khả năng biến đổi (có thể không mong muốn), một hàm băm được thực hiện.

Băm, bất biến

@dataclasses.dataclass(frozen=True)                             # `eq=True` (default) to be immutable 
class Color:
    ...

Băm bây giờ được thực hiện và thay đổi đối tượng hoặc gán cho các thuộc tính không được phép.

Nhìn chung, đối tượng là hashable nếu một trong hai unsafe_hash=Truehoặc frozen=True.

Xem thêm bảng logic băm gốc với nhiều chi tiết hơn.

Những gì bạn không nhận được

Để có được các tính năng sau, các phương pháp đặc biệt phải được thực hiện thủ công:

Giải nén

@dataclasses.dataclass
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

    def __iter__(self):
        yield from dataclasses.astuple(self)

Tối ưu hóa

@dataclasses.dataclass
class SlottedColor:
    __slots__ = ["r", "b", "g"]
    r : int
    g : int
    b : int

Kích thước đối tượng bây giờ đã giảm:

>>> imp sys
>>> sys.getsizeof(Color)
1056
>>> sys.getsizeof(SlottedColor)
888

Trong một số trường hợp, __slots__cũng cải thiện tốc độ tạo phiên bản và truy cập các thuộc tính. Ngoài ra, các vị trí không cho phép bài tập mặc định; mặt khác, a ValueErrorđược nâng lên

Xem thêm về các vị trí trong bài viết trên blog này .


Bảng tóm tắt

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       Feature        |       Keyword        |                      Example                       |           Implement in a Class          |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| Attributes           |  init                |  Color().r -> 0                                    |  __init__                               |
| Representation       |  repr                |  Color() -> Color(r=0, g=0, b=0)                   |  __repr__                               |
| Comparision*         |  eq                  |  Color() == Color(0, 0, 0) -> True                 |  __eq__                                 |
|                      |                      |                                                    |                                         |
| Order                |  order               |  sorted([Color(0, 50, 0), Color()]) -> ...         |  __lt__, __le__, __gt__, __ge__         |
| Hashable             |  unsafe_hash/frozen  |  {Color(), {Color()}} -> {Color(r=0, g=0, b=0)}    |  __hash__                               |
| Immutable            |  frozen + eq         |  Color().r = 10 -> TypeError                       |  __setattr__, __delattr__               |
|                      |                      |                                                    |                                         |
| Unpacking+           |  -                   |  r, g, b = Color()                                 |   __iter__                              |
| Optimization+        |  -                   |  sys.getsizeof(SlottedColor) -> 888                |  __slots__                              |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+

+ Các phương thức này không được tạo tự động và yêu cầu thực hiện thủ công trong một bảng dữ liệu.

* __ne__ là không cần thiết và do đó không được thực hiện .


Tính năng bổ sung

Hậu khởi tạo

@dataclasses.dataclass
class RGBA:
    r : int = 0
    g : int = 0
    b : int = 0
    a : float = 1.0

    def __post_init__(self):
        self.a : int =  int(self.a * 255)


RGBA(127, 0, 255, 0.5)
# RGBA(r=127, g=0, b=255, a=127)

Di sản

@dataclasses.dataclass
class RGBA(Color):
    a : int = 0

Chuyển đổi

Chuyển đổi một dataclass thành một tuple hoặc dict, đệ quy :

>>> dataclasses.astuple(Color(128, 0, 255))
(128, 0, 255)
>>> dataclasses.asdict(Color(128, 0, 255))
{r: 128, g: 0, b: 255}

Hạn chế


Người giới thiệu

  • Cuộc trò chuyện của R. Hettinger trên Dataclass: Trình tạo mã để kết thúc tất cả các trình tạo mã
  • Cuộc nói chuyện của T. Hunner về các lớp dễ dàng hơn: Các lớp Python không có tất cả các hành trình
  • Tài liệu của Python về chi tiết băm
  • Hướng dẫn thực sự của Python về Hướng dẫn cơ bản về các lớp dữ liệu trong Python 3.7
  • Bài viết trên blog của A. Shaw về chuyến tham quan ngắn về các lớp dữ liệu Python 3.7
  • Kho lưu trữ github của E. Smith trên dataclass

2

Từ đặc tả PEP :

Một trình trang trí lớp được cung cấp để kiểm tra định nghĩa lớp cho các biến có chú thích kiểu như được định nghĩa trong PEP 526, "Cú pháp cho chú thích biến". Trong tài liệu này, các biến như vậy được gọi là các trường. Sử dụng các trường này, trình trang trí thêm các định nghĩa phương thức được tạo vào lớp để hỗ trợ khởi tạo cá thể, repr, phương thức so sánh và các phương thức khác tùy chọn như được mô tả trong phần Đặc tả. Một lớp như vậy được gọi là Lớp dữ liệu, nhưng thực sự không có gì đặc biệt về lớp: trình trang trí thêm các phương thức được tạo vào lớp và trả về cùng một lớp mà nó đã được cung cấp.

Các @dataclassmáy phát điện bổ sung thêm phương thức cho lớp mà bạn muốn bằng cách khác xác định mình như __repr__, __init__, __lt__, và __gt__.


2

Hãy xem xét lớp học đơn giản này Foo

from dataclasses import dataclass
@dataclass
class Foo:    
    def bar():
        pass  

Dưới đây là dir()so sánh tích hợp. Ở phía bên trái là Fookhông có trang trí @dataclass, và bên phải là với trang trí @dataclass.

nhập mô tả hình ảnh ở đây

Đây là một khác, sau khi sử dụng các inspectmô-đun để so sánh.

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.