Python & AI Tutorials Logo
Pemrograman Python

32. Memperluas Class dengan Pewarisan dan Polimorfisme

Di Bab 30, kita belajar cara membuat class kita sendiri untuk memodelkan konsep dunia nyata. Kita membangun class seperti BankAccount dan Student yang menggabungkan data dan perilaku menjadi satu. Tapi apa yang terjadi kalau kamu perlu membuat class baru yang mirip dengan class yang sudah ada, tetapi dengan beberapa perbedaan atau tambahan?

Pewarisan (inheritance) adalah mekanisme Python untuk membuat class baru berdasarkan class yang sudah ada. Alih-alih menyalin dan menempelkan kode, kamu bisa membuat subclass yang secara otomatis mendapatkan semua atribut dan method dari class induk (juga disebut base class atau superclass), lalu menambahkan atau memodifikasi apa yang kamu butuhkan.

Bab ini membahas bagaimana pewarisan memungkinkan kamu membangun hierarki class yang saling berhubungan, bagaimana menyesuaikan perilaku yang diwariskan, dan bagaimana polimorfisme (polymorphism) memungkinkan class yang berbeda dipakai secara saling menggantikan ketika mereka berbagi interface yang sama.

32.1) Membuat Subclass dari Class yang Sudah Ada

32.1.1) Sintaks Dasar Pewarisan

Pewarisan memungkinkan kamu membuat class baru (disebut subclass atau child class) berdasarkan class yang sudah ada (disebut class induk atau base class). Subclass secara otomatis mewarisi semua method dan atribut dari class induk.

Saat kamu membuat subclass, kamu menyebutkan class induk di dalam tanda kurung setelah nama class.

python
# Class induk
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} makes a sound"
 
# Subclass yang mewarisi dari Animal
class Dog(Animal):
    pass  # Belum perlu kode tambahan
 
# Membuat instance Dog
buddy = Dog("Buddy")
print(buddy.speak())  # Output: Buddy makes a sound
print(buddy.name)     # Output: Buddy

Walaupun Dog tidak punya kode sendiri (hanya pass), ia mewarisi semuanya dari Animal. Class Dog secara otomatis memiliki method __init__ dan method speak dari class induknya.

Animal

+name

+init(name)

+speak()

Dog

32.1.2) Kenapa Pewarisan Itu Penting

Pewarisan menyelesaikan masalah pemrograman yang umum: duplikasi kode. Bayangkan kamu sedang membangun sistem untuk mengelola berbagai tipe karyawan:

python
# Tanpa pewarisan - banyak duplikasi
class FullTimeEmployee:
    def __init__(self, name, employee_id, salary):
        self.name = name
        self.employee_id = employee_id
        self.salary = salary
    
    def get_info(self):
        return f"{self.name} (ID: {self.employee_id})"
 
class PartTimeEmployee:
    def __init__(self, name, employee_id, hourly_rate):
        self.name = name
        self.employee_id = employee_id
        self.hourly_rate = hourly_rate
    
    def get_info(self):
        return f"{self.name} (ID: {self.employee_id})"

Perhatikan bagaimana name, employee_id, dan get_info() terduplikasi. Dengan pewarisan, kita bisa menghilangkan duplikasi ini:

python
# Dengan pewarisan - kode bersama di class induk
class Employee:
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id
    
    def get_info(self):
        return f"{self.name} (ID: {self.employee_id})"
 
class FullTimeEmployee(Employee):
    def __init__(self, name, employee_id, salary):
        Employee.__init__(self, name, employee_id)  # Memanggil __init__ milik induk
        # Catatan: Kita akan mempelajari cara yang lebih baik untuk ini dengan super() di Bagian 32.3
        self.salary = salary
 
class PartTimeEmployee(Employee):
    def __init__(self, name, employee_id, hourly_rate):
        Employee.__init__(self, name, employee_id)  # Memanggil __init__ milik induk
        self.hourly_rate = hourly_rate
 
# Kedua subclass mewarisi get_info()
alice = FullTimeEmployee("Alice", "E001", 75000)
bob = PartTimeEmployee("Bob", "E002", 25)
 
