Data class adalah salah satu fitur Python yang, setelah Anda menemukannya, Anda tidak akan kembali ke cara lama. Pertimbangkan kelas biasa berikut:
classExercise:def__init__(self, name, reps, sets, weight):
self.name = name
self.reps = reps
self.sets = sets
self.weight = weight
Didukung oleh
Bagi saya, definisi kelas itu sangat tidak efisien — di metode __init__, Anda mengulang setiap parameter setidaknya tiga kali. Ini mungkin terdengar sepele, tetapi pikirkan seberapa sering Anda menulis kelas seumur hidup dengan parameter yang jauh lebih banyak.
Sebagai perbandingan, lihat alternatif data class dari kode di atas:
from dataclasses import dataclass
@dataclassclassExercise:
name:str
reps:int
sets:int
weight:float# Weight in lbsDidukung oleh
Potongan kode yang tampak sederhana ini jauh lebih baik daripada kelas biasa. Dekorator kecil @dataclass mengimplementasikan kelas __init__, __repr__, __eq__ di balik layar, yang jika ditulis manual akan memakan setidaknya 20 baris kode.
Selain itu, banyak fitur lain seperti operator perbandingan, pengurutan objek, dan imutabilitas, semuanya hanya berjarak satu baris untuk tercipta secara “ajaib” bagi kelas kita.
Jadi, tujuan tutorial ini adalah menunjukkan mengapa data class adalah salah satu hal terbaik yang terjadi pada Python jika Anda menyukai pemrograman berorientasi objek.
Mari kita mulai!
Dasar-dasar Data Class Python
Mari bahas beberapa konsep mendasar dari data class Python yang membuatnya begitu berguna.
Beberapa metode dihasilkan secara otomatis dalam data class
Meski penuh fitur, data class adalah kelas biasa yang membutuhkan jauh lebih sedikit kode untuk mengimplementasikan fungsionalitas yang sama. Ini kelas Exercise lagi:
from dataclasses import dataclass
@dataclassclassExercise:
name:str
reps:int
sets:int
weight:float
ex1 = Exercise("Bench press",10,3,52.5)# Verifying Exercise is a regular class
ex1.name
'Bench press'Didukung oleh
Saat ini, Exercise sudah memiliki metode __repr__ dan __eq__ yang terimplementasi. Mari kita verifikasi:
repr(ex1)"Exercise(name='Bench press', reps=10, sets=3, weight=52.5)"Didukung oleh
Representasi objek oleh repr harus mengembalikan kode yang dapat merekreasinya, dan kita bisa lihat itulah yang terjadi pada ex1.
Sebagai perbandingan, Exercise yang didefinisikan dengan cara lama akan terlihat seperti ini:
classExercise:def__init__(self, name, reps, sets, weight):
self.name = name
self.reps = reps
self.sets = sets
self.weight = weight
ex3 = Exercise("Bench press",10,3,52.5)
ex3
<__main__.Exercise at 0x7f6834100130>Didukung oleh
Terlihat buruk dan tidak berguna!
Sekarang, mari verifikasi keberadaan __eq__, yaitu operator kesetaraan:
# Redefine the class@dataclassclassExercise:
name:str
reps:int
sets:int
weight:float
ex1 = Exercise("Bench press",10,3,52.5)
ex2 = Exercise("Bench press",10,3,52.5)Didukung oleh
Membandingkan kelas dengan dirinya sendiri dan dengan kelas lain yang parameternya identik harus menghasilkan True:
Dan memang begitu! Dalam kelas biasa, logika ini akan merepotkan untuk ditulis.
Data class memerlukan type hint
Seperti yang mungkin Anda perhatikan, data class memerlukan type hint saat mendefinisikan field. Faktanya, data class mengizinkan tipe apa pun dari modul typing. Misalnya, berikut cara membuat field yang dapat menerima tipe data Any:
from typing import Any
@dataclassclassDummy:
attr: AnyDidukung oleh
Namun, keunikan Python adalah meskipun data class memerlukan type hint, tipe tersebut sebenarnya tidak dipaksakan.
Sebagai contoh, membuat instance kelas Exercise dengan tipe data yang sama sekali tidak benar tetap dapat dijalankan tanpa error:
silly_exercise = Exercise("Bench press","ten","three sets",52.5)
silly_exercise.sets
“Three sets”Didukung oleh
Jika Anda ingin memaksa tipe data, Anda harus menggunakan type checker seperti Mypy.
Data class memungkinkan nilai default pada field
Sejauh ini, kita belum menambahkan nilai default pada kelas kita. Mari kita perbaiki:
@dataclassclassExercise:
name:str="Push-ups"
reps:int=10
sets:int=3
weight:float=0# Now, all fields have defaults
ex5 = Exercise()
ex5
Exercise(name='Push-ups', reps=10, sets=3, weight=0)Didukung oleh
Ingat bahwa field non-default tidak boleh berada setelah field default. Misalnya, kode di bawah ini akan melempar error:
@dataclassclassExercise:
name:str="Push-ups"
reps:int=10
sets:int=3
weight:float# NOT ALLOWED
ex5 = Exercise()
ex5
TypeError: non-default argument 'weight' follows default argumentDidukung oleh
Dalam praktiknya, Anda jarang mendefinisikan default dengan sintaks name: type = value.
Sebagai gantinya, Anda akan menggunakan fungsi field yang memberi kontrol lebih pada setiap definisi field:
from dataclasses import field
@dataclassclassExercise:
name:str= field(default="Push-up")
reps:int= field(default=10)
sets:int= field(default=3)
weight:float= field(default=0)# Now, all fields have defaults
ex5 = Exercise()
ex5
Exercise(name='Push-up', reps=10, sets=3, weight=0)Didukung oleh
Fungsi field memiliki lebih banyak parameter, seperti:
repr
init
compare
default_factory
dan seterusnya. Kita akan membahas ini di bagian berikutnya.
Data class dapat dibuat dengan fungsi
Catatan terakhir tentang dasar data class adalah bahwa definisinya bisa lebih singkat lagi dengan menggunakan fungsi make_dataclass:
from dataclasses import make_dataclass
Exercise = make_dataclass("Exercise",[("name",str),("reps",int),("sets",int),("weight",float),],)
ex3 = Exercise("Deadlifts",8,3,69.0)
ex3
Exercise(name='Deadlifts', reps=8, sets=3, weight=69.0)Didukung oleh
Namun Anda akan mengorbankan keterbacaan, jadi saya tidak menyarankan menggunakan fungsi ini.
Data Class Python Lanjutan
Di bagian ini, kita akan membahas fitur-fitur lanjutan data class yang memberikan lebih banyak manfaat. Salah satunya adalah default factory.
Default factory
Untuk menjelaskan default factory, mari buat kelas lain bernama WorkoutSession yang menerima dua field:
from dataclasses import dataclass
from typing import List
@dataclassclassExercise:
name:str="Push-ups"
reps:int=10
sets:int=3
weight:float=0@dataclassclassWorkoutSession:
exercises: List[Exercise]
duration_minutes:intDidukung oleh
Dengan menggunakan tipe List, kita menyatakan bahwa WorkoutSession menerima daftar instance Exercise.
# Define the Exercise instances for HIIT training
ex1 = Exercise(name="Burpees", reps=15, sets=3)
ex2 = Exercise(name="Mountain Climbers", reps=20, sets=3)
ex3 = Exercise(name="Jump Squats", reps=12, sets=3)
exercises_monday =[ex1, ex2, ex3]
hiit_monday = WorkoutSession(exercises=exercises_monday, duration_minutes=30)Didukung oleh
Saat ini, setiap instance sesi latihan memerlukan exercises untuk diinisialisasi. Namun ini tidak mencerminkan cara orang berolahraga — pertama, mereka memulai sesi (mungkin di aplikasi), lalu menambahkan latihan seiring berjalannya waktu.
Jadi, kita harus bisa membuat sesi tanpa latihan dan tanpa durasi. Mari wujudkan ini dengan menambahkan daftar kosong sebagai nilai default untuk exercises:
@dataclassclassWorkoutSession:
exercises: List[Exercise]=[]
duration_minutes:int=None
hiit_monday = WorkoutSession("25-02-2024")
ValueError: mutable default <class'list'>for field exercises isnot allowed: use default_factoryDidukung oleh
Namun, kita mendapat error — ternyata data class tidak mengizinkan nilai default yang mutable.
Untungnya, kita bisa memperbaikinya dengan menggunakan default factory:
Parameter default_factory menerima fungsi yang mengembalikan nilai awal untuk field data class. Artinya, ia dapat menerima fungsi apa pun:
tuple
dict
set
Fungsi kustom buatan pengguna apa pun
Ini berlaku terlepas dari apakah hasil fungsi tersebut mutable atau tidak.
Sekarang, jika dipikir-pikir, kebanyakan orang memulai latihan dengan pemanasan yang biasanya serupa untuk jenis latihan apa pun. Jadi, menginisialisasi sesi tanpa latihan mungkin bukan yang diinginkan sebagian orang.
Sebagai gantinya, mari buat fungsi yang mengembalikan tiga Exercise pemanasan:
defcreate_warmup():return[
Exercise("Jumping jacks",30,1),
Exercise("Squat lunges",10,2),
Exercise("High jumps",20,1),]@dataclassclassWorkoutSession:
exercises: List[Exercise]= field(default_factory=create_warmup)
duration_minutes:int=5# Increase the default duration as well
hiit_monday = WorkoutSession()
hiit_monday
WorkoutSession(exercises=[Exercise(name='Jumping jacks', reps=30, sets=1, weight=0), Exercise(name='Squat lunges', reps=10, sets=2, weight=0), Exercise(name='High jumps', reps=20, sets=1, weight=0)], duration_minutes=5)Didukung oleh
Sekarang, setiap kali kita membuat sesi, sesi tersebut akan memuat beberapa latihan pemanasan yang sudah tercatat. Versi baru WorkoutSession memiliki durasi default lima menit untuk mengakomodasi hal itu.
Menambahkan metode ke data class
Karena data class adalah kelas biasa, menambahkan metode ke dalamnya tetap sama. Mari tambahkan dua metode ke data class WorkoutSession kita:
Dengan menggunakan metode-metode ini, sekarang kita dapat mencatat aktivitas baru ke sebuah sesi:
hiit_monday = WorkoutSession()# Log a new exercise
new_exercise = Exercise("Deadlifts",6,4,60)
hiit_monday.add_exercise(new_exercise)
hiit_monday.increase_duration(15)Didukung oleh
Saat kita mencetak sesi, representasi default-nya terlalu verbose dan sulit dibaca karena berisi kode untuk merekreasikan objek. Mari kita perbaiki.
__repr__ dan __str__ dalam data class
Data class mengimplementasikan __repr__ secara otomatis tetapi tidak __str__. Ini membuat kelas menggunakan __repr__ ketika kita memanggil print pada objek tersebut.
Jadi, mari timpa perilaku ini dengan mendefinisikan __str__ sendiri:
@dataclassclassExercise:
name:str="Push-ups"
reps:int=10
sets:int=3
weight:float=0def__str__(self):
base =f"{self.name}: {self.reps}/{self.sets}"if self.weight ==0:return base
return base +f", {self.weight} lbs"
ex1 = Exercise(name="Burpees", reps=15, sets=3)
ex1
Exercise(name='Burpees', reps=15, sets=3, weight=0)Didukung oleh
__repr__ masih sama, tetapi ketika kita memanggil print:
Representasi string kelas jauh lebih enak dibaca. Sekarang, mari perbaiki WorkoutSession juga:
@dataclassclassWorkoutSession:
exercises: List[Exercise]= field(default_factory=create_warmup)
duration_minutes:int=5# Increase the default duration as welldefadd_exercise(self, exercise: Exercise):
self.exercises.append(exercise)defincrease_duration(self, minutes:int):
self.duration_minutes += minutes
def__str__(self):
base =""for ex in self.exercises:
base +=str(ex)+"\n"
base +=f"\nSession duration: {self.duration_minutes} minutes."return base
hiit_monday = WorkoutSession()print(hiit_monday)
Jumping jacks:30/1
Squat lunges:10/2
High jumps:20/1
Session duration:5 minutes.Didukung oleh
Catatan: Gunakan tombol “Explain code” di bagian bawah cuplikan untuk mendapatkan penjelasan kode baris demi baris.
Sekarang, kita mendapatkan keluaran yang ringkas dan mudah dibaca.
Perbandingan dalam data class
Untuk banyak kelas, masuk akal untuk membandingkan objeknya dengan suatu logika. Untuk latihan, ini bisa berupa durasi latihan, intensitas, atau beban.
Pertama, mari lihat apa yang terjadi jika kita mencoba membandingkan dua sesi latihan dalam keadaan saat ini:
hiit_wednesday = WorkoutSession()
hiit_wednesday.add_exercise(Exercise("Pull-ups",7,3))print(hiit_wednesday)
Jumping jacks:30/1
Squat lunges:10/2
High jumps:20/1
Pull-ups:7/3
Session duration:5 minutes.
hiit_monday > hiit_wednesday
TypeError:'>'not supported between instances of 'WorkoutSession'and'WorkoutSession'Didukung oleh
Kita menerima TypeError karena data class tidak mengimplementasikan operator perbandingan. Namun ini mudah diperbaiki dengan menyetel parameter order ke True:
Kali ini, perbandingan berhasil, tetapi apa yang sebenarnya kita bandingkan?
Dalam data class, perbandingan dilakukan sesuai urutan field didefinisikan. Saat ini, kelas dibandingkan berdasarkan durasi latihan karena field pertama, exercises, berisi objek non-standar.
Kita bisa memverifikasi ini dengan menambah durasi sesi hari Rabu:
Meskipun sesi Senin berlangsung lebih lama, perbandingan menunjukkan bahwa sesi tersebut lebih kecil daripada Rabu. Alasannya adalah “25” datang sebelum “27” dalam perbandingan string Python.
Jadi, bagaimana kita mempertahankan urutan field dan tetap mengurutkan sesi berdasarkan durasi latihan? Ini mudah melalui fungsi field:
Dengan menyetel compare ke False untuk field apa pun, kita mengecualikannya dari pengurutan, seperti yang dibuktikan oleh hasil di atas.
Manipulasi field pasca-inisialisasi
Saat ini, kita memiliki durasi sesi default lima menit untuk mengakomodasi latihan pemanasan. Namun, ini hanya masuk akal jika pengguna memulai sesi dengan pemanasan. Bagaimana jika mereka memulai sesi dengan latihan lain:
new_session = WorkoutSession([Exercise("Diamond push-ups",10,3)])
new_session.duration_minutes
5Didukung oleh
Untuk satu latihan saja, total durasi lima menit terasa tidak masuk akal. Setiap sesi harus secara dinamis memperkirakan durasinya berdasarkan jumlah set dari tiap latihan. Artinya kita harus membuat duration_minutes bergantung pada field exercises.
Mari kita implementasikan:
@dataclassclassWorkoutSession:
exercises: List[Exercise]= field(default_factory=create_warmup)
duration_minutes:int= field(default=0, init=False)def__post_init__(self):
set_duration =3for ex in self.exercises:
self.duration_minutes += ex.sets * set_duration
...Didukung oleh
Kali ini, kita mendefinisikan duration_minutes dengan init disetel ke False untuk menunda inisialisasi field.
Lalu, di dalam metode khusus __post_init__, kita memperbarui nilainya berdasarkan total jumlah set di setiap Exercise.
Sekarang, ketika kita menginisialisasi WorkoutSession, duration_minutes secara dinamis bertambah tiga menit untuk setiap set di tiap latihan.
# Adding an exercise with three sets
hiit_friday = WorkoutSession([Exercise("Diamond push-ups",10,3)])
hiit_friday.duration_minutes
9Didukung oleh
Secara umum, jika Anda ingin mendefinisikan field yang bergantung pada field lain dari data class Anda, Anda dapat menggunakan logika __post_init__.
Imutabilitas dalam Data Class
Data class WorkoutSession kita hampir siap; tinggal perlu dilindungi. Saat ini, kelas ini mudah sekali diutak-atik:
hiit_friday.duration_minutes =1000
hiit_friday.duration_minutes
1000del hiit_friday.exercisesDidukung oleh
Kita ingin melindungi semua field kelas kita agar hanya bisa dimodifikasi dengan cara yang kita inginkan. Untuk mencapai ini, dekorator @dataclass menawarkan argumen frozen yang praktis:
@dataclass(frozen=True)classFrozenExercise:
name:str
reps:int
sets:int
weight:int|float=0
ex1 = FrozenExercise("Muscle-ups",5,3)Didukung oleh
Sekarang, jika kita ingin mengubah field apa pun, kita mendapat error:
ex1.sets =5
FrozenInstanceError: cannot assign to field 'sets'Didukung oleh
Menyetel frozen ke True secara otomatis menambahkan metode __deleteattr__ dan __setattr__ untuk setiap field sehingga terlindung dari penghapusan atau pembaruan setelah inisialisasi. Juga, orang lain tidak akan bisa menambahkan field baru:
ex1.new_field =10
FrozenInstanceError: cannot assign to field 'new_field'Didukung oleh
Fungsionalitas ini akan membutuhkan puluhan baris kode jika kita menggunakan kelas tradisional.
Namun, perlu dicatat bahwa kita tidak bisa membuat kelas benar-benar immutable. Misalnya, mari tulis ulang WorkoutSession dengan frozen disetel ke True:
@dataclass(frozen=True)classImmutableWorkoutSession:
exercises: List[Exercise]= field(default_factory=create_warmup)
duration_minutes:int=5
session1 = ImmutableWorkoutSession()Didukung oleh
Seperti yang diharapkan, kita tidak bisa langsung memodifikasi daftar latihan:
Namun, exercises adalah daftar, yang sepenuhnya mutable, sehingga operasi berikut memungkinkan:
# Mengubah salah satu elemen dalam daftar
# Changing one of the elements in a list
session1.exercises[1]= FrozenExercise("Totally new exercise",5,5)print(session1)
ImmutableWorkoutSession(exercises=[Exercise(name='Jumping jacks', reps=30, sets=1, weight=0), FrozenExercise(name='Totally new exercise', reps=5, sets=5, weight=0), Exercise(name='High jumps', reps=20, sets=1, weight=0)], duration_minutes=5)Didukung oleh
Jadi, untuk melindungi dari perubahan yang tidak disengaja, disarankan menggunakan objek immutable seperti tuple untuk nilai field.
Pewarisan dalam data class
Satu poin terakhir yang akan kita bahas adalah urutan field di kelas induk dan turunan.
Karena data class adalah kelas biasa, pewarisan berjalan seperti biasa:
@dataclass(frozen=True)classImmutableWorkoutSession:
exercises: List[Exercise]= field(default_factory=create_warmup)
duration_minutes:int=5@dataclass(frozen=True)classCardioWorkoutSession(ImmutableWorkoutSession):passDidukung oleh
Namun, karena field terakhir di kelas induk (ImmutableWorkoutSession) memiliki nilai default, semua field di kelas turunan harus memiliki nilai default.
Sebagai contoh, ini tidak diizinkan:
@dataclass(frozen=True)classImmutableWorkoutSession:
exercises: List[Exercise]= field(default_factory=create_warmup)
duration_minutes:int=5@dataclass(frozen=True)classCardioWorkoutSession(ImmutableWorkoutSession):
intensity_level:str# Not allowed, must have a default
TypeError: non-default argument 'intensity_level' follows default argument
Didukung oleh
Kekurangan Data Class dan Sumber Lanjutan
Data class terus meningkat sejak Python 3.7 (sejak awal pun sudah bagus) dan mencakup banyak kasus penggunaan saat Anda perlu menulis kelas. Namun, data class bisa kurang menguntungkan dalam skenario berikut:
Metode __init__ kustom
Metode __new__ kustom
Beragam pola pewarisan
Dan banyak lagi, seperti dibahas dalam thread Reddit yang bagus ini. Jika Anda menginginkan alasan lebih rinci mengapa data class diperkenalkan dan mengapa mereka bukan pengganti langsung definisi kelas biasa, bacalah PEP 557.
Jika Anda tertarik pada pemrograman berorientasi objek secara umum, berikut kursus untuk melanjutkan perjalanan Anda:
Pada dasarnya, data class adalah struktur yang lebih canggih untuk menyimpan dan mengambil data dengan lebih efisien. Namun, Python memiliki banyak struktur data lain yang melakukan tugas ini dengan cara yang kurang lebih serupa. Misalnya, Anda dapat mempelajari counter, defaultdict, dan namedtuple di bab terakhir dari kursus Data Types for Data Science.
Kuasai pertanyaan wawancara basis data, dari konsep SQL dasar hingga skenario desain sistem tingkat lanjut. Panduan mendalam ini mencakup semua yang Anda perlukan untuk sukses di wawancara DBMS dan meraih peran berikutnya.
Temukan alasan mengapa Anda sebaiknya (tidak) menggunakan spaghetti plot untuk menyampaikan ketidakpastian jalur prediksi badai serta dampaknya terhadap interpretasi.
Kembangkan keterampilan data Anda, pelajari cara mengolah dan memvisualisasikan data, serta terapkan analisis canggih untuk mengambil keputusan berbasis data.
Data class adalah salah satu fitur Python yang, setelah Anda menemukannya, Anda tidak akan kembali ke cara lama. Pertimbangkan kelas biasa berikut:
Bagi saya, definisi kelas itu sangat tidak efisien — di metode
__init__, Anda mengulang setiap parameter setidaknya tiga kali. Ini mungkin terdengar sepele, tetapi pikirkan seberapa sering Anda menulis kelas seumur hidup dengan parameter yang jauh lebih banyak.Sebagai perbandingan, lihat alternatif data class dari kode di atas:
Potongan kode yang tampak sederhana ini jauh lebih baik daripada kelas biasa. Dekorator kecil
@dataclassmengimplementasikan kelas__init__,__repr__,__eq__di balik layar, yang jika ditulis manual akan memakan setidaknya 20 baris kode.Selain itu, banyak fitur lain seperti operator perbandingan, pengurutan objek, dan imutabilitas, semuanya hanya berjarak satu baris untuk tercipta secara “ajaib” bagi kelas kita.
Jadi, tujuan tutorial ini adalah menunjukkan mengapa data class adalah salah satu hal terbaik yang terjadi pada Python jika Anda menyukai pemrograman berorientasi objek.
Mari kita mulai!
Dasar-dasar Data Class Python
Mari bahas beberapa konsep mendasar dari data class Python yang membuatnya begitu berguna.
Beberapa metode dihasilkan secara otomatis dalam data class
Meski penuh fitur, data class adalah kelas biasa yang membutuhkan jauh lebih sedikit kode untuk mengimplementasikan fungsionalitas yang sama. Ini kelas
Exerciselagi:Saat ini,
Exercisesudah memiliki metode__repr__dan__eq__yang terimplementasi. Mari kita verifikasi:Representasi objek oleh
reprharus mengembalikan kode yang dapat merekreasinya, dan kita bisa lihat itulah yang terjadi padaex1.Sebagai perbandingan,
Exerciseyang didefinisikan dengan cara lama akan terlihat seperti ini:Terlihat buruk dan tidak berguna!
Sekarang, mari verifikasi keberadaan
__eq__, yaitu operator kesetaraan:Membandingkan kelas dengan dirinya sendiri dan dengan kelas lain yang parameternya identik harus menghasilkan True:
Dan memang begitu! Dalam kelas biasa, logika ini akan merepotkan untuk ditulis.
Data class memerlukan type hint
Seperti yang mungkin Anda perhatikan, data class memerlukan type hint saat mendefinisikan field. Faktanya, data class mengizinkan tipe apa pun dari modul
typing. Misalnya, berikut cara membuat field yang dapat menerima tipe dataAny:Namun, keunikan Python adalah meskipun data class memerlukan type hint, tipe tersebut sebenarnya tidak dipaksakan.
Sebagai contoh, membuat instance kelas
Exercisedengan tipe data yang sama sekali tidak benar tetap dapat dijalankan tanpa error:Jika Anda ingin memaksa tipe data, Anda harus menggunakan type checker seperti Mypy.
Data class memungkinkan nilai default pada field
Sejauh ini, kita belum menambahkan nilai default pada kelas kita. Mari kita perbaiki:
Ingat bahwa field non-default tidak boleh berada setelah field default. Misalnya, kode di bawah ini akan melempar error:
Dalam praktiknya, Anda jarang mendefinisikan default dengan sintaks
name: type = value.Sebagai gantinya, Anda akan menggunakan fungsi
fieldyang memberi kontrol lebih pada setiap definisi field:Fungsi
fieldmemiliki lebih banyak parameter, seperti:reprinitcomparedefault_factorydan seterusnya. Kita akan membahas ini di bagian berikutnya.
Data class dapat dibuat dengan fungsi
Catatan terakhir tentang dasar data class adalah bahwa definisinya bisa lebih singkat lagi dengan menggunakan fungsi
make_dataclass:Namun Anda akan mengorbankan keterbacaan, jadi saya tidak menyarankan menggunakan fungsi ini.
Data Class Python Lanjutan
Di bagian ini, kita akan membahas fitur-fitur lanjutan data class yang memberikan lebih banyak manfaat. Salah satunya adalah default factory.
Default factory
Untuk menjelaskan default factory, mari buat kelas lain bernama
WorkoutSessionyang menerima dua field:Dengan menggunakan tipe
List, kita menyatakan bahwaWorkoutSessionmenerima daftar instanceExercise.Saat ini, setiap instance sesi latihan memerlukan exercises untuk diinisialisasi. Namun ini tidak mencerminkan cara orang berolahraga — pertama, mereka memulai sesi (mungkin di aplikasi), lalu menambahkan latihan seiring berjalannya waktu.
Jadi, kita harus bisa membuat sesi tanpa latihan dan tanpa durasi. Mari wujudkan ini dengan menambahkan daftar kosong sebagai nilai default untuk
exercises:Namun, kita mendapat error — ternyata data class tidak mengizinkan nilai default yang mutable.
Untungnya, kita bisa memperbaikinya dengan menggunakan default factory:
Parameter
default_factorymenerima fungsi yang mengembalikan nilai awal untuk field data class. Artinya, ia dapat menerima fungsi apa pun:tupledictsetIni berlaku terlepas dari apakah hasil fungsi tersebut mutable atau tidak.
Sekarang, jika dipikir-pikir, kebanyakan orang memulai latihan dengan pemanasan yang biasanya serupa untuk jenis latihan apa pun. Jadi, menginisialisasi sesi tanpa latihan mungkin bukan yang diinginkan sebagian orang.
Sebagai gantinya, mari buat fungsi yang mengembalikan tiga
Exercisepemanasan:Sekarang, setiap kali kita membuat sesi, sesi tersebut akan memuat beberapa latihan pemanasan yang sudah tercatat. Versi baru
WorkoutSessionmemiliki durasi default lima menit untuk mengakomodasi hal itu.Menambahkan metode ke data class
Karena data class adalah kelas biasa, menambahkan metode ke dalamnya tetap sama. Mari tambahkan dua metode ke data class
WorkoutSessionkita:Dengan menggunakan metode-metode ini, sekarang kita dapat mencatat aktivitas baru ke sebuah sesi:
Tetapi ada masalah:
Saat kita mencetak sesi, representasi default-nya terlalu verbose dan sulit dibaca karena berisi kode untuk merekreasikan objek. Mari kita perbaiki.
__repr__dan__str__dalam data classData class mengimplementasikan
__repr__secara otomatis tetapi tidak__str__. Ini membuat kelas menggunakan__repr__ketika kita memanggil print pada objek tersebut.Jadi, mari timpa perilaku ini dengan mendefinisikan
__str__sendiri:__repr__masih sama, tetapi ketika kita memanggilprint:Representasi string kelas jauh lebih enak dibaca. Sekarang, mari perbaiki
WorkoutSessionjuga:Catatan: Gunakan tombol “Explain code” di bagian bawah cuplikan untuk mendapatkan penjelasan kode baris demi baris.
Sekarang, kita mendapatkan keluaran yang ringkas dan mudah dibaca.
Perbandingan dalam data class
Untuk banyak kelas, masuk akal untuk membandingkan objeknya dengan suatu logika. Untuk latihan, ini bisa berupa durasi latihan, intensitas, atau beban.
Pertama, mari lihat apa yang terjadi jika kita mencoba membandingkan dua sesi latihan dalam keadaan saat ini:
Kita menerima
TypeErrorkarena data class tidak mengimplementasikan operator perbandingan. Namun ini mudah diperbaiki dengan menyetel parameterorderkeTrue:Kali ini, perbandingan berhasil, tetapi apa yang sebenarnya kita bandingkan?
Dalam data class, perbandingan dilakukan sesuai urutan field didefinisikan. Saat ini, kelas dibandingkan berdasarkan durasi latihan karena field pertama,
exercises, berisi objek non-standar.Kita bisa memverifikasi ini dengan menambah durasi sesi hari Rabu:
Sesuai dugaan, kita menerima
False.Namun apa yang terjadi jika field pertama
Workoutadalah tipe lain, misalnya string? Mari kita coba:Meskipun sesi Senin berlangsung lebih lama, perbandingan menunjukkan bahwa sesi tersebut lebih kecil daripada Rabu. Alasannya adalah “25” datang sebelum “27” dalam perbandingan string Python.
Jadi, bagaimana kita mempertahankan urutan field dan tetap mengurutkan sesi berdasarkan durasi latihan? Ini mudah melalui fungsi
field:Dengan menyetel
comparekeFalseuntuk field apa pun, kita mengecualikannya dari pengurutan, seperti yang dibuktikan oleh hasil di atas.Manipulasi field pasca-inisialisasi
Saat ini, kita memiliki durasi sesi default lima menit untuk mengakomodasi latihan pemanasan. Namun, ini hanya masuk akal jika pengguna memulai sesi dengan pemanasan. Bagaimana jika mereka memulai sesi dengan latihan lain:
Untuk satu latihan saja, total durasi lima menit terasa tidak masuk akal. Setiap sesi harus secara dinamis memperkirakan durasinya berdasarkan jumlah set dari tiap latihan. Artinya kita harus membuat
duration_minutesbergantung pada fieldexercises.Mari kita implementasikan:
Kali ini, kita mendefinisikan
duration_minutesdenganinitdisetel keFalseuntuk menunda inisialisasi field.Lalu, di dalam metode khusus
__post_init__, kita memperbarui nilainya berdasarkan total jumlah set di setiapExercise.Sekarang, ketika kita menginisialisasi
WorkoutSession,duration_minutessecara dinamis bertambah tiga menit untuk setiap set di tiap latihan.Secara umum, jika Anda ingin mendefinisikan field yang bergantung pada field lain dari data class Anda, Anda dapat menggunakan logika
__post_init__.Imutabilitas dalam Data Class
Data class
WorkoutSessionkita hampir siap; tinggal perlu dilindungi. Saat ini, kelas ini mudah sekali diutak-atik:Kita ingin melindungi semua field kelas kita agar hanya bisa dimodifikasi dengan cara yang kita inginkan. Untuk mencapai ini, dekorator
@dataclassmenawarkan argumenfrozenyang praktis:Sekarang, jika kita ingin mengubah field apa pun, kita mendapat error:
Menyetel
frozenkeTruesecara otomatis menambahkan metode__deleteattr__dan__setattr__untuk setiap field sehingga terlindung dari penghapusan atau pembaruan setelah inisialisasi. Juga, orang lain tidak akan bisa menambahkan field baru:Fungsionalitas ini akan membutuhkan puluhan baris kode jika kita menggunakan kelas tradisional.
Namun, perlu dicatat bahwa kita tidak bisa membuat kelas benar-benar immutable. Misalnya, mari tulis ulang
WorkoutSessiondenganfrozendisetel keTrue:Seperti yang diharapkan, kita tidak bisa langsung memodifikasi daftar latihan:
Namun,
exercisesadalah daftar, yang sepenuhnya mutable, sehingga operasi berikut memungkinkan:# Mengubah salah satu elemen dalam daftar
Jadi, untuk melindungi dari perubahan yang tidak disengaja, disarankan menggunakan objek immutable seperti tuple untuk nilai field.
Pewarisan dalam data class
Satu poin terakhir yang akan kita bahas adalah urutan field di kelas induk dan turunan.
Karena data class adalah kelas biasa, pewarisan berjalan seperti biasa:
Namun, karena field terakhir di kelas induk (
ImmutableWorkoutSession) memiliki nilai default, semua field di kelas turunan harus memiliki nilai default.Sebagai contoh, ini tidak diizinkan:
Kekurangan Data Class dan Sumber Lanjutan
Data class terus meningkat sejak Python 3.7 (sejak awal pun sudah bagus) dan mencakup banyak kasus penggunaan saat Anda perlu menulis kelas. Namun, data class bisa kurang menguntungkan dalam skenario berikut:
__init__kustom__new__kustomDan banyak lagi, seperti dibahas dalam thread Reddit yang bagus ini. Jika Anda menginginkan alasan lebih rinci mengapa data class diperkenalkan dan mengapa mereka bukan pengganti langsung definisi kelas biasa, bacalah PEP 557.
Jika Anda tertarik pada pemrograman berorientasi objek secara umum, berikut kursus untuk melanjutkan perjalanan Anda:
Pada dasarnya, data class adalah struktur yang lebih canggih untuk menyimpan dan mengambil data dengan lebih efisien. Namun, Python memiliki banyak struktur data lain yang melakukan tugas ini dengan cara yang kurang lebih serupa. Misalnya, Anda dapat mempelajari counter,
defaultdict, dannamedtupledi bab terakhir dari kursus Data Types for Data Science.