OOP Python untuk Tugas Kuliah: Class, self, __init__, Inheritance
Belajar OOP Python lewat satu contoh: class Mahasiswa dengan hitung IPK, self, __init__, atribut kelas, inheritance dengan super(), dan error praktikum.

Di halaman ini
- Class dan object: membuat class Mahasiswa
- Apa sebenarnya self dan init?
- Atribut kelas vs atribut instance
- Inheritance: subclass MahasiswaBeasiswa dengan super()
- Empat pilar OOP lewat kode yang sama
- Soal tracing gaya UTS: atribut kelas sebagai penghitung
- Kesalahan praktikum OOP yang paling sering
- FAQ
- Kenapa print(objek) hanya menampilkan tulisan seperti <__main__.Mahasiswa object at 0x...>?
- Apa beda method biasa dengan @staticmethod?
- Apakah subclass wajib memanggil super().init?
- Apa beda overriding dan overloading di Python?
- Kapan sebaiknya memakai class daripada fungsi biasa?
OOP di Python berarti mengelompokkan data dan fungsi yang mengolahnya ke dalam satu class, lalu membuat object dari class itu. __init__ mengisi data awal setiap object, self adalah object yang sedang dipakai, dan inheritance membuat class baru yang mewarisi semua isi class lama. Artikel ini memakai satu contoh yang terus dikembangkan: class Mahasiswa yang bisa menghitung IPK, lalu subclass MahasiswaBeasiswa.
Semua output di bawah adalah hasil menjalankan kodenya di Python 3.12.
Class dan object: membuat class Mahasiswa
class Mahasiswa:
kampus = "Universitas Nusantara"
BOBOT = {"A": 4, "B": 3, "C": 2, "D": 1, "E": 0}
def __init__(self, nama, nim):
self.nama = nama
self.nim = nim
self.nilai = []
def tambah_nilai(self, sks, huruf):
if huruf not in self.BOBOT:
raise ValueError(f"Nilai huruf tidak dikenal: {huruf}")
self.nilai.append((sks, huruf))
def hitung_ipk(self):
total_sks = sum(sks for sks, _ in self.nilai)
if total_sks == 0:
return 0.0
total_mutu = sum(sks * self.BOBOT[h] for sks, h in self.nilai)
return round(total_mutu / total_sks, 2)
def info(self):
return f"{self.nama} ({self.nim}) IPK {self.hitung_ipk()}"
Class adalah cetakan. Object adalah benda yang dicetak dari cetakan itu. Baris berikut membuat satu object dan mengisinya dengan empat mata kuliah:
rina = Mahasiswa("Rina", "2301001")
rina.tambah_nilai(3, "A")
rina.tambah_nilai(2, "B")
rina.tambah_nilai(4, "A")
rina.tambah_nilai(3, "C")
print(rina.hitung_ipk())
print(rina.info())
Output:
3.33
Rina (2301001) IPK 3.33
Cek hitungannya secara manual. Total SKS . Total mutu . IPK . Kalau ingin mencocokkan dengan transkripmu sendiri, rumusnya sama dengan cara menghitung IPK dan IP semester, atau langsung pakai kalkulator IPK.
Apa sebenarnya self dan init?
Ketika kamu menulis rina.hitung_ipk(), Python menjalankan Mahasiswa.hitung_ipk(rina). Object di depan titik dikirim sebagai argumen pertama, dan argumen pertama itulah yang diberi nama self. Jadi di dalam method, self.nilai berarti "list nilai milik object yang sedang dipakai", yaitu milik Rina.
Nama self hanya konvensi. Python tidak mewajibkan kata itu, tapi semua orang memakainya dan penilai praktikum akan mempertanyakan nama lain.
__init__ adalah method yang otomatis dijalankan setiap kali object dibuat. Baris Mahasiswa("Rina", "2301001") kira-kira melakukan dua hal: membuat object kosong, lalu memanggil __init__(object_itu, "Rina", "2301001"). Tugas __init__ hanya mengisi atribut awal. Ia tidak perlu return.
Urutan argumennya harus cocok. Kalau kamu menulis Mahasiswa() tanpa argumen, hasilnya:
TypeError: Mahasiswa.__init__() missing 2 required positional arguments: 'nama' and 'nim'
Atribut kelas vs atribut instance
Pada class di atas ada dua jenis atribut:
| Jenis | Contoh | Ditulis di | Dimiliki oleh |
|---|---|---|---|
| Atribut instance | self.nama, self.nilai | Di dalam __init__, dengan self. | Setiap object punya salinan sendiri |
| Atribut kelas | kampus, BOBOT | Langsung di badan class | Dipakai bersama semua object |
BOBOT cocok sebagai atribut kelas karena tabel konversi nilai sama untuk semua mahasiswa. nilai harus atribut instance karena setiap mahasiswa punya daftar nilai sendiri.
Ada satu perilaku yang sering muncul di soal. Kalau kamu menulis rina.kampus = "Kampus Cabang", Python tidak mengubah atribut kelas. Ia membuat atribut instance baru bernama kampus khusus untuk Rina:
sari = Mahasiswa("Sari", "2301003")
rina.kampus = "Kampus Cabang"
print(rina.kampus, sari.kampus, Mahasiswa.kampus)
Kampus Cabang Universitas Nusantara Universitas Nusantara
Inheritance: subclass MahasiswaBeasiswa dengan super()
Mahasiswa penerima beasiswa punya semua sifat mahasiswa biasa, ditambah jenis beasiswa dan batas IPK yang harus dijaga. Daripada menyalin seluruh class, kita mewarisinya:
class MahasiswaBeasiswa(Mahasiswa):
def __init__(self, nama, nim, jenis, ipk_minimal=3.0):
super().__init__(nama, nim)
self.jenis = jenis
self.ipk_minimal = ipk_minimal
def info(self):
status = "aman" if self.hitung_ipk() >= self.ipk_minimal else "di bawah syarat"
return super().info() + f", beasiswa {self.jenis}: {status}"
Baris super().__init__(nama, nim) menjalankan __init__ milik Mahasiswa, sehingga nama, nim, dan list nilai tetap terisi. Setelah itu baru atribut tambahan diisi.
budi = MahasiswaBeasiswa("Budi", "2301002", "Prestasi")
budi.tambah_nilai(3, "B")
budi.tambah_nilai(3, "C")
budi.tambah_nilai(2, "C")
print(budi.info())
Budi (2301002) IPK 2.38, beasiswa Prestasi: di bawah syarat
Hitungannya: mutu , SKS 8, IPK , yang dibulatkan menjadi 2,38. Karena 2,38 kurang dari batas 3,0 dalam contoh ini, statusnya "di bawah syarat".
MahasiswaBeasiswa tidak menulis ulang tambah_nilai atau hitung_ipk, tapi tetap bisa memakainya karena diwarisi. Method info ditulis ulang (overriding), dan versi baru itu memanggil versi lama lewat super().info() lalu menambahkan keterangan beasiswa.
Empat pilar OOP lewat kode yang sama
Soal teori UTS hampir selalu meminta penjelasan empat pilar. Jawaban yang menunjuk ke kode nyata jauh lebih kuat daripada definisi hafalan.
- Encapsulation. Data nilai disimpan di dalam object, dan cara menambahkannya lewat
tambah_nilai, yang menolak huruf tak dikenal seperti"F". Python tidak punyaprivatesungguhan. Awalan garis bawah (self._nilai) hanya penanda bahwa atribut itu sebaiknya tidak diubah langsung dari luar. - Abstraction. Kode pemakai cukup memanggil
rina.hitung_ipk()tanpa perlu tahu bahwa di dalamnya ada perkalian SKS dan bobot, pengecekan SKS nol, dan pembulatan. - Inheritance.
MahasiswaBeasiswa(Mahasiswa)mewarisinama,nim,nilai,tambah_nilai, danhitung_ipktanpa menulis ulang. - Polymorphism. Pemanggilan yang sama menghasilkan perilaku berbeda tergantung jenis object:
for m in [rina, budi]:
print(m.info())
Rina (2301001) IPK 3.33
Budi (2301002) IPK 2.38, beasiswa Prestasi: di bawah syarat
Loop tidak perlu tahu mana mahasiswa biasa dan mana penerima beasiswa. Setiap object menjalankan versi info miliknya sendiri. Satu cek tambahan yang sering ditanya: isinstance(budi, Mahasiswa) bernilai True, sedangkan isinstance(rina, MahasiswaBeasiswa) bernilai False. Subclass adalah jenis khusus dari induknya, bukan sebaliknya.
Soal tracing gaya UTS: atribut kelas sebagai penghitung
Soal ini menguji apakah kamu benar-benar paham beda atribut kelas dan atribut instance. Tentukan outputnya sebelum membaca pembahasan.
class Akun:
jumlah = 0
def __init__(self, nama):
self.nama = nama
Akun.jumlah += 1
self.nomor = Akun.jumlah
a = Akun("Rina")
b = Akun("Budi")
a.jumlah = 10
c = Akun("Sari")
print(a.jumlah, b.jumlah, c.jumlah, Akun.jumlah)
print(a.nomor, b.nomor, c.nomor)
| Baris | Akun.jumlah | Atribut yang dibuat |
|---|---|---|
a = Akun("Rina") | 1 | a.nomor = 1 |
b = Akun("Budi") | 2 | b.nomor = 2 |
a.jumlah = 10 | 2 | a.jumlah = 10 (atribut instance baru milik a) |
c = Akun("Sari") | 3 | c.nomor = 3 |
Output:
10 3 3 3
1 2 3
Baris a.jumlah = 10 tidak menyentuh penghitung kelas. Ia hanya membuat atribut instance bernama sama, yang menutupi atribut kelas khusus untuk object a. Object b dan c tidak punya atribut instance jumlah, sehingga Python mencarinya di class dan menemukan 3. Itulah sebabnya __init__ menulis Akun.jumlah += 1, bukan self.jumlah += 1. Versi self.jumlah += 1 akan membaca 0 dari class, lalu menyimpan 1 sebagai atribut instance, sehingga penghitung kelas tetap 0 selamanya.
Kesalahan praktikum OOP yang paling sering
| Kesalahan | Pesan yang muncul | Perbaikan |
|---|---|---|
Lupa self di definisi method: def sapa(): | TypeError: Mahasiswa.sapa() takes 0 positional arguments but 1 was given | Tulis def sapa(self): |
Lupa self. di __init__: nama = nama | AttributeError: 'Mahasiswa' object has no attribute 'nama' | Tulis self.nama = nama |
Lupa super().__init__ di subclass | AttributeError: 'MahasiswaBeasiswa' object has no attribute 'nilai' | Panggil super().__init__(nama, nim) di baris pertama |
| List ditaruh sebagai atribut kelas | Tidak ada error, tapi nilai bocor ke object lain | Pindahkan ke __init__ sebagai self.nilai = [] |
Kesalahan terakhir paling berbahaya karena tidak memunculkan pesan apa pun. Contohnya:
class Mahasiswa:
nilai = []
def __init__(self, nama):
self.nama = nama
def tambah_nilai(self, x):
self.nilai.append(x)
a = Mahasiswa("Rina")
b = Mahasiswa("Budi")
a.tambah_nilai("A")
print(b.nilai)
Outputnya ['A']. Budi tiba-tiba punya nilai milik Rina, karena keduanya memakai satu list yang sama di level class. Ini kerabat dekat jebakan parameter default berupa list, yang dibahas di beda print dan return di fungsi Python.
Cara membaca pesan-pesan error ini secara umum ada di error Python untuk pemula.
FAQ
Kenapa print(objek) hanya menampilkan tulisan seperti <__main__.Mahasiswa object at 0x...>?
Karena class belum punya cara menampilkan dirinya sebagai teks. Tambahkan method __str__(self) yang mengembalikan string, misalnya return self.info(). Setelah itu print(rina) menampilkan teks tersebut.
Apa beda method biasa dengan @staticmethod?
Method biasa menerima self dan bisa membaca atribut object. @staticmethod tidak menerima self, jadi tidak bisa membaca data object. Pakai untuk fungsi bantu yang kebetulan berkaitan dengan class, misalnya konversi huruf ke bobot.
Apakah subclass wajib memanggil super().init?
Kalau subclass menulis __init__ sendiri dan induknya punya atribut yang dibutuhkan, ya. Kalau subclass sama sekali tidak menulis __init__, Python otomatis memakai milik induk.
Apa beda overriding dan overloading di Python?
Overriding adalah menulis ulang method induk di subclass, seperti info di atas. Overloading (beberapa method bernama sama dengan parameter berbeda) tidak didukung seperti di Java. Definisi kedua akan menimpa yang pertama. Di Python, efek serupa biasanya dibuat dengan parameter default.
Kapan sebaiknya memakai class daripada fungsi biasa?
Saat ada data yang selalu bepergian bersama fungsi yang mengolahnya. Nilai, nama, dan perhitungan IPK cocok disatukan. Kalau tugasnya hanya menghitung sekali dari input lalu selesai, fungsi biasa sudah cukup.