print(alice.get_info())  # Output: Alice (ID: E001)
print(bob.get_info())    # Output: Bob (ID: E002)

Sekarang atribut dan method yang umum berada di Employee, dan setiap subclass hanya mendefinisikan apa yang membuatnya unik.

32.1.3) Menambahkan Method Baru ke Subclass

Subclass bisa menambahkan method mereka sendiri yang tidak dimiliki induknya:

python
class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
    
    def get_description(self):
        return f"{self.brand} {self.model}"
 
class Car(Vehicle):
    def __init__(self, brand, model, num_doors):
        Vehicle.__init__(self, brand, model)
        self.num_doors = num_doors
    
    def honk(self):  # Method baru khusus untuk Car
        return "Beep beep!"
 
class Motorcycle(Vehicle):
    def __init__(self, brand, model, has_sidecar):
        Vehicle.__init__(self, brand, model)
        self.has_sidecar = has_sidecar
    
    def rev_engine(self):  # Method baru khusus untuk Motorcycle
        return "Vroom vroom!"
 
# Setiap subclass punya method dari induknya ditambah miliknya sendiri
my_car = Car("Toyota", "Camry", 4)
print(my_car.get_description())  # Output: Toyota Camry
print(my_car.honk())             # Output: Beep beep!
 
my_bike = Motorcycle("Harley", "Sportster", False)
print(my_bike.get_description())  # Output: Harley Sportster
print(my_bike.rev_engine())       # Output: Vroom vroom!

Class Car memiliki get_description() (diwariskan) dan honk() (miliknya sendiri). Class Motorcycle memiliki get_description() (diwariskan) dan rev_engine() (miliknya sendiri).

32.1.4) Menambahkan Atribut Baru ke Subclass

Subclass juga bisa menambahkan atribut instance mereka sendiri. Biasanya kamu melakukan ini di method __init__ milik subclass:

python
class BankAccount:
    def __init__(self, account_number, balance):
        self.account_number = account_number
        self.balance = balance
    
    def deposit(self, amount):
        self.balance += amount
        return self.balance
 
class SavingsAccount(BankAccount):
    def __init__(self, account_number, balance, interest_rate):
        BankAccount.__init__(self, account_number, balance)
        self.interest_rate = interest_rate  # Atribut baru
    
    def apply_interest(self):  # Method baru yang memakai atribut baru
        interest = self.balance * self.interest_rate
        self.balance += interest
        return interest
 
# SavingsAccount punya semua atribut BankAccount plus interest_rate
savings = SavingsAccount("SA001", 1000, 0.03)
savings.deposit(500)  # Method yang diwariskan
print(f"Balance: ${savings.balance}")  # Output: Balance: $1500
 
interest_earned = savings.apply_interest()
print(f"Interest earned: ${interest_earned:.2f}")  # Output: Interest earned: $45.00
print(f"New balance: ${savings.balance:.2f}")      # Output: New balance: $1545.00

SavingsAccount memiliki account_number dan balance dari BankAccount, ditambah atribut interest_rate miliknya sendiri.

32.1.5) Beberapa Tingkat Pewarisan

Class bisa mewarisi dari class yang juga mewarisi dari class lain, sehingga membentuk hierarki pewarisan:

python
class LivingThing:
    def __init__(self, name):
        self.name = name
    
    def is_alive(self):
        return True
 
class Animal(LivingThing):
    def __init__(self, name, species):
        LivingThing.__init__(self, name)
        self.species = species
    
    def move(self):
        return f"{self.name} is moving"
 
class Dog(Animal):
    def __init__(self, name, breed):
        Animal.__init__(self, name, "Dog")
        self.breed = breed
    
    def bark(self):
        return f"{self.name} says: Woof!"
 
# Dog mewarisi dari Animal, yang mewarisi dari LivingThing
max_dog = Dog("Max", "Golden Retriever")
 
