Apa itu kelas data dan apa bedanya dengan kelas umum?

141

Dengan PEP 557 kelas data dimasukkan ke pustaka standar python.

Mereka menggunakan @dataclassdekorator dan mereka seharusnya menjadi "nama yang dapat diubah dengan standar" tetapi saya tidak begitu yakin saya mengerti apa arti sebenarnya dan bagaimana mereka berbeda dari kelas umum.

Apa sebenarnya kelas data python dan kapan waktu terbaik untuk menggunakannya?

raja julian
sumber
8
Mengingat konten PEP yang luas, apa lagi yang ingin Anda ketahui? namedtuples tidak dapat diubah dan tidak dapat memiliki nilai default untuk atribut, sedangkan kelas data bisa berubah dan dapat memilikinya.
jonrsharpe
31
@jonrsharpe Sepertinya masuk akal bagi saya bahwa harus ada threadoverflow pada subjek. Stackoverflow dimaksudkan untuk menjadi ensiklopedia dalam format tanya jawab, bukan? Jawabannya tidak pernah "lihat saja di situs web lain ini." Seharusnya tidak ada downvotes di sini.
Luke Davis
12
Ada lima utas tentang cara menambahkan item ke daftar. Satu pertanyaan pada @dataclasstidak akan menyebabkan situs hancur.
eric
2
@jonrsharpe namedtuplesBISA memiliki nilai default. Lihat di sini: stackoverflow.com/questions/11351032/…
MJB

Jawaban:

152

Kelas data hanyalah kelas reguler yang diarahkan untuk menyimpan keadaan, lebih dari mengandung banyak logika. Setiap kali Anda membuat kelas yang sebagian besar terdiri dari atribut Anda membuat kelas data.

Apa yang dilakukan dataclassesmodul adalah membuatnya lebih mudah untuk membuat kelas data. Itu mengurus banyak pelat ketel untuk Anda.

Ini sangat penting ketika kelas data Anda harus hashable; ini membutuhkan __hash__metode dan __eq__metode. Jika Anda menambahkan __repr__metode khusus untuk kemudahan debugging, itu bisa menjadi sangat bertele-tele:

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))

Dengan dataclassesAnda dapat menguranginya menjadi:

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

Kelas dekorator yang sama juga dapat menghasilkan metode perbandingan ( __lt__, __gt__, dll) dan menangani kekekalan.

namedtuplekelas juga kelas data, tetapi tidak dapat diubah secara default (juga urutannya). dataclassesjauh lebih fleksibel dalam hal ini, dan dapat dengan mudah disusun sedemikian rupa sehingga mereka dapat mengisi peran yang sama dengan namedtuplekelas .

PEP terinspirasi oleh attrsproyek , yang dapat melakukan lebih banyak lagi (termasuk slot, validator, konverter, metadata, dll.).

Jika Anda ingin melihat beberapa contoh, saya baru-baru ini menggunakan dataclassesbeberapa solusi Advent of Code saya , lihat solusi untuk hari 7 , hari 8 , hari 11 dan hari 20 .

Jika Anda ingin menggunakan dataclassesmodul dalam versi Python <3.7, maka Anda dapat menginstal modul backported (memerlukan 3.6) atau menggunakan attrsproyek yang disebutkan di atas.

Martijn Pieters
sumber
2
Dalam contoh pertama apakah Anda sengaja menyembunyikan anggota kelas dengan anggota contoh dengan nama yang sama? Tolong bantu memahami idiom ini.
VladimirLenin
4
@VladimirLenin: tidak ada atribut kelas, hanya ada anotasi jenis. Lihat PEP 526 , khususnya bagian Anotasi variabel variabel dan instance .
Martijn Pieters
1
@ Bananan: @dataclassmenghasilkan __init__metode yang kira-kira sama , dengan quantity_on_handargumen kata kunci dengan nilai default. Saat Anda membuat instance, itu akan mengatur quantity_on_handatribut instance, selalu. Jadi contoh pertama saya , non-dataclass menggunakan pola yang sama untuk mengulangi apa yang akan dilakukan kode dataclass.
Martijn Pieters
1
@Bananach: sehingga dalam contoh pertama, kita bisa saja omit menetapkan atribut contoh dan tidak bayangan atribut kelas, itu adalah berlebihan pengaturan tetap dalam arti itu, tapi dataclasses jangan mengaturnya.
Martijn Pieters
1
@ user2853437 kasing Anda tidak benar-benar didukung oleh dataclasses; mungkin Anda akan lebih baik menggunakan sepupu dataclasses yang lebih besar, attrs . Proyek itu mendukung konverter per-bidang yang memungkinkan Anda menormalkan nilai-nilai bidang. Jika Anda ingin tetap menggunakan kacamata, maka ya, lakukan normalisasi dalam __post_init__metode ini.
Martijn Pieters
62

Gambaran

Pertanyaannya telah diatasi. Namun, jawaban ini menambahkan beberapa contoh praktis untuk membantu dalam pemahaman dasar kacamata.

