Pengantar kelas data

Salah satu fitur baru yang diperkenalkan di Python 3.7 adalah kelas Data. Mereka dirancang untuk mengotomatisasi pembuatan kode untuk kelas yang digunakan untuk menyimpan data. Terlepas dari kenyataan bahwa mereka menggunakan mekanisme kerja lain, mereka dapat dibandingkan dengan "tuple bernama yang bisa berubah dengan nilai default."



Pendahuluan


Semua contoh di atas memerlukan Python 3.7 atau lebih tinggi untuk operasi mereka.

Sebagian besar pengembang python harus menulis kelas-kelas ini secara teratur:


class RegularBook: def __init__(self, title, author): self.title = title self.author = author 

Sudah dalam contoh ini, redundansi terlihat. Judul dan pengidentifikasi penulis digunakan beberapa kali. Kelas nyata juga akan berisi metode yang ditimpa __eq__ dan __repr__ .


Modul dataclasses berisi dekorator @dataclass . Dengan menggunakannya, kode yang mirip akan terlihat seperti ini:


 from dataclasses import dataclass @dataclass class Book: title: str author: str 

Penting untuk dicatat bahwa anotasi jenis diperlukan . Semua bidang yang tidak memiliki tanda jenis akan diabaikan. Tentu saja, jika Anda tidak ingin menggunakan jenis tertentu, Anda dapat menentukan Any dari modul typing .


Apa yang Anda dapatkan sebagai hasilnya? Anda secara otomatis mendapatkan kelas, dengan metode yang diterapkan __init__ , __repr__ , __eq__ dan __eq__ . Selain itu, ini akan menjadi kelas reguler dan Anda dapat mewarisi darinya atau menambahkan metode sewenang-wenang.


 >>> book = Book(title="Fahrenheit 451", author="Bradbury") >>> book Book(title='Fahrenheit 451', author='Bradbury') >>> book.author 'Bradbury' >>> other = Book("Fahrenheit 451", "Bradbury") >>> book == other True 

Alternatif


Tuple atau kamus


Tentu saja, jika strukturnya cukup sederhana, Anda dapat menyimpan data dalam kamus atau tuple:


 book = ("Fahrenheit 451", "Bradbury") other = {'title': 'Fahrenheit 451', 'author': 'Bradbury'} 

Namun, pendekatan ini memiliki kelemahan:


  • Harus diingat bahwa variabel berisi data yang terkait dengan struktur ini.
  • Dalam hal kamus, Anda harus melacak nama-nama tombol. Inisialisasi kamus {'name': 'Fahrenheit 451', 'author': 'Bradbury'} juga akan benar secara formal.
  • Dalam kasus tuple, Anda harus melacak urutan nilai, karena mereka tidak memiliki nama.

Ada opsi yang lebih baik:


Namedtuple


 from collections import namedtuple NamedTupleBook = namedtuple("NamedTupleBook", ["title", "author"]) 

Jika kita menggunakan kelas yang dibuat dengan cara ini, kita mendapatkan hal yang sama seperti menggunakan kelas data.


 >>> book = NamedTupleBook("Fahrenheit 451", "Bradbury") >>> book.author 'Bradbury' >>> book NamedTupleBook(title='Fahrenheit 451', author='Bradbury') >>> book == NamedTupleBook("Fahrenheit 451", "Bradbury")) True 

Namun terlepas dari kesamaan umum, tuple bernama memiliki keterbatasan. Mereka datang dari fakta bahwa tuple bernama masih tuple.


Pertama, Anda masih dapat membandingkan contoh kelas yang berbeda.


 >>> Car = namedtuple("Car", ["model", "owner"]) >>> book = NamedTupleBook("Fahrenheit 451", "Bradbury")) >>> book == Car("Fahrenheit 451", "Bradbury") True 

Kedua, tuple bernama tidak dapat diubah. Dalam beberapa situasi, ini berguna, tetapi saya ingin lebih banyak fleksibilitas.
Akhirnya, Anda dapat beroperasi pada tuple bernama juga yang biasa. Misalnya, beralih lagi.


Proyek lainnya


Jika tidak terbatas pada perpustakaan standar, Anda dapat menemukan solusi lain untuk masalah ini. Secara khusus, proyek ini menarik. Ia dapat melakukan lebih dari sekadar dataclass dan bekerja pada versi python yang lebih lama seperti 2.7 dan 3.4. Namun demikian, fakta bahwa itu bukan bagian dari perpustakaan standar mungkin merepotkan