# Method dari ketiga level semuanya berfungsi
print(max_dog.is_alive())  # Output: True (dari LivingThing)
print(max_dog.move())      # Output: Max is moving (dari Animal)
print(max_dog.bark())      # Output: Max says: Woof! (dari Dog)
 
# Atribut dari ketiga level semuanya ada
print(max_dog.name)     # Output: Max (dari LivingThing)
print(max_dog.species)  # Output: Dog (dari Animal)
print(max_dog.breed)    # Output: Golden Retriever (dari Dog)

LivingThing

+name

+is_alive()

Animal

+species

+move()

Dog

+breed

+bark()

Dog mewarisi dari Animal, yang mewarisi dari LivingThing. Ini berarti Dog punya akses ke method dan atribut dari kedua class induk tersebut.

32.2) Meng-override Method di Subclass

32.2.1) Apa Arti Method Overriding

Terkadang sebuah subclass perlu mengubah cara kerja method yang diwariskan. Method overriding berarti mendefinisikan sebuah method di subclass dengan nama yang sama seperti method di class induk. Saat kamu memanggil method itu pada instance subclass, Python memakai versi milik subclass, bukan milik induk.

python
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} makes a sound"
 
class Dog(Animal):
    def speak(self):  # Meng-override method speak milik induk
        return f"{self.name} says: Woof!"
 
class Cat(Animal):
    def speak(self):  # Override dengan perilaku yang berbeda
        return f"{self.name} says: Meow!"
 
# Setiap class punya versinya sendiri untuk speak()
generic_animal = Animal("Generic")
print(generic_animal.speak())  # Output: Generic makes a sound
 
buddy = Dog("Buddy")
print(buddy.speak())  # Output: Buddy says: Woof!
 
whiskers = Cat("Whiskers")
print(whiskers.speak())  # Output: Whiskers says: Meow!

Saat kamu memanggil buddy.speak(), Python mencari method speak di class Dog terlebih dulu, karena buddy adalah instance Dog. Karena Dog mendefinisikan method speak sendiri, Python memakai versi itu. Kalau Dog tidak punya method speak, Python kemudian akan mencari di class induk Animal dan memakai versi dari sana.

Urutan pencarian ini—dimulai dari class milik instance, lalu naik ke class induk—adalah cara kerja method overriding dan bagaimana subclass menyesuaikan perilaku yang diwariskan.

32.2.2) Kenapa Meng-override Method?

Method overriding memungkinkan kamu membuat versi spesifik dari perilaku yang umum. Pertimbangkan hierarki shape:

python
class Shape:
    def __init__(self, name):
        self.name = name
    
    def area(self):
        return 0  # Implementasi default
    
    def describe(self):
        return f"{self.name} with area {self.area()}"
 
class Rectangle(Shape):
    def __init__(self, width, height):
        Shape.__init__(self, "Rectangle")
        self.width = width
        self.height = height
    
    def area(self):  # Override dengan perhitungan khusus rectangle
        return self.width * self.height
 
class Circle(Shape):
    def __init__(self, radius):
        Shape.__init__(self, "Circle")
        self.radius = radius
    
    def area(self):  # Override dengan perhitungan khusus circle
        return 3.14159 * self.radius ** 2
 
# Setiap shape menghitung area secara berbeda
rect = Rectangle(5, 3)
print(rect.describe())  # Output: Rectangle with area 15
 
circle = Circle(4)
print(circle.describe())  # Output: Circle with area 50.26544

Method describe() diwariskan oleh kedua subclass dan bekerja dengan benar karena setiap subclass menyediakan implementasi area() miliknya sendiri.

Saat kamu memanggil rect.describe(), method describe() yang diwariskan dijalankan, tetapi self merujuk ke instance Rectangle. Jadi ketika describe() memanggil self.area(), Python mencari area() di class Rectangle terlebih dulu dan menemukan versi yang dioverride.

32.2.3) Meng-override __init__ dan Memanggil Inisialisasi Induk

Saat kamu meng-override __init__, biasanya kamu perlu memanggil __init__ milik induk untuk memastikan inisialisasi induk tetap terjadi:

