Tutorial Python: Menguasai Modul DataClasses

Tutorial Python Dataclasses

Dataclasses adalah modul Python yang pertama kali diperkenalkan di Python 3.7. Modul dataclasses bertujuan untuk memudahkan pembuatan kelas di mana fungsi utamanya adalah untuk menyimpan data. Dengan menggunakan dataclasses, kita bisa membuat kelas data secara lebih efisien dan praktis karena modul ini mampu mengurangi banyak boilerplate code yang biasanya diperlukan saat mendefinisikan kelas biasa di Python. Selain itu, dataclasses secara otomatis menyediakan metode seperti __init__, __repr__, __eq__, dan lainnya, sehingga kita tidak perlu mendefinisikannya secara manual.

Berikut ini adalah beberapa keuntungan menggunakan dataclasses untuk menyimpan struktur data dibandingkan kelas biasa:

  • Otomatisasi metode standar: dataclasses secara otomatis menghasilkan metode-metode standar seperti __init__, __repr__, __eq__ dan __hash__ (jika diperlukan). Hal ini menjadikan proses pembuatan kelas lebih efisien dan mengurangi kesalahan.
  • Kemudahan penggunaan: membuat kelas data dengan default values, validasi tipe, dan fitur lainnya lebih mudah dilakukan dengan dataclasses. Kita bisa dengan cepat dan sederhana mendefinisikan atribut-atribut kelas sesuai kebutuhan tanpa perlu menulis kode tambahan.
  • Immutability: dataclasses memungkinkan kita untuk membuat kelas yang immutable dengan mudah cukup menggunakan parameter frozen=True. Dengan ini, objek yang dibuat dari kelas tersebut tidak bisa diubah setelah dibuat, sehingga untuk keperluan tertentu menjadi lebih aman dan stabil.
  • Pengelolaan metadata: dataclasses memungkinkan penambahan metadata ke dalam field menggunakan fungsi field(). Adanya metadata akan mempermudah pengelolaan informasi tambahan yang mungkin diperlukan dalam konteks tertentu tanpa mengganggu struktur utama dari kelas data.

Secara keseluruhan, dataclasses memberikan cara yang lebih efisien, praktis, dan fleksibel untuk mendefinisikan kelas data di Python, sehingga sangat bermanfaat bagi pengembang yang sering bekerja dengan aplikasi berbasis data (data-driven applications).

Membuat Dataclasses

Pembuatan dataclasses cukup dengan mendefinisikan kelas seperti biasa dan menambahkan dekorator @dataclass. Misalkan kita membuat sebuah dataclass dengan nama Employee dengan 5 atribut yaitu id, name, tenure, position dan salary.

note: Tipe data dalam bahasa python bersifat dinamis (dinamic typing), yaitu ditentukan saat kode dijalankan. Sehingga suatu variabel dapat menyimpan tipe apapun sesuai nilai yang diberikan. Untuk membuat kode lebih mudah dibaca kita dapat menambahkan informasi tipe data pada variabel (type hinting) seperti pada contoh kode ini (misal id: int). Namun, perlu diingat hal ini hanya bertujuan agar kode mudah dibaca saja dan tidak akan menyebabkan error ketika diberikan nilai dengan tipe apapun yang berbeda.

Python

from dataclasses import dataclass

@dataclass
class Employee:
    id: int
    name: str
    tenure: int
    position: str
    salary: float

Inisiasi Objek Dataclasses

Setelah dataclass Employee dibuat, maka kita dapat menggunakannya untuk membuat objek dataclass tersebut. Mari kita buat satu objek dari Employee dengan menentukan nilai setiap atributnya.

Python

# Membuat objek Employee 
tonystark = Employee(
    id="AVG-01",
    name="Tony Stark",
    tenure=10,
    position="Manager",
    salary=500_000,
)

# Mencetak objek
print(tonystark)
# OUTPUT

Employee(id='AVG-01', name='Tony Stark', tenure=10, position='Manager', salary=500000)

Contoh dataclass di atas, mengharuskan kita untuk menetapkan nilai dari setiap atributnya. Jika suatu objek diinisiasi tanpa beberapa atributnya maka akan menyebabkan error, misalkan seperti contoh berikut:

Python

# Membuat Objek Employee (Error)
# ada atribut yang tidak ditentukan
steveroger = Employee(
    id="AVG-02",
    name="Steve Roger",
)
# OUTPUT