Ciptaan


Anda dapat menggunakan dekorator @dataclass untuk membuat kelas data. Dalam hal ini, semua bidang kelas yang ditentukan dengan anotasi tipe akan digunakan dalam metode yang sesuai dari kelas yang dihasilkan.


Sebagai alternatif, ada fungsi make_dataclass , yang berfungsi serupa untuk membuat tupel bernama.


 from dataclasses import make_dataclass Book = make_dataclass("Book", ["title", "author"]) book = Book("Fahrenheit 451", "Bradbury") 

Nilai default


Salah satu fitur yang bermanfaat adalah kemudahan menambahkan nilai default ke bidang. Masih tidak perlu mendefinisikan ulang metode __init__ , cukup tentukan nilai langsung di kelas.


 @dataclass class Book: title: str = "Unknown" author: str = "Unknown author" 

Mereka akan diperhitungkan dalam metode __init__ dihasilkan


 >>> Book() Book(title='Unknown', author='Unknown author') >>> Book("Farenheit 451") Book(title='Farenheit 451', author='Unknown author') 

Tetapi seperti halnya kelas dan metode reguler, Anda harus berhati-hati menggunakan standar yang bisa berubah. Jika, misalnya, Anda perlu menggunakan daftar sebagai nilai default, ada cara lain, tetapi lebih pada itu di bawah ini.


Selain itu, penting untuk memantau urutan bidang mana dengan nilai default yang ditentukan, karena sama persis dengan urutannya dalam metode __init__


Kelas Data Tidak Berubah


Contoh tuple bernama tidak berubah. Dalam banyak situasi, ini adalah ide yang bagus. Untuk kelas data, Anda dapat melakukannya juga. Cukup tentukan parameter frozen=True saat membuat kelas, dan jika Anda mencoba mengubah bidangnya, pengecualian FrozenInstanceError akan FrozenInstanceError


 @dataclass(frozen=True) class Book: title: str author: str 

 >>> book = Book("Fahrenheit 451", "Bradbury") >>> book.title = "1984" dataclasses.FrozenInstanceError: cannot assign to field 'title' 

Pengaturan Kelas Data


Selain parameter frozen , dekorator @dataclass memiliki parameter lain:


  • init : jika True (default), metode __init__ dihasilkan. Jika kelas sudah memiliki metode __init__ yang ditentukan, parameter diabaikan.
  • repr : memungkinkan (secara default) pembuatan metode __repr__ . String yang dihasilkan berisi nama kelas dan nama serta representasi dari semua bidang yang didefinisikan dalam kelas. Dalam hal ini, masing-masing bidang dapat dikecualikan (lihat di bawah)
  • eq : memungkinkan (secara default) pembuatan metode __eq__ . Objek dibandingkan dengan cara yang sama seolah-olah mereka adalah tupel yang berisi nilai bidang yang sesuai. Selain itu, pencocokan jenis diperiksa.
  • order memungkinkan (default tidak aktif) pembuatan metode __lt__ , __le__ , __gt__ dan __ge__ . Objek dibandingkan dengan cara yang sama dengan tupel nilai bidang yang sesuai. Pada saat yang sama, jenis objek juga diperiksa. Jika order ditentukan, tetapi eq tidak, pengecualian ValueError akan dibuang. Juga, kelas tidak boleh berisi metode perbandingan yang sudah didefinisikan.
  • unsafe_hash memengaruhi pembuatan metode __hash__ . Perilaku juga tergantung pada nilai-nilai parameter eq dan frozen

Kustomisasi masing-masing bidang


Dalam sebagian besar situasi standar, ini tidak diperlukan, tetapi dimungkinkan untuk menyesuaikan perilaku kelas data hingga masing-masing bidang menggunakan fungsi bidang.


Default yang Dapat Diubah


Situasi khas yang disebutkan di atas adalah penggunaan daftar atau nilai default yang bisa berubah-ubah. Anda mungkin menginginkan kelas "rak buku" yang berisi daftar buku. Jika Anda menjalankan kode berikut:


 @dataclass class Bookshelf: books: List[Book] = [] 

interpreter akan melaporkan kesalahan:


 ValueError: mutable default <class 'list'> for field books is not allowed: use default_factory 