python
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def introduce(self):
        return f"I'm {self.name}, {self.age} years old"
 
class Student(Person):
    def __init__(self, name, age, student_id, major):
        # Panggil __init__ milik induk untuk mengatur name dan age
        Person.__init__(self, name, age)
        # Lalu set atribut khusus student
        self.student_id = student_id
        self.major = major
    
alice = Student("Alice", 20, "S12345", "Computer Science")
print(alice.name)        # Output: Alice
print(alice.student_id)  # Output: S12345

Perhatikan bagaimana Student.__init__ pertama-tama memanggil Person.__init__(self, name, age) untuk menginisialisasi atribut milik class induk, lalu menambahkan atributnya sendiri.

32.3) Menggunakan super() untuk Mengakses Perilaku Induk

32.3.1) Apa yang Dilakukan super()

Di bagian sebelumnya, kita memanggil method induk secara eksplisit: ParentClass.method(self, ...). Python menyediakan cara yang lebih rapi: fungsi super(). super() mengembalikan objek sementara yang memungkinkan kamu memanggil method dari class induk tanpa menyebut nama induk secara eksplisit.

python
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} makes a sound"
 
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # Lebih rapi daripada Animal.__init__(self, name)
        self.breed = breed
    
    def speak(self):
        parent_sound = super().speak()  # Memanggil speak() milik induk
        return f"{parent_sound} - specifically, Woof!"
 
buddy = Dog("Buddy", "Labrador")
print(buddy.speak())
# Output: Buddy makes a sound - specifically, Woof!

Menggunakan super() punya beberapa keuntungan:

  • Kamu tidak perlu menyebut nama class induk secara eksplisit
  • Ia bekerja dengan benar pada multiple inheritance (dibahas nanti)
  • Ini membuat kode lebih mudah dirawat jika kamu mengubah class induk

32.3.2) Menggunakan super() di __init__

Penggunaan super() yang paling umum adalah memanggil __init__ milik induk:

python
class Employee:
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id
        self.is_active = True
    
    def deactivate(self):
        self.is_active = False
 
class Manager(Employee):
    def __init__(self, name, employee_id, department):
        super().__init__(name, employee_id)  # Inisialisasi atribut induk
        self.department = department
        self.team = []  # Atribut khusus Manager
    
    def add_team_member(self, employee):
        self.team.append(employee)
 
# Manager mendapatkan semua atribut Employee plus miliknya sendiri
sarah = Manager("Sarah", "M001", "Engineering")
print(sarah.name)        # Output: Sarah
print(sarah.is_active)   # Output: True
print(sarah.department)  # Output: Engineering

Dengan memanggil super().__init__(name, employee_id), class Manager memastikan bahwa semua logika inisialisasi dari Employee berjalan, termasuk mengatur is_active menjadi True.

32.3.3) Memperluas Method Induk dengan super()

Kamu bisa memakai super() untuk memperluas method induk alih-alih menggantinya sepenuhnya:

python
class BankAccount:
    def __init__(self, account_number, balance):
        self.account_number = account_number
        self.balance = balance
        self.transaction_count = 0
    
    def deposit(self, amount):
        self.balance += amount
        self.transaction_count += 1
        return self.balance
 
class CheckingAccount(BankAccount):
    def __init__(self, account_number, balance, overdraft_limit):
        super().__init__(account_number, balance)
        self.overdraft_limit = overdraft_limit
    
    def deposit(self, amount):
        was_overdrawn = self.balance < 0
        
        # Panggil deposit milik induk untuk menangani logika dasar
        new_balance = super().deposit(amount)
        
        # Tambahkan perilaku khusus checking
        if was_overdrawn and new_balance >= 0:
            print("Account is no longer overdrawn")
        return new_balance
 
checking = CheckingAccount("C001", -50, 100)
checking.deposit(75)
# Output: Account is no longer overdrawn
print(f"Balance: ${checking.balance}")  # Output: Balance: $25
print(f"Transactions: {checking.transaction_count}")  # Output: Transactions: 1

