06 โ Object-Oriented Programming (OOP)
Estimasi: 8 jam Tujuan: Paham class, inheritance, dan dunder methods. Wajib karena PyTorch, scikit-learn, HuggingFace semuanya OOP.
Kenapa Materi Ini Penting?
Kalau kamu buka dokumentasi PyTorch, scikit-learn, atau HuggingFace, hampir semua API-nya class-based. model = LinearRegression(), tokenizer = AutoTokenizer.from_pretrained(...), class MyModel(nn.Module). Tanpa pemahaman OOP yang solid, kamu akan kesulitan baca dokumentasi, debug error, atau extend behavior model.
OOP juga adalah cara berpikir tentang enkapsulasi โ menyembunyikan kompleksitas di balik interface yang clean. Saat kamu bikin model ML production, kamu mau user-nya cukup panggil .predict(), tidak peduli bagaimana model tersebut load weights, preprocess input, dan post-process output. OOP yang membuat itu mungkin.
Analogi besar:
- Class = blueprint rumah (gambar arsitektur).
- Object/Instance = rumah jadi yang kamu tinggal di dalamnya.
- Satu blueprint bisa bikin banyak rumah, masing-masing punya interior beda (attribute), tapi struktur dasar sama (method).
Peta Konsep
flowchart TD
A[๐๏ธ OOP] --> B[๐ Class & Object]
A --> C[๐ Attributes]
A --> D[โ๏ธ Methods]
A --> E[๐ณ Inheritance]
A --> F[๐ Encapsulation]
A --> G[โจ Dunder Methods]
A --> H[๐ฆ Dataclass]
B --> B1[Blueprint vs Instance]
C --> C1[Instance vs Class attr]
D --> D1[Instance method]
D --> D2[classmethod]
D --> D3[staticmethod]
D --> D4[@property]
E --> E1[Single]
E --> E2[Multiple]
E --> E3[super]
G --> G1[__init__, __str__, __repr__]
G --> G2[__eq__, __lt__]
G --> G3[__add__, __mul__]
Bagian 1 โ Kenapa OOP?
Tanpa OOP
# Data user disimpan di dict
user1 = {"nama": "Budi", "umur": 25, "saldo": 1000000}
user2 = {"nama": "Ani", "umur": 30, "saldo": 500000}
# Function untuk operasi
def tambah_saldo(user, jumlah):
user["saldo"] += jumlah
def info_user(user):
print(f"{user['nama']}: Rp{user['saldo']:,}")
tambah_saldo(user1, 50000)
info_user(user1)
Masalah:
- Data dan function terpisah
- Mudah salah pakai (misal pass dict yang bukan user)
- Tidak ada validasi
- Sulit di-extend
Dengan OOP
class User:
def __init__(self, nama, umur, saldo=0):
self.nama = nama
self.umur = umur
self.saldo = saldo
def tambah_saldo(self, jumlah):
if jumlah < 0:
raise ValueError("Jumlah harus positif")
self.saldo += jumlah
def info(self):
print(f"{self.nama}: Rp{self.saldo:,}")
# Pakai
user1 = User("Budi", 25, 1000000)
user1.tambah_saldo(50000)
user1.info()
Keuntungan:
- Data + behavior dalam satu unit (encapsulation)
- Tidak bisa salah pass
- Validasi terpusat
- Mudah extend (inheritance)
Bagian 2 โ Class & Object
Diagram: Class = Blueprint, Object = Instance
Cara Membaca Diagram
Top: class Mobil sebagai blueprint. Layer 2: komponen class (__init__, attributes, methods). Layer 3: 3 instance konkret (mobil1, mobil2, mobil3) โ masing-masing punya state berbeda tapi struktur sama. Bottom-right: panggil method .info() yang kerja di tiap instance.
Walkthrough Step-by-Step
- Definisi class โ
class Mobil:dengan__init__, attributes (self.merk,self.warna), dan methods (def info(self):). - Instantiation โ
mobil1 = Mobil("Toyota", "merah")panggil__init__dan bikin instance baru. - Tiap instance punya state independen: mobil1.merk = "Toyota", mobil2.merk = "Honda", mobil3.merk = "Suzuki".
- Method call โ
mobil1.info()dipanggil. Python translate keMobil.info(mobil1).self= mobil1. - Banyak instance pakai logic yang sama (method body), tapi data beda (attributes per instance).
Analogi Sehari-hari
Class = resep kue, instance = kue jadi. Resep punya bahan (attributes) dan cara membuat (methods). Tiap kue jadi punya rasa beda (data berbeda) โ kue coklat, kue keju, kue strawberry. Tapi semuanya pakai resep dasar yang sama. Kalau kamu update resep (ubah class), semua kue baru ikut ter-update โ tapi kue yang sudah jadi tetap sama.
Diagram statis Mermaid sebagai fallback:
classDiagram
class Mobil {
+str merk
+str warna
+info()
}
Mobil <|.. mobil1 : instance
Mobil <|.. mobil2 : instance
note for mobil1 "merk = 'Toyota'<br/>warna = 'merah'"
note for mobil2 "merk = 'Honda'<br/>warna = 'biru'"
Analogi: class itu cetakan kue, object itu kue jadi. Cetakan menentukan bentuk (struktur), tapi tiap kue bisa beda rasa/topping (attribute).
Class = template/blueprint Object (instance) = wujud nyata dari class
class Mobil: # class definition
def __init__(self, merk, warna):
self.merk = merk # attribute
self.warna = warna
def info(self): # method
print(f"{self.merk} warna {self.warna}")
# Instantiation (bikin object)
mobil1 = Mobil("Toyota", "merah")
mobil2 = Mobil("Honda", "biru")
mobil1.info() # Toyota warna merah
mobil2.info() # Honda warna biru
Anatomi
class Mobil:โ definisi class__init__โ konstruktor (dipanggil saat bikin instance)selfโ referensi ke instance saat iniself.merkโ attribute (data)def info(self):โ method (function dalam class)
self Itu Apa?
self = parameter pertama otomatis untuk semua method, merujuk ke instance.
mobil1.info()
# Sebenarnya Python panggil:
# Mobil.info(mobil1)
# self = mobil1
Aturan: parameter pertama method selalu
self(konvensi).
Bagian 3 โ Attributes
Instance Attribute
Beda antar instance:
class Mobil:
def __init__(self, merk):
self.merk = merk # instance attribute
m1 = Mobil("Toyota")
m2 = Mobil("Honda")
print(m1.merk, m2.merk) # Toyota Honda
Class Attribute
Sama untuk semua instance:
class Mobil:
roda = 4 # class attribute (shared)
def __init__(self, merk):
self.merk = merk
m1 = Mobil("Toyota")
m2 = Mobil("Honda")
print(m1.roda, m2.roda) # 4 4
print(Mobil.roda) # 4 (akses lewat class)
# Hati-hati modifikasi
Mobil.roda = 6 # ubah class attribute
print(m1.roda) # 6 (semua instance ter-update)
Pitfall: kalau class attribute mutable (list, dict), bisa jadi shared state bug. Hindari.
Cek Attribute
hasattr(m1, "merk") # True
getattr(m1, "merk") # "Toyota"
getattr(m1, "xyz", None) # None (default)
setattr(m1, "merk", "Suzuki")
delattr(m1, "merk")
Bagian 4 โ Methods
Instance Method (Standar)
class Calculator:
def __init__(self, value=0):
self.value = value
def add(self, n):
self.value += n
return self # return self โ method chaining
def multiply(self, n):
self.value *= n
return self
c = Calculator(10)
c.add(5).multiply(2) # method chaining
print(c.value) # 30
Class Method
Tidak butuh instance, akses ke class.
class User:
user_count = 0
def __init__(self, nama):
self.nama = nama
User.user_count += 1
@classmethod
def total_users(cls):
return cls.user_count
User("a")
User("b")
print(User.total_users()) # 2
Static Method
Tidak butuh instance maupun class. Cuma function di dalam class untuk grouping logical.
class MathUtils:
@staticmethod
def is_even(n):
return n % 2 == 0
print(MathUtils.is_even(10)) # True
Property (Getter/Setter Pythonic)
class Lingkaran:
def __init__(self, radius):
self._radius = radius # convention: _ untuk "private"
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius tidak boleh negatif")
self._radius = value
@property
def luas(self):
return 3.14159 * self._radius ** 2
c = Lingkaran(5)
print(c.radius) # 5
print(c.luas) # 78.539... (akses kayak attribute, tapi computed)
c.radius = 10 # pakai setter
c.radius = -1 # โ ValueError
Penting:
@propertymembuat method bertingkah seperti attribute. Pythonic untuk computed values.
Bagian 5 โ Inheritance
Diagram: Class Inheritance
Cara Membaca Diagram
Top: base class Hewan dengan attribute & method generic. Layer tengah: 4 subclass (Anjing, Kucing, Ikan, Burung) yang inherit dari Hewan. Bottom: hasil method bersuara() yang berbeda-beda (override) โ bukti polymorphism. Edge dashed = relasi inheritance.
Walkthrough Step-by-Step
- Hewan = base class dengan
nama,umur, danbersuara()(default). - Anjing(Hewan) inherit semua dari Hewan, tambah method
lari(), overridebersuara()jadi "Guk!". - Kucing(Hewan) override
bersuara()jadi "Meong!", tambahmanjat(). - Ikan(Hewan) override
bersuara()jadi silent, tambahberenang(). - Burung(Hewan) override
bersuara()jadi "Cuit!", tambahterbang(). - Polymorphism โ panggil
.bersuara()di list of Hewan, tiap object respon dengan suara sendiri tanpa cek tipe manual.
Analogi Sehari-hari
Hewan = template gen DNA dasar. Anjing, Kucing, Ikan, Burung = spesies turunan yang dapat semua sifat dasar (makan, bernafas, ada nama-umur), tapi tambah/ubah karakteristik sendiri. Anjing override "suara" jadi gonggongan, Kucing jadi meong. Polymorphism = kamu bisa bilang "semua hewan suara", tiap hewan jawab sesuai jenisnya โ tidak perlu instruksi spesifik per spesies.
Diagram statis Mermaid sebagai fallback:
classDiagram
class Hewan {
+str nama
+int umur
+bersuara()
}
class Anjing {
+str ras
+bersuara() override
}
class Kucing {
+bersuara() override
}
class Ikan {
+berenang()
}
Hewan <|-- Anjing
Hewan <|-- Kucing
Hewan <|-- Ikan
Analogi: inheritance = mewarisi gen dari orang tua. Anjing dapat semua fitur Hewan (nama, umur, bersuara), tapi bisa tambahkan fitur sendiri (ras) atau ubah behavior (bersuara jadi guk-guk).
Class bisa "mewarisi" dari class lain.
Bentuk Dasar
class Hewan:
def __init__(self, nama):
self.nama = nama
def bersuara(self):
print("Suara generic")
class Anjing(Hewan): # inherit dari Hewan
def bersuara(self): # override
print(f"{self.nama}: Guk guk!")
class Kucing(Hewan):
def bersuara(self):
print(f"{self.nama}: Meong!")
a = Anjing("Rex")
a.bersuara() # Rex: Guk guk!
Memanggil Parent dengan super()
class Hewan:
def __init__(self, nama, umur):
self.nama = nama
self.umur = umur
class Anjing(Hewan):
def __init__(self, nama, umur, ras):
super().__init__(nama, umur) # panggil parent __init__
self.ras = ras
def info(self):
print(f"{self.nama}, {self.umur}, ras {self.ras}")
a = Anjing("Rex", 3, "Husky")
a.info()
Multiple Inheritance (Hati-hati)
class Berenang:
def aksi(self):
print("Berenang")
class Terbang:
def aksi(self):
print("Terbang")
class Bebek(Berenang, Terbang):
pass
b = Bebek()
b.aksi() # Berenang (yang pertama di-list)
Best practice: hindari multiple inheritance kompleks. Lebih baik composition (akan dibahas).
MRO (Method Resolution Order)
Python pakai algoritma C3 linearization:
print(Bebek.mro())
# [Bebek, Berenang, Terbang, object]
object adalah parent dari semua class.
Bagian 6 โ Encapsulation (Public/Private)
Python tidak punya private murni seperti Java. Pakai konvensi:
class BankAccount:
def __init__(self, saldo):
self.saldo = saldo # public
self._pin = "1234" # protected (konvensi)
self.__token = "secret-xyz" # "private" (name mangling)
def get_token(self):
return self.__token
acc = BankAccount(1000)
print(acc.saldo) # OK
print(acc._pin) # bisa, tapi konvensi: jangan
print(acc.__token) # โ AttributeError
print(acc._BankAccount__token) # bisa (name mangled), tapi jangan
Aturan konvensi Python:
namaโ public_namaโ protected (jangan akses dari luar, tapi tidak dipaksa)__namaโ "private" (Python rename ke_ClassName__nama)
Python pakai konsensus, bukan paksaan. "We're all consenting adults here."
Bagian 7 โ Dunder Methods (Magic Methods)
Method dengan __nama__ โ Python panggil otomatis untuk operasi tertentu.
__init__ โ Konstruktor (sudah dibahas)
__str__ & __repr__
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})" # untuk user
def __repr__(self):
return f"Point(x={self.x}, y={self.y})" # untuk developer
p = Point(3, 4)
print(p) # (3, 4) โ __str__
print(repr(p)) # Point(x=3, y=4) โ __repr__
[p, p] # [Point(x=3, y=4), Point(x=3, y=4)] โ list pakai __repr__
Best practice: selalu implement
__repr__minimal. Sangat membantu debugging.
__eq__ โ Operator ==
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
p1 = Point(3, 4)
p2 = Point(3, 4)
print(p1 == p2) # True (tanpa __eq__: False, karena beda instance)
__lt__, __gt__, dll โ Comparison
class Buku:
def __init__(self, judul, halaman):
self.judul = judul
self.halaman = halaman
def __lt__(self, other):
return self.halaman < other.halaman
books = [Buku("A", 100), Buku("B", 50), Buku("C", 200)]
books.sort() # sort berdasarkan halaman
Arithmetic (__add__, __sub__, dll)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(3, 6)
__len__, __getitem__, __iter__
class Deck:
def __init__(self):
self.cards = ["Aโฅ", "Kโฅ", "Qโฅ"]
def __len__(self):
return len(self.cards)
def __getitem__(self, i):
return self.cards[i]
d = Deck()
print(len(d)) # 3
print(d[0]) # Aโฅ
for card in d: # iterable! karena ada __getitem__
print(card)
Daftar Dunder Lengkap
__init__ konstruktor
__del__ destruktor
__str__ str(obj), print(obj)
__repr__ repr(obj)
__eq__ ==
__ne__ !=
__lt__ <
__le__ <=
__gt__ >
__ge__ >=
__add__ +
__sub__ -
__mul__ *
__truediv__ /
__len__ len()
__getitem__ obj[i]
__setitem__ obj[i] = x
__contains__ x in obj
__iter__ for x in obj
__call__ obj()
__enter__ with obj:
__exit__ with obj:
Bagian 8 โ Composition vs Inheritance
Diagram: Composition (has-a) vs Inheritance (is-a)
classDiagram
class Engine {
+start()
}
class Car_C {
-Engine engine
+drive()
}
Car_C *-- Engine : composition (has-a)
class Vehicle {
+move()
}
class Car_I {
+honk()
}
Vehicle <|-- Car_I : inheritance (is-a)
Analogi:
- Inheritance = "Kucing adalah Hewan" (sifat keturunan)
- Composition = "Mobil punya Mesin" (komponen)
Kalau ragu, tanya: bisa diganti komponen-nya tanpa ganti identitas? Kalau iya โ composition.
Composition
Class punya instance dari class lain (relasi "has-a"):
class Engine:
def start(self):
print("Engine starting...")
class Car:
def __init__(self):
self.engine = Engine() # composition
def drive(self):
self.engine.start()
print("Driving!")
Inheritance
Class is-a class lain (relasi "is-a"):
class Vehicle:
pass
class Car(Vehicle): # inheritance: Car is a Vehicle
pass
Aturan
- Composition = "has-a" (Car has Engine)
- Inheritance = "is-a" (Dog is Animal)
Best practice modern: Favor composition over inheritance. Inheritance fleksibel di awal tapi sering jadi rigid. Composition lebih maintainable.
Bagian 9 โ Dataclass (Pythonic Modern)
Untuk class yang hanya wadah data, pakai @dataclass:
from dataclasses import dataclass
@dataclass
class User:
nama: str
umur: int
email: str = "no-email" # default
# Otomatis dapat:
# - __init__
# - __repr__
# - __eq__
u = User("Budi", 25, "budi@x.com")
print(u) # User(nama='Budi', umur=25, email='budi@x.com')
Bandingkan dengan class biasa yang ribet boilerplate. Dataclass = produktivitas.
@dataclass(frozen=True) # immutable
class Point:
x: float
y: float
frozen=True bikin instance tidak bisa diubah setelah dibuat โ mirip tuple tapi dengan field bernama.
Bagian 10 โ OOP di Konteks AI
Untuk konteks bootcamp Dicoding GenAI:
scikit-learn API
from sklearn.linear_model import LinearRegression
model = LinearRegression() # instantiate
model.fit(X, y) # method
prediction = model.predict(X) # method
print(model.coef_) # attribute
Semua model sklearn ikut interface yang sama: .fit(), .predict(), .score(). Itu pola OOP.
PyTorch
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super().__init__()
self.layer1 = nn.Linear(10, 5)
self.layer2 = nn.Linear(5, 1)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
return x
model = MyModel()
PyTorch model selalu subclass nn.Module. Kalau tidak paham OOP, Fase 6 akan struggle.
HuggingFace
from transformers import AutoModel, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
model = AutoModel.from_pretrained("bert-base-uncased")
from_pretrained adalah class method (bukan instance method). Pattern OOP.
Common Mistakes & FAQ
โ Mistake 1: Lupa self
class Counter:
def __init__(self):
count = 0 # โ ini local variable!
def increment(self):
count += 1 # โ NameError
# Fix: pakai self
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
โ Mistake 2: Mutable class attribute
class Student:
grades = [] # โ shared antar instance!
def add_grade(self, g):
self.grades.append(g)
s1 = Student()
s2 = Student()
s1.add_grade(90)
print(s2.grades) # [90] โ bug! semua instance share
# Fix: pakai instance attribute
class Student:
def __init__(self):
self.grades = []
โ Mistake 3: Lupa super().__init__()
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
# โ lupa super().__init__(name)
self.breed = breed
d = Dog("Rex", "Husky")
print(d.name) # AttributeError!
โ Mistake 4: Bingung @classmethod vs @staticmethod
class Math:
@staticmethod
def add(a, b):
return a + b # tidak akses class/instance
@classmethod
def from_string(cls, s):
return cls(...) # akses class (untuk factory)
Aturan:
- instance method = butuh self (akses instance state)
- classmethod = butuh class (factory pattern, alternative constructor)
- staticmethod = utility murni dalam scope class
โ Mistake 5: Akses dunder dari luar
class A:
def __secret(self): # double underscore = name mangling
pass
a = A()
a.__secret() # โ AttributeError
Gunakan single underscore _method untuk "internal" yang masih bisa diakses.
FAQ
Q: Apa beda __init__ dan __new__?
A: __new__ bikin instance, __init__ initialize-nya. Hampir tidak pernah override __new__ di kode normal โ kecuali bikin singleton atau metaclass.
Q: Apakah Python punya interface seperti Java?
A: Tidak persis. Pakai abc.ABC (abstract base class) dengan @abstractmethod untuk paksa subclass implement method tertentu. Atau pakai duck typing biasa.
Q: Kapan pakai @dataclass?
A: Kalau class kamu sebagian besar wadah data (attribute + minimal logic). Untuk class dengan banyak behavior, pakai class biasa.
Q: Multiple inheritance aman dipakai? A: Bisa, tapi rumit. Pakai mixin pattern (class kecil dengan method spesifik) lebih maintainable. Hindari diamond inheritance.
Q: Property vs getter/setter biasa?
A: Property Pythonic. Akses obj.value (kayak attribute), tapi belakang layar bisa ada validasi/komputasi. Java-style getter/setter (getValue()) tidak idiomatic di Python.
Q: Kenapa PyTorch model selalu nn.Module?
A: nn.Module handle weights, gradients, GPU transfer otomatis. Dengan inherit, kamu dapat semua infrastruktur "gratis", tinggal define forward().
Cek Pemahaman
- Bisa bikin class dengan
__init__dan method? - Tahu beda instance vs class attribute?
- Bisa pakai
@property? - Paham inheritance dan
super()? - Bisa implement
__str__,__repr__,__eq__? - Tahu kapan pakai composition vs inheritance?
- Bisa pakai
@dataclass?
Challenge 2.6
Challenge 1 โ Class Buku
Bikin class Buku dengan:
- Attribute: judul, penulis, halaman, tahun
- Method
info()print info buku - Method
__str__dan__repr__ - Method
__eq__(dua buku sama jika judul + penulis sama)
Challenge 2 โ BankAccount
Bikin class BankAccount:
- Attribute: nomor_rekening, pemilik, saldo (private dengan property)
- Method
setor(jumlah),tarik(jumlah),transfer(target_account, jumlah) - Validasi: tidak bisa tarik melebihi saldo, jumlah harus positif
- History transaksi (list)
Challenge 3 โ Inheritance: Hewan
Bikin hierarchy:
Hewan(base) โ nama, umur,bersuara()(abstract)Anjing(Hewan)โ bersuara "Guk!", methodlari()Kucing(Hewan)โ bersuara "Meong!", methodmanjat()Ikan(Hewan)โ bersuara "..." (silent), methodberenang()
Test dengan list of hewan, panggil method yang sesuai polymorphic.
Challenge 4 โ Property & Validation
Bikin class Suhu:
- Internal store celsius
- Property
celsius,fahrenheit,kelvin(computed) - Set lewat property mana saja (dia auto-konversi)
- Validasi: tidak bisa di bawah -273.15ยฐC
s = Suhu(celsius=25)
print(s.fahrenheit) # 77.0
s.kelvin = 300
print(s.celsius) # ~26.85
Challenge 5 โ Composition: Library System
Bikin sistem perpustakaan:
- Class
Buku(judul, penulis, ISBN) - Class
Anggota(nama, ID, list buku_dipinjam) - Class
Perpustakaan(list buku, list anggota) - Method: pinjam, kembali, cari_buku, daftar_anggota
Pakai composition (Perpustakaan has buku + anggota).
Challenge 6 โ Dunder Methods: Vector
Bikin class Vector (3D):
__init__(x, y, z)__add__,__sub__,__mul__(scalar),__eq____abs__(magnitude/length)__str__dan__repr__- Method
dot(other)(dot product),cross(other)(cross product)
Challenge 7 โ Dataclass
Refactor class User dari Challenge 2 jadi dataclass. Tambahkan:
- Field
emaildengan default - Validasi di
__post_init__
Challenge 8 โ Mini ML Model API
Bikin class SimpleClassifier:
__init__(threshold=0.5)fit(X, y)โ train (untuk simpler, hitung mean of class 1)predict(X)โ predictscore(X, y)โ accuracy- Internal state:
_is_fitted, raise error kalau predict sebelum fit
Test dengan dummy data. Ini latihan pola sklearn API.
Selanjutnya: 07-file-io-modules.md โ file I/O, JSON, CSV, dan organisasi code (modules + packages).