Apa sebenarnya kelas data python dan kapan waktu terbaik untuk menggunakannya?

  1. generator kode : menghasilkan kode boilerplate; Anda dapat memilih untuk menerapkan metode khusus di kelas reguler atau meminta dataclass menerapkannya secara otomatis.
  2. wadah data : struktur yang menyimpan data (misalnya tupel dan dikt), seringkali dengan titik, akses atribut seperti kelas, namedtupledan lainnya .

"dapat dinamai namesuple dengan [s] default"

Inilah yang dimaksud frasa terakhir:

  • bisa berubah : secara default, atribut dataclass dapat dipindahkan. Secara opsional Anda dapat membuatnya tidak berubah (lihat Contoh di bawah).
  • namedtuple : Anda telah bertitik, akses atribut seperti namedtuplekelas biasa atau.
  • default : Anda dapat menetapkan nilai default ke atribut.

Dibandingkan dengan kelas umum, Anda lebih menghemat mengetik kode boilerplate.


fitur

Ini adalah gambaran umum fitur-fitur dataclass (TL; DR? Lihat Tabel Ringkasan di bagian selanjutnya).

Apa yang kau dapatkan

Berikut adalah fitur-fitur yang Anda dapatkan dari kacamata standar.

Atribut + Representasi + Perbandingan

import dataclasses


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

Default ini disediakan dengan secara otomatis mengatur kata kunci berikut untuk True:

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

Apa yang bisa Anda nyalakan

Fitur tambahan tersedia jika kata kunci yang sesuai diatur ke True.

Memesan

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

Metode pemesanan sekarang diterapkan (operator kelebihan beban:) < > <= >=, mirip dengan functools.total_orderingdengan uji kesetaraan yang lebih kuat.

Hashable, Mutable

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

Meskipun objek berpotensi berubah (mungkin tidak diinginkan), hash diimplementasikan.

Hashable, Immutable

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

Hash sekarang diimplementasikan dan mengubah objek atau menugaskan ke atribut tidak diizinkan.

Secara keseluruhan, objek tersebut dapat di hash jika salah satu unsafe_hash=Trueatau frozen=True.

Lihat juga tabel logika hashing asli dengan detail lebih lanjut.

Apa yang tidak Anda dapatkan

Untuk mendapatkan fitur berikut, metode khusus harus diterapkan secara manual:

Membongkar

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

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

Optimasi

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

Ukuran objek sekarang berkurang:

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

Dalam beberapa keadaan, __slots__juga meningkatkan kecepatan membuat instance dan mengakses atribut. Juga, slot tidak memungkinkan penetapan standar; jika tidak, a ValueErrordinaikkan.

Lihat lebih banyak di slot di posting blog ini .


Tabel Ringkasan

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       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__                              |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+

+ Metode-metode ini tidak dihasilkan secara otomatis dan membutuhkan implementasi manual dalam sebuah dataclass.

* __ne__ tidak diperlukan dan karenanya tidak diterapkan .


Fitur tambahan

Pasca inisialisasi

@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)

Warisan

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

Konversi

Ubah dataclass menjadi tuple atau dict, secara rekursif :

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

Keterbatasan


Referensi

  • Pembicaraan R. Hettinger tentang Dataclasses: Pembuat kode untuk mengakhiri semua pembuat kode
  • Pembicaraan T. Hunner tentang Kelas yang Lebih Mudah: Kelas Python Tanpa Semua Cruft
  • Dokumentasi Python tentang detail hashing
  • Nyata Python panduan tentang The Ultimate Guide to Kelas data dengan Python 3.7
  • Posting blog A. Shaw pada tur singkat kelas data Python 3.7
  • E. gudang github Smith pada dataclasses
pylang
sumber
2

Dari spesifikasi PEP :

Dekorator kelas disediakan untuk memeriksa definisi kelas untuk variabel dengan anotasi tipe seperti yang didefinisikan dalam PEP 526, "Sintaks untuk Anotasi Variabel". Dalam dokumen ini, variabel tersebut disebut bidang. Menggunakan bidang-bidang ini, dekorator menambahkan definisi metode yang dihasilkan ke kelas untuk mendukung inisialisasi instance, repr, metode perbandingan, dan opsional metode lain seperti yang dijelaskan di bagian Spesifikasi. Kelas semacam itu disebut Kelas Data, tetapi benar-benar tidak ada yang istimewa tentang kelas: dekorator menambahkan metode yang dihasilkan ke kelas dan mengembalikan kelas yang sama seperti yang diberikan.

The @dataclassGenerator menambahkan metode untuk kelas yang Anda sebaliknya akan mendefinisikan diri sendiri seperti __repr__, __init__, __lt__, dan __gt__.

Mahmoud Hanafy
sumber
2

Pertimbangkan kelas sederhana ini Foo

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

Berikut ini adalah dir()perbandingan built-in. Di sisi kiri adalah Footanpa dekorator @dacaclass, dan di kanan adalah dekorator @dacaclass.

masukkan deskripsi gambar di sini

Berikut ini perbedaan lain, setelah menggunakan inspectmodul untuk perbandingan.

masukkan deskripsi gambar di sini

prosti
sumber