Method CheckingAccount.deposit() memanggil super().deposit(amount) untuk menangani logika deposit dasar (mengupdate balance dan jumlah transaksi), lalu menambahkan pengecekan sendiri untuk status overdraft.

32.3.4) Kapan Menggunakan super() vs Pemanggilan Induk Secara Langsung

Gunakan super() dalam kebanyakan kasus:

python
class Vehicle:
    def __init__(self, brand):
        self.brand = brand
 
class Car(Vehicle):
    def __init__(self, brand, model):
        super().__init__(brand)  # Direkomendasikan
        self.model = model

Gunakan pemanggilan induk secara langsung saat kamu perlu memanggil induk tertentu dalam skenario multiple inheritance (dibahas nanti) atau saat kamu ingin eksplisit tentang induk mana yang kamu panggil:

python
class Car(Vehicle):
    def __init__(self, brand, model):
        Vehicle.__init__(self, brand)  # Eksplisit, tapi kurang fleksibel
        self.model = model

Untuk single inheritance (satu induk), super() hampir selalu pilihan yang lebih baik.

32.3.5) super() dengan Method Lain

Kamu bisa menggunakan super() dengan method apa pun, bukan hanya __init__:

python
class TextProcessor:
    def process(self, text):
        # Pemrosesan dasar: hapus spasi di awal/akhir
        return text.strip()
 
class UppercaseProcessor(TextProcessor):
    def process(self, text):
        # Pertama lakukan pemrosesan milik induk
        processed = super().process(text)
        # Lalu tambahkan konversi ke huruf besar
        return processed.upper()
 
class PrefixProcessor(UppercaseProcessor):
    def __init__(self, prefix):
        self.prefix = prefix
    
    def process(self, text):
        # Pertama lakukan pemrosesan milik induk (yang juga memanggil induknya)
        processed = super().process(text)
        # Lalu tambahkan prefix
        return f"{self.prefix}: {processed}"
 
processor = PrefixProcessor("ALERT")
result = processor.process("  system error  ")
print(result)  # Output: ALERT: SYSTEM ERROR

32.4) Polimorfisme: Bekerja dengan Class yang Kompatibel

32.4.1) Apa Arti Polimorfisme

Polimorfisme (dari bahasa Yunani: "many forms") adalah kemampuan untuk memperlakukan objek dari class yang berbeda dengan cara yang sama jika mereka menyediakan method yang sama.

Di Python, jika beberapa class memiliki method dengan nama yang sama, kamu bisa memanggil method itu tanpa mengetahui class persis dari objek tersebut.

python
class Dog:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} says: Woof!"
 
class Cat:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} says: Meow!"
 
class Bird:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} says: Tweet!"
 
# Fungsi yang bekerja dengan objek apa pun yang punya method speak()
def make_animal_speak(animal):
    print(animal.speak())
 
# Bekerja dengan class yang berbeda
buddy = Dog("Buddy")
whiskers = Cat("Whiskers")
tweety = Bird("Tweety")
 
make_animal_speak(buddy)     # Output: Buddy says: Woof!
make_animal_speak(whiskers)  # Output: Whiskers says: Meow!
make_animal_speak(tweety)    # Output: Tweety says: Tweet!

Fungsi make_animal_speak() tidak peduli class apa yang menjadi parameter animal—ia hanya butuh objek tersebut memiliki method speak(). Ini adalah polimorfisme dalam praktik.

32.4.2) Polimorfisme dengan Pewarisan

Polimorfisme sangat kuat saat dikombinasikan dengan pewarisan, ketika subclass meng-override method milik induk:

python
class PaymentMethod:
    def process_payment(self, amount):
        return f"Processing ${amount:.2f}"
 
class CreditCard(PaymentMethod):
    def __init__(self, card_number):
        self.card_number = card_number
    
    def process_payment(self, amount):
        return f"Charging ${amount:.2f} to credit card ending in {self.card_number[-4:]}"
 
class PayPal(PaymentMethod):
    def __init__(self, email):
        self.email = email
    
    def process_payment(self, amount):
        return f"Sending ${amount:.2f} via PayPal to {self.email}"
 