Namun, untuk nilai yang dapat diubah lainnya, peringatan ini tidak akan berfungsi dan akan menyebabkan perilaku program yang salah.


Untuk menghindari masalah, disarankan untuk menggunakan parameter default_factory dari fungsi field . Nilainya dapat berupa objek atau fungsi apa pun yang disebut tanpa parameter.
Versi kelas yang benar terlihat seperti ini:


 @dataclass class Bookshelf: books: List[Book] = field(default_factory=list) 

Pilihan lain


Selain default_factory ditentukan, fungsi bidang memiliki parameter berikut:


  • default : nilai default . Parameter ini diperlukan karena bidang ajakan menggantikan nilai bidang default.
  • init : memungkinkan (default) penggunaan suatu bidang dalam metode __init__
  • repr : memungkinkan (default) penggunaan suatu bidang dalam metode __repr__
  • compare termasuk (default) penggunaan bidang dalam metode perbandingan ( __eq__ , __le__ dan lainnya)
  • hash : mungkin nilai boolean atau None . Jika True , bidang digunakan untuk menghitung hash. Jika None ditentukan (secara default), nilai parameter compare digunakan.
    Salah satu alasan untuk menentukan hash=False untuk compare=True diberikan compare=True mungkin kesulitan menghitung hash bidang sementara itu diperlukan untuk perbandingan.
  • metadata : kamus khusus atau None . Nilai tersebut dibungkus dalam MappingProxyType sehingga menjadi tidak berubah. Parameter ini tidak digunakan oleh kelas data itu sendiri dan dimaksudkan untuk ekstensi pihak ketiga.

Memproses setelah inisialisasi


Metode __init__ dibuat secara otomatis memanggil metode __post_init__ , jika didefinisikan dalam kelas. Sebagai aturan, ini disebut dalam bentuk self.__post_init__() , namun, jika variabel tipe InitVar didefinisikan di kelas, mereka akan dilewatkan sebagai parameter metode.


Jika metode __init__ belum dihasilkan, maka __post_init__ tidak akan dipanggil.


Misalnya, tambahkan deskripsi buku yang dihasilkan


 @dataclass class Book: title: str author: str desc: str = None def __post_init__(self): self.desc = self.desc or "`%s` by %s" % (self.title, self.author) 

 >>> Book("Fareneheit 481", "Bradbury") Book(title='Fareneheit 481', author='Bradbury', desc='`Fareneheit 481` by Bradbury') 

Parameter untuk inisialisasi saja


Salah satu kemungkinan yang terkait dengan metode __post_init__ adalah parameter yang hanya digunakan untuk inisialisasi. Jika, ketika mendeklarasikan sebuah field, tentukan InitVar sebagai tipenya, nilainya akan diteruskan sebagai parameter dari metode __post_init__ . Tidak ada cara lain yang bidang yang digunakan dalam kelas data.


 @dataclass class Book: title: str author: str gen_desc: InitVar[bool] = True desc: str = None def __post_init__(self, gen_desc: str): if gen_desc and self.desc is None: self.desc = "`%s` by %s" % (self.title, self.author) 

 >>> Book("Fareneheit 481", "Bradbury") Book(title='Fareneheit 481', author='Bradbury', desc='`Fareneheit 481` by Bradbury') >>> Book("Fareneheit 481", "Bradbury", gen_desc=False) Book(title='Fareneheit 481', author='Bradbury', desc=None) 

Warisan


Ketika Anda menggunakan dekorator @dataclass , ia melewati semua kelas induk dimulai dengan objek dan untuk setiap kelas data menemukan itu menyimpan bidang dalam kamus yang diurutkan, kemudian menambahkan properti dari kelas yang diproses. Semua metode yang dihasilkan menggunakan bidang dari kamus yang dipesan.


Akibatnya, jika kelas induk mendefinisikan nilai default, Anda harus mendefinisikan bidang dengan nilai default.


Karena kamus yang diurutkan menyimpan nilai-nilai dalam urutan penyisipan, untuk kelas-kelas berikut


 @dataclass class BaseBook: title: Any = None author: str = None @dataclass class Book(BaseBook): desc: str = None title: str = "Unknown" 

metode __init__ dengan tanda tangan ini akan dihasilkan:


 def __init__(self, title: str="Unknown", author: str=None, desc: str=None) 

Source: https://habr.com/ru/post/id415829/


All Articles