Traceback (most recent call last):
  File "C:\Users\cahya\OneDrive\Documents\11. sainsdata.id\Posts (041-080)\080. Dataclasses Python\employee.py", line 29, in <module>
    steveroger = Employee(
                 ^^^^^^^^^
TypeError: Employee.__init__() missing 3 required positional arguments: 'tenure', 'position', and 'salary'

Nilai Default

Terkadang, kita mungkin ingin menetapkan nilai default untuk beberapa atribut dalam sebuah dataclass. Praktik ini sangat bermanfaat ketika ada nilai-nilai tertentu yang sering digunakan atau secara logis lebih masuk akal jika memiliki nilai default. Dengan memberikan nilai default, kita dapat mengurangi kebutuhan untuk menetapkan nilai-nilai tersebut setiap kali membuat instance baru dari kelas, sehingga kode menjadi lebih bersih dan efisien.

Mari kita perbarui dataclass Employee dengan menambahkan nilai default misalkan untuk atribut tenure, position dan salary. Nilai default untuk tenure kita atur menjadi 0, position menjadi "Staf" dan salary menjadi 50.000. Selanjutnya mari buat dua objek lagi dan memanfaatkan nilai default yang sudah ditentukan.

Objek pertama yaitu steveroger, diinisiasi tanpa atribut tenure, position dan salary. Dengan demikian nilai ketiga atribut tersebut akan menggunakan nilai defaultnya. Objek berikutnya adalah kamalakhan, di mana kita inisiasi tanpa atribut tenure sehingga juga akan menggunakan nilai defaultnya yaitu 0.

Python

@dataclass
class Employee:
    id: int
    name: str
    tenure: int = 0
    position: str = "Staf"
    salary: float = 50_000

# Membuat objek baru hanya dengan atribut id dan name
steveroger = Employee(
    id="AVG-02",
    name="Steve Roger"
)

# Membuat objek baru tanpa atribut tenure
kamalakhan = Employee(
    id="AVG-03",
    name="Kamala Khan",
    position="Intern",
    salary=25_000

print(kamalakhan)

# merubah nilai atribut tenure
kamalakhan.tenure = 1

print(kamalakhan)
# OUTPUT

Employee(id='AVG-02', name='Steve Roger', tenure=0, position='Staf', salary=50000)
Employee(id='AVG-03', name='Kamala Khan', tenure=0, position='Intern', salary=25000)
Employee(id='AVG-03', name='Kamala Khan', tenure=1, position='Intern', salary=25000)

Frozen Dataclasses

Pada kondisi tertentu kita mungkin memerlukan data-data yang tidak dapat dirubah (immutable) setelah dibuat. Penggunaan dataclass memungkinkan kita untuk menyimpan data yang bersifat immutable tersebut. dataclass immutable dapat dibuat dengan menambahkan parameter frozen=True pada dekorator-nya.

Ketika dataclass diatur dengan frozen=True maka atribut dari objek dataclass tersebut tidak dapat dimodifikasi dan akan menghasilkan error jika dilakukan.

Python

@dataclass(frozen=True)
class Employee:
    id: int
    name: str
    tenure: int = 0
    position: str = "Staf"
    salary: float = 50_000

brucebanner = Employee(id="AVG-4", name="Bruce Banner")
print(brucebanner, "\n")

# Merubah Nilai salary (Error)
brucebanner.salary = 10_000_000
# OUTPUT

Employee(id='AVG-4', name='Bruce Banner', tenure=0, position='Staf', salary=50000)

Traceback (most recent call last):
  File "C:\Users\cahya\OneDrive\Documents\11. sainsdata.id\Posts (041-080)\080. Dataclasses Python\employee.py", line 65, in <module>
    brucebanner.salary = 10_000_000
    ^^^^^^^^^^^^^^^^^^
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'salary'

Method

Seperti kelas pada umumnya, dataclass juga dapat memiliki method untuk mengerjakan berbagai fungsi. Pembuatan method-nya juga sama dengan kelas biasa. Contoh di bawah ini adalah dataclass Employee dengan tambahan 2 method untuk menghitung nilai pajak dan untuk menambahkan salary dengan nilai tertentu.

Python

@dataclass
class Employee:
    id: int
    name: str
    tenure: int = 0
    position: str = "Staf"
    salary: float = 50_000

    # method menghitung tax berdasarkan salary
    def calculate_tax(self):
        if self.salary > 100_000:
            return 0.15 * self.salary
        elif self.salary > 50_000:
            return 0.10 * self.salary
        else:
            return 0.05 * self.salary

    # method penambahan salary
    def increase_salary(self, nominal):
        self.salary += nominal


# membuat objek baru
natasharomanoff = Employee(
    id="AVG-05",
    name="Natasha Romanoff",
    position="Supervisor",
    salary=250_000,
)

print(natasharomanoff)

# menghitung Tax objek natasharomanoff
nr_tax = natasharomanoff.calculate_tax()
print(f"Tax: {nr_tax}")

# menaikkan Salary objek natasharomanoff
natasharomanoff.increase_salary(50_000)
print(f"New Salary: {natasharomanoff.salary}")

print(f"New Tax: {natasharomanoff.calculate_tax()}")
# OUTPUT

Employee(id='AVG-05', name='Natasha Romanoff', tenure=0, position='Supervisor', salary=250000)
Tax: 37500.0
New Salary: 300000
New Tax: 45000.0

Perbandingan Objek

Secara default dataclass secara otomatis memiliki method __eq__, sehingga kita dapat membandingkan kesamaan dua buah objek. Objek akan dianggap sama, jika dan hanya jika nilai pada setiap atributnya juga sama. Selain itu, ojek-objek dari suatu dataclass juga dapat diatur sehingga memiliki urutan tertentu tanpa mendefinisikannya secara eksplisit. Untuk membandingkan objek menggunakan operator <, >, <=, atau >=, kita bisa menggunakan decorator @dataclass(order=True). Pengaturan ini akan membuat secara otomatis metode perbandingan (__lt__, __le__, __gt__, __ge__) berdasarkan urutan field yang didefinisikan dalam dataclass.

Pada contoh dataclass yang kita miliki, maka objek dengan atribut id yang lebih kecil akan dianggap lebih kecil. Jika nilai atribut tersebut sama, maka akan dilihat pada atribut berikutnya yaitu name dan seterusnya sampai atribut terakhir yaitu salary. Dengan sifat ini, maka kumpulan objek-objek (misalkan dalam bentuk list) dapat secara langsung diurutkan baik itu secara menaik (ascending) maupun menurun (descending).

Python

@dataclass(order=True)
class Employee:
    id: int
    name: str
    tenure: int = 0
    position: str = "Staf"
    salary: float = 50_000

    # method menghitung tax berdasarkan salary
    def calculate_tax(self):
        if self.salary > 100_000:
            return 0.15 * self.salary
        elif self.salary > 50_000:
            return 0.10 * self.salary
        else:
            return 0.05 * self.salary

    # method penambahan salary
    def increase_salary(self, nominal):
        self.salary += nominal


# Membuat 4 objek Employee
em_1 = Employee(id="AVG-10", name="Peter Parker", tenure=8)
em_2 = Employee(id="AVG-10", name="Peter Parker", tenure=10)
em_3 = Employee(id="AVG-08", name="Thor", tenure=12)
em_4 = Employee(id="AVG-09", name="Peter Quill", tenure=6)

print(em_1 == em_2)   # False
print(em_1 < em_2)    # True (karena em_2.tenure > em_1.tenure)
print(em_3 < em_4)    # True (karena em_3.id < em_3.id)


# Membuat list dengan 4 objek Employee
list_em = [em_1, em_2, em_3, em_4]

# Mengurutkan elemen ascending
list_em.sort()

print("\nPengurutan Ascending:")

for em in list_em:
    print(em)

# Mengurutkan elemen descending
list_em.sort(reverse=True)

print("\nPengurutan Descending")
for em in list_em:
    print(em)
# OUTPUT

False
True
True

Pengurutan Ascending:
Employee(id='AVG-08', name='Thor', tenure=12, position='Staf', salary=50000)
Employee(id='AVG-09', name='Peter Quill', tenure=6, position='Staf', salary=50000)
Employee(id='AVG-10', name='Peter Parker', tenure=8, position='Staf', salary=50000)
Employee(id='AVG-10', name='Peter Parker', tenure=10, position='Staf', salary=50000)

Pengurutan Descending
Employee(id='AVG-10', name='Peter Parker', tenure=10, position='Staf', salary=50000)
Employee(id='AVG-10', name='Peter Parker', tenure=8, position='Staf', salary=50000)
Employee(id='AVG-09', name='Peter Quill', tenure=6, position='Staf', salary=50000)
Employee(id='AVG-08', name='Thor', tenure=12, position='Staf', salary=50000)

Field dan Metadata

Metadata adalah informasi tambahan yang kita tambahkan ke field untuk menyediakan detail tambahan yang mungkin berguna untuk berbagai tujuan. Berikut beberapa fungsi dari metadata pada dataclass:

  • Dokumentasi: metadata dapat digunakan untuk mendokumentasikan tujuan atau penggunaan field tertentu. Ini sangat berguna untuk memberikan penjelasan tambahan kepada pengembang lain yang menggunakan kelas tersebut.
  • Validasi: metadata dapat digunakan untuk menyimpan aturan validasi untuk field. Misalnya batasan nilai atau tipe data yang valid untuk field tertentu.
  • Konfigurasi: metadata dapat menyimpan konfigurasi tambahan yang digunakan oleh alat atau library eksternal. Misalnya pada ORM (Object-Relational Mapping), dapat menggunakan metadata untuk memetakan field ke kolom database.
  • User Interface: metadata dapat digunakan untuk menentukan bagaimana field ditampilkan atau diedit dalam antarmuka pengguna. Misalnya pada label atau hint untuk input form.
  • Analitik: metadata dapat menyimpan informasi tambahan yang berguna untuk analitik atau pemrosesan data lebih lanjut.

Pada contoh di bawah ini, kita membuat nilai atribut tenure dan salary menggunakan field. Di dalam field kita dapat menentukan nilai default-nya pada parameter default dan parameter metadata berupa dictionary yang dapat berisi informasi apapun. Misalkan untuk atribut tenure kita tambahkan metadata help dan untuk salary berupa metadata dengan tiga key yaitu help, min dan max.

Kita juga menambahkan method baru yaitu check_salary_range di mana dilakukan pengecekan nilai minimum dan maksimum yang tersimpan di dalam metadata, dan jika nilai salary tidak sesuai akan menampilkan informasi ketidaksesuaian tersebut. Denganca ra yang sama tentunya kita juga dapat mengecek metadata lainnya misalkan help.

Perlu diingat metadata bukanlah bentuk validasi isian data atribut. Nilai apapun yang disimpan di dalam metadata tidak akan menghalangi atribut tersebut untuk memiliki nilai yang tidak sesuai. Namun tentunya informasi di dalamnya bisa menjadi dokumentasi yang nantinya dapat diakses jika diperlukan.

Python

from dataclasses import dataclass, Field

@dataclass(order=True)
class Employee:
    id: int
    name: str
    tenure: int = field(default=0, metadata={"help": "Tenure (in months)"})
    position: str = "Staf"
    salary: float = field(
        default=50_000,
        metadata={
            "help": "Annual Salary in USD",
            "min": 10_000,
            "max": 1_000_000,
        },
    )

    # method menghitung tax berdasarkan salary
    def calculate_tax(self):
        if self.salary > 100_000:
            return 0.15 * self.salary
        elif self.salary > 50_000:
            return 0.10 * self.salary
        else:
            return 0.05 * self.salary

    # method penambahan salary
    def increase_salary(self, nominal):
        self.salary += nominal

    # method pengecekan nilai salary berdasarkan metadata
    def check_salary_range(self):
        info = self.__dataclass_fields__["salary"].metadata
        min = info.get("min", 0)    # ambil nilai min dari metadata (jika tidak ada maka 0)
        max = info.get("max", 0)    # ambil nilai max dari metadata (jika tidak ada maka 0)

        if self.salary > max or self.salary < min:
            print("Nilai Salary di luar batas yang ditentukan ")


logan = Employee(id="AVG-99", name="Logan", salary=2_000_000)

print(logan)

logan.check_salary_range()
# OUTPUT

Employee(id='AVG-99', name='Logan', tenure=0, position='Staf', salary=2000000)
Nilai Salary di luar batas yang ditentukan 

Misalkan untuk keperluan dokumentasi kita tambahkan fungsi menampilkan semua informasi help bagi dataclass Employee.

Python

def print_help(cls):
    for field_name, field_info in cls.__dataclass_fields__.items():
        help_text = field_info.metadata.get("help", "No help available")
        print(f"{field_name}: {help_text}")


# Menggunakan fungsi print_help
print_help(Employee)
# OUTPUT

id: No help available
name: No help available
tenure: Tenure (in months)
position: No help available
salary: Annual Salary in USD

Kesimpulan

dataclasses di Python menyediakan teknik yang sederhana dan efisien untuk mendefinisikan kelas yang digunakan terutama untuk menyimpan data. Dengan menggunakan decorator @dataclass, kita bisa secara otomatis menghasilkan metode-metode umum dan mengurangi jumlah kode yang harus kita tulis. Untuk informasi selengkapmya, silahkan melihat dokumentasi resmi Python tentang dataclasses di sini.

Tulisan Lainnya

Tutorial Pemrograman Python

You may also like...

Daftar Isi