class BankTransfer(PaymentMethod):
    def __init__(self, account_number):
        self.account_number = account_number
    
    def process_payment(self, amount):
        return f"Transferring ${amount:.2f} to account {self.account_number}"
 
# Fungsi yang bekerja dengan PaymentMethod apa pun
def complete_purchase(payment_method, amount):
    print(payment_method.process_payment(amount))
    print("Purchase complete!")
 
# Semua metode pembayaran bekerja dengan fungsi yang sama
credit = CreditCard("1234567890123456")
paypal = PayPal("user@example.com")
bank = BankTransfer("9876543210")
 
complete_purchase(credit, 99.99)
# Output: Charging $99.99 to credit card ending in 3456
# Output: Purchase complete!
 
complete_purchase(paypal, 49.50)
# Output: Sending $49.50 via PayPal to user@example.com
# Output: Purchase complete!
 
complete_purchase(bank, 199.00)
# Output: Transferring $199.00 to account 9876543210
# Output: Purchase complete!

Fungsi complete_purchase() bekerja dengan subclass PaymentMethod mana pun. Setiap subclass menyediakan implementasinya sendiri untuk process_payment(), tetapi fungsi tersebut tidak perlu tahu class spesifik mana yang sedang dipakai.

32.4.3) Duck Typing: "Kalau Jalannya Seperti Bebek..."

Polimorfisme di Python tidak mengharuskan class saling berhubungan lewat pewarisan. Ini disebut duck typing: "If it walks like a duck and quacks like a duck, then it is a duck." Dengan kata lain, Python peduli pada apa yang bisa dilakukan sebuah objek (method-nya), bukan apa dirinya (class-nya). Jika sebuah objek memiliki method yang kamu butuhkan, kamu bisa memakainya, terlepas dari hierarki class-nya.

python
class FileWriter:
    def __init__(self, filename):
        self.filename = filename
    
    def write(self, data):
        print(f"Writing to {self.filename}: {data}")
 
class DatabaseWriter:
    def __init__(self, table_name):
        self.table_name = table_name
    
    def write(self, data):
        print(f"Inserting into {self.table_name}: {data}")
 
class ConsoleWriter:
    def write(self, data):
        print(f"Console output: {data}")
 
# Fungsi yang bekerja dengan objek apa pun yang punya method write()
def save_data(writer, data):
    writer.write(data)
 
# Ketiga class bekerja, walaupun tidak saling berhubungan
file_writer = FileWriter("data.txt")
db_writer = DatabaseWriter("users")
console_writer = ConsoleWriter()
 
save_data(file_writer, "User data")
# Output: Writing to data.txt: User data
 
save_data(db_writer, "User data")
# Output: Inserting into users: User data
 
save_data(console_writer, "User data")
# Output: Console output: User data

Tidak ada satu pun dari class ini yang mewarisi dari induk yang sama, tetapi semuanya bekerja dengan save_data() karena semuanya punya method write(). Ini adalah duck typing—fungsi tidak peduli pada class, hanya pada interface (method yang tersedia).

32.4.4) Contoh Praktis: Sistem Plugin

Polimorfisme memungkinkan sistem yang fleksibel dan mudah diperluas. Berikut sistem plugin sederhana untuk pemrosesan data:

python
class DataProcessor:
    def process(self, data):
        return data  # Implementasi dasar tidak melakukan apa pun
 
class UppercaseProcessor(DataProcessor):
    def process(self, data):
        return data.upper()
 
class ReverseProcessor(DataProcessor):
    def process(self, data):
        return data[::-1]
 
class RemoveSpacesProcessor(DataProcessor):
    def process(self, data):
        return data.replace(" ", "")
 
class DataPipeline:
    def __init__(self):
        self.processors = []
    
    def add_processor(self, processor):
        self.processors.append(processor)
    
    def run(self, data):
        result = data
        for processor in self.processors:
            result = processor.process(result)  # Pemanggilan polimorfik
        return result
 
# Membangun pipeline pemrosesan
pipeline = DataPipeline()
pipeline.add_processor(UppercaseProcessor())
pipeline.add_processor(RemoveSpacesProcessor())
pipeline.add_processor(ReverseProcessor())
 
# Memproses data melalui pipeline
input_data = "Hello World"
output = pipeline.run(input_data)
print(f"Input:  {input_data}")   # Output: Input:  Hello World
print(f"Output: {output}")        # Output: Output: DLROWOLLEH

DataPipeline tidak perlu tahu processor spesifik apa yang dimilikinya—ia hanya memanggil process() pada masing-masing. Kamu bisa dengan mudah menambahkan tipe processor baru tanpa mengubah kode pipeline.

32.5) Mengecek Tipe dan Relasi Class (isinstance, issubclass)

32.5.1) Mengecek Tipe Instance dengan isinstance()

Terkadang kamu perlu mengecek apakah sebuah objek adalah instance dari class tertentu. Fungsi isinstance() melakukan ini:

python
class Animal:
    pass
 
class Dog(Animal):
    pass
 
class Cat(Animal):
    pass
 
buddy = Dog()
whiskers = Cat()
 
# Mengecek apakah sebuah objek merupakan instance dari sebuah class
print(isinstance(buddy, Dog))     # Output: True
print(isinstance(buddy, Animal))  # Output: True (Dog mewarisi dari Animal)
print(isinstance(buddy, Cat))     # Output: False
 
print(isinstance(whiskers, Cat))    # Output: True
print(isinstance(whiskers, Animal)) # Output: True
print(isinstance(whiskers, Dog))    # Output: False

Perhatikan bahwa isinstance(buddy, Animal) menghasilkan True walaupun buddy adalah instance Dog. Ini karena Dog mewarisi dari Animal, jadi instance Dog juga dianggap sebagai instance Animal.

32.5.2) Kenapa isinstance() Menghormati Pewarisan

Fungsi isinstance() mengecek seluruh rantai pewarisan:

python
class Vehicle:
    pass
 
class Car(Vehicle):
    pass
 
class ElectricCar(Car):
    pass
 
tesla = ElectricCar()
 
# Mengecek semua level pewarisan
print(isinstance(tesla, ElectricCar))  # Output: True
print(isinstance(tesla, Car))          # Output: True
print(isinstance(tesla, Vehicle))      # Output: True
print(isinstance(tesla, str))          # Output: False

adalah

adalah

adalah

instance tesla

ElectricCar

Car

Vehicle

Objek tesla adalah instance dari ElectricCar, tetapi juga instance dari Car dan Vehicle karena pewarisan.

32.5.3) Mengecek Beberapa Tipe Sekaligus

Kamu bisa mengecek apakah sebuah objek adalah instance dari salah satu dari beberapa class dengan mengoper sebuah tuple:

python
class Dog:
    pass
 
class Cat:
    pass
 
class Bird:
    pass
 
def is_pet(animal):
    return isinstance(animal, (Dog, Cat, Bird))
 
buddy = Dog()
whiskers = Cat()
tweety = Bird()
rock = "just a rock"
 
print(is_pet(buddy))     # Output: True
print(is_pet(whiskers))  # Output: True
print(is_pet(tweety))    # Output: True
print(is_pet(rock))      # Output: False

Ini lebih ringkas daripada menulis isinstance(animal, Dog) or isinstance(animal, Cat) or isinstance(animal, Bird).

32.5.4) Mengecek Relasi Class dengan issubclass()

Fungsi issubclass() mengecek apakah sebuah class merupakan subclass dari class lainnya:

python
class Animal:
    pass
 
class Dog(Animal):
    pass
 
class Cat(Animal):
    pass
 
class Poodle(Dog):
    pass
 
# Mengecek relasi antar class
print(issubclass(Dog, Animal))    # Output: True
print(issubclass(Cat, Animal))    # Output: True
print(issubclass(Poodle, Dog))    # Output: True
print(issubclass(Poodle, Animal)) # Output: True (pewarisan tidak langsung)
print(issubclass(Dog, Cat))       # Output: False
 
# Sebuah class dianggap subclass dari dirinya sendiri
print(issubclass(Dog, Dog))       # Output: True

Perlu diingat issubclass() bekerja untuk class, bukan instance. Gunakan isinstance() untuk instance dan issubclass() untuk class.

32.5.5) Contoh Penggunaan Praktis untuk Pengecekan Tipe

Pengecekan tipe berguna saat kamu butuh perilaku berbeda untuk tipe yang berbeda:

python
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
 
class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus
 
class Contractor:
    def __init__(self, name, hourly_rate, hours):
        self.name = name
        self.hourly_rate = hourly_rate
        self.hours = hours
 
def calculate_payment(worker):
    # Saat menggunakan isinstance() dengan pewarisan, cek subclass sebelum class induk
    if isinstance(worker, Manager):  # Cek Manager dulu
        return worker.salary + worker.bonus
    elif isinstance(worker, Employee):  # Lalu cek Employee (class induk)
        return worker.salary
    elif isinstance(worker, Contractor):
        return worker.hourly_rate * worker.hours
    else:
        return 0
 
alice = Employee("Alice", 50000)
bob = Manager("Bob", 70000, 10000)
charlie = Contractor("Charlie", 50, 160)
 
print(f"Alice's payment: ${calculate_payment(alice)}")    # Output: Alice's payment: $50000
print(f"Bob's payment: ${calculate_payment(bob)}")        # Output: Bob's payment: $80000
print(f"Charlie's payment: ${calculate_payment(charlie)}")# Output: Charlie's payment: $8000

Namun, dalam banyak kasus, polimorfisme (membuat setiap class mengimplementasikan method yang sama) lebih baik daripada pengecekan tipe:

python
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
    
    def calculate_payment(self):
        return self.salary
 
class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus
    
    def calculate_payment(self):
        return self.salary + self.bonus
 
class Contractor:
    def __init__(self, name, hourly_rate, hours):
        self.name = name
        self.hourly_rate = hourly_rate
        self.hours = hours
    
    def calculate_payment(self):
        return self.hourly_rate * self.hours
 
# Tidak perlu pengecekan tipe - polimorfisme yang menanganinya
workers = [
    Employee("Alice", 50000),
    Manager("Bob", 70000, 10000),
    Contractor("Charlie", 50, 160)
]
 
for worker in workers:
    payment = worker.calculate_payment()  # Pemanggilan polimorfik
    print(f"{worker.name}'s payment: ${payment}")

Output:

Alice's payment: $50000
Bob's payment: $80000
Charlie's payment: $8000

Pendekatan polimorfik ini lebih fleksibel dan lebih mudah diperluas dengan tipe worker baru. Kamu tidak perlu memodifikasi kode pemanggil saat kamu menambahkan class worker baru—cukup pastikan ia punya method calculate_payment().


Pewarisan dan polimorfisme adalah alat yang kuat untuk mengorganisir kode dan membuat sistem yang fleksibel serta mudah diperluas. Dengan membuat subclass, kamu bisa menggunakan ulang kode yang sudah ada sambil menambahkan atau memodifikasi perilaku. Dengan meng-override method, kamu bisa menyesuaikan cara kerja subclass. Dan dengan menggunakan polimorfisme, kamu bisa menulis kode yang bekerja dengan banyak class berbeda melalui interface yang sama.

Kuncinya adalah menggunakan fitur-fitur ini secara bijak:

  • Gunakan pewarisan ketika ada hubungan "is-a" yang benar-benar nyata (seekor Dog adalah sebuah Animal)
  • Override method untuk membuat perilaku lebih spesifik, bukan untuk mengubah total apa yang dilakukan sebuah class
  • Gunakan super() untuk memperluas perilaku induk alih-alih menggantinya sepenuhnya
  • Utamakan polimorfisme (method yang sama) daripada pengecekan tipe jika memungkinkan

Saat kamu membangun program yang lebih besar, teknik OOP ini akan membantu kamu membuat kode yang lebih mudah dipahami, dirawat, dan dikembangkan.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai