Python & AI Tutorials Logo
Pemrograman Python

30. Pengenalan Class dan Object

30.1) Gagasan Pemrograman Berorientasi Objek (Object-Oriented Programming) (Membangun Tipe Kamu Sendiri)

Sepanjang buku ini, kamu sudah bekerja dengan tipe bawaan Python: integer, string, list, dictionary, dan lainnya. Setiap tipe menggabungkan data (seperti karakter dalam sebuah string) dengan operasi yang bisa kamu lakukan pada data tersebut (seperti .upper() atau .split()). Kombinasi data dan perilaku ini sangat kuat—ini membuat kamu bisa memikirkan string sebagai entitas utuh dengan kemampuannya sendiri, bukan sekadar urutan karakter mentah.

Pemrograman berorientasi objek (object-oriented programming/OOP) memperluas ide ini: ini memungkinkan kamu membuat tipe kustom kamu sendiri, yang disebut class, yang menggabungkan data dan perilaku yang spesifik untuk domain masalahmu. Sama seperti Python menyediakan tipe str untuk bekerja dengan teks dan tipe list untuk bekerja dengan urutan, kamu bisa membuat tipe BankAccount untuk mengelola transaksi finansial, tipe Student untuk melacak catatan akademik, atau tipe Product untuk sistem inventori.

Kenapa Membuat Tipe Kamu Sendiri?

Bayangkan mengelola informasi tentang siswa dalam sebuah sistem sekolah. Tanpa class, kamu mungkin memakai variabel terpisah atau dictionary:

python
# Menggunakan variabel terpisah - cepat jadi berantakan
student1_name = "Alice Johnson"
student1_id = "S12345"
student1_gpa = 3.8
 
student2_name = "Bob Smith"
student2_id = "S12346"
student2_gpa = 3.5
 
# Atau menggunakan dictionary - lebih baik, tapi tetap terbatas
student1 = {"name": "Alice Johnson", "id": "S12345", "gpa": 3.8}
student2 = {"name": "Bob Smith", "id": "S12346", "gpa": 3.5}

Pendekatan ini bekerja untuk kasus sederhana, tapi ada keterbatasannya:

  1. Tanpa validasi: Tidak ada yang mencegah kamu mengatur gpa ke nilai tidak valid seperti -5.0 atau "excellent"
  2. Tanpa perilaku terkait: Operasi seperti menghitung status honors atau memformat informasi siswa menjadi fungsi terpisah yang tersebar di seluruh code kamu
  3. Tanpa pemeriksaan tipe: Sebuah dictionary yang merepresentasikan siswa terlihat identik dengan dictionary lain mana pun—Python tidak bisa membantu kamu menangkap kesalahan saat kamu tanpa sengaja memakai dictionary produk ketika yang diharapkan adalah dictionary siswa

Class menyelesaikan masalah ini dengan memungkinkan kamu mendefinisikan tipe baru yang merepresentasikan secara tepat apa itu siswa dan operasi apa yang masuk akal untuk siswa:

python
# Kita akan membangun menuju ini - class Student yang menggabungkan data dan perilaku
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def is_honors(self):
        return self.gpa >= 3.5
    
    def display_info(self):
        status = "Honors" if self.is_honors() else "Regular"
        return f"{self.name} ({self.student_id}) - GPA: {self.gpa} [{status}]"
 
# Sekarang kita bisa membuat object siswa
alice = Student("Alice Johnson", "S12345", 3.8)
bob = Student("Bob Smith", "S12346", 3.5)
 
print(alice.display_info())  # Output: Alice Johnson (S12345) - GPA: 3.8 [Honors]
print(bob.is_honors())       # Output: True

Bab ini akan mengajarkan kamu cara membangun class seperti ini dari nol. Kita akan mulai dari class yang paling sederhana dan perlahan menambahkan fitur sampai kamu bisa membuat tipe kustom yang kaya dan berguna.

Class vs Instance: Analogi Blueprint

Memahami perbedaan antara class dan instance adalah hal mendasar dalam pemrograman berorientasi objek:

  • Sebuah class seperti blueprint atau template. Ia mendefinisikan data apa yang akan dimiliki sebuah tipe object dan operasi apa yang bisa dilakukannya. Class itu sendiri bukan siswa tertentu—ia adalah definisi tentang apa artinya menjadi seorang siswa.

  • Sebuah instance (juga disebut object) adalah contoh spesifik yang dibuat dari blueprint tersebut. Saat kamu membuat alice = Student("Alice Johnson", "S12345", 3.8), kamu sedang membuat satu instance siswa yang spesifik dengan data milik Alice.

Class Student
Blueprint

instance alice
Nama: Alice Johnson
ID: S12345
GPA: 3.8

instance bob
Nama: Bob Smith
ID: S12346
GPA: 3.5

instance carol
Nama: Carol Davis
ID: S12347
GPA: 3.9

Kamu bisa membuat sebanyak yang kamu butuhkan dari satu class, sama seperti seorang arsitek bisa menggunakan satu blueprint untuk membangun banyak rumah. Setiap instance punya data sendiri (GPA Alice berbeda dari Bob), tapi mereka semua berbagi struktur dan kemampuan yang sama yang didefinisikan oleh class.

Apa yang Akan Kamu Pelajari di Bab Ini

Bab ini memperkenalkan konsep inti pemrograman berorientasi objek di Python:

  1. Mendefinisikan class dengan keyword class
  2. Membuat instance dan mengakses atributnya
  3. Menambahkan method yang beroperasi pada data instance
  4. Memahami self dan bagaimana method mengakses data instance
  5. Menginisialisasi instance dengan method __init__
  6. Mengontrol representasi string dengan __str__ dan __repr__
  7. Membuat banyak instance yang independen dari class yang sama

Di akhir bab ini, kamu akan bisa merancang dan mengimplementasikan tipe kustom kamu sendiri yang membuat programmu lebih rapi, mudah dirawat, dan ekspresif. Kita akan membangun di atas fondasi ini di Bab 31 dengan fitur class yang lebih lanjut, dan di Bab 32 dengan inheritance dan polymorphism.

30.2) Mendefinisikan Class Sederhana dengan class

Mari mulai dengan membuat class sesederhana mungkin—class yang hanya mendefinisikan tipe baru tanpa data atau perilaku apa pun dulu.

Keyword class

Kamu mendefinisikan class menggunakan keyword class, diikuti nama class dan titik dua:

python
class Student:
    pass  # Class kosong untuk sekarang
 
# Buat sebuah instance
alice = Student()
print(alice)  # Output: <__main__.Student object at 0x...>
print(type(alice))  # Output: <class '__main__.Student'>

Bahkan class minimal ini berguna—ia membuat tipe baru bernama Student. Saat kamu membuat instance dengan alice = Student(), Python membuat object baru bertipe Student. Output menunjukkan bahwa alice memang sebuah object Student, meskipun belum melakukan hal menarik apa pun.

Konvensi Penamaan Class

Nama class Python mengikuti konvensi tertentu yang disebut CapWords atau PascalCase: setiap kata dimulai dengan huruf kapital, tanpa underscore di antara kata:

python
class BankAccount:      # Bagus: CapWords
    pass
 
class ProductInventory:  # Bagus: CapWords
    pass
 
class HTTPRequest:      # Bagus: akronim ditulis semua kapital
    pass
 
# Hindari gaya-gaya ini untuk class:
# class bank_account:   # Salah: snake_case untuk fungsi/variabel
# class bankaccount:    # Salah: sulit dibaca
# class BANKACCOUNT:    # Salah: ALL_CAPS untuk konstanta

Konvensi ini membantu membedakan class dari fungsi dan variabel (yang memakai snake_case) saat membaca code.

Membuat Instance

Membuat instance dari sebuah class terlihat seperti memanggil fungsi—kamu menggunakan nama class diikuti tanda kurung:

python
class Product:
    pass
 
# Buat tiga instance product yang berbeda
item1 = Product()
item2 = Product()
item3 = Product()
 
# Setiap instance adalah object terpisah
print(item1)  # Output: <__main__.Product object at 0x...>
print(item2)  # Output: <__main__.Product object at 0x...>
print(item3)  # Output: <__main__.Product object at 0x...>
 
# Mereka adalah object yang berbeda, meskipun tipenya sama
print(item1 is item2)  # Output: False
print(type(item1) is type(item2))  # Output: True

Setiap pemanggilan Product() membuat instance baru yang independen. Alamat memori (bagian 0x...) berbeda, mengonfirmasi bahwa ini adalah object terpisah di memori.

Kenapa Mulai dari Class Kosong?

Kamu mungkin bertanya-tanya kenapa kita mulai dari class yang tidak melakukan apa-apa. Ada dua alasan:

  1. Kejelasan konsep: Memahami bahwa class hanyalah tipe baru, terpisah dari data dan perilakunya, membantu kamu menangkap konsep mendasar sebelum menambah kompleksitas.

  2. Kegunaan praktis: Bahkan class kosong bisa berguna sebagai penanda atau placeholder. Misalnya, kamu bisa mendefinisikan tipe exception kustom:

python
class InvalidGradeError:
    pass
 
class StudentNotFoundError:
    pass
 
# Class kosong ini berfungsi sebagai tipe error yang berbeda

Namun, class kosong jarang ada di code nyata. Mari tambahkan data agar class kita berguna.

30.3) Membuat Instance dan Mengakses Atribut

Class menjadi berguna saat mereka menyimpan data. Di Python, kamu bisa menambahkan atribut (attribute) (data yang menempel pada sebuah instance) kapan saja hanya dengan melakukan assignment.

Menambahkan Atribut ke Instance

Kamu bisa menambahkan atribut ke sebuah instance menggunakan notasi titik:

python
class Student:
    pass
 
# Buat sebuah instance
alice = Student()
 
# Tambahkan atribut
alice.name = "Alice Johnson"
alice.student_id = "S12345"
alice.gpa = 3.8
 
# Akses atribut
print(alice.name)        # Output: Alice Johnson
print(alice.student_id)  # Output: S12345
print(alice.gpa)         # Output: 3.8

Operator titik (.) mengakses atribut: alice.name berarti "ambil atribut name dari object alice." Ini adalah sintaks yang sama yang sudah kamu gunakan dengan string (seperti text.upper()) dan list (seperti numbers.append(5))—itu mengakses method dan atribut dari object tersebut.

Setiap Instance Punya Atributnya Sendiri

Instance yang berbeda dari class yang sama punya atribut yang independen:

python
class Student:
    pass
 
# Buat dua siswa
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
bob = Student()
bob.name = "Bob Smith"
bob.gpa = 3.5
 
# Setiap instance punya datanya sendiri
print(alice.name)  # Output: Alice Johnson
print(bob.name)    # Output: Bob Smith
 
# Mengubah yang satu tidak memengaruhi yang lain
alice.gpa = 3.9
print(alice.gpa)  # Output: 3.9
print(bob.gpa)    # Output: 3.5 (unchanged)

Kemandirian ini sangat penting: alice dan bob adalah object terpisah dengan data terpisah. Memodifikasi alice.gpa tidak memengaruhi bob.gpa.

Atribut Bisa Bertipe Apa Pun

Atribut tidak terbatas pada tipe sederhana—mereka bisa menyimpan nilai Python apa pun:

python
class Student:
    pass
 
student = Student()
student.name = "Carol Davis"
student.grades = [95, 88, 92, 90]  # Atribut list
student.contact = {                 # Atribut dictionary
    "email": "carol@example.com",
    "phone": "555-0123"
}
student.is_active = True            # Atribut boolean
 
# Akses data bertingkat
print(student.grades[0])           # Output: 95
print(student.contact["email"])    # Output: carol@example.com

Fleksibilitas ini memungkinkan kamu memodelkan entitas dunia nyata yang kompleks dengan struktur data yang kaya.

Mengakses Atribut yang Tidak Ada

Mencoba mengakses atribut yang tidak ada akan memunculkan AttributeError:

python
class Student:
    pass
 
student = Student()
student.name = "David Lee"
 
print(student.name)  # Output: David Lee
# print(student.age)  # AttributeError: 'Student' object has no attribute 'age'

Error ini membantu—ia menangkap typo dan error logika ketika kamu berharap sebuah atribut ada, tapi ternyata tidak.

Masalah dengan Assignment Atribut Manual

Meskipun kamu bisa menambahkan atribut secara manual setelah membuat instance, pendekatan ini punya kekurangan serius:

python
class Student:
    pass
 
# Mudah lupa atribut atau salah eja
alice = Student()
alice.name = "Alice Johnson"
alice.student_id = "S12345"
# Lupa mengatur gpa!
 
bob = Student()
bob.name = "Bob Smith"
bob.stuent_id = "S12346"  # Typo: stuent bukan student
bob.gpa = 3.5
 
# Sekarang alice tidak punya gpa, dan bob punya typo
# print(alice.gpa)  # AttributeError
# print(bob.student_id)  # AttributeError

Ini rawan error dan melelahkan. Kamu butuh cara untuk memastikan setiap instance dimulai dengan atribut yang benar. Di sinilah method __init__ berperan, yang akan kita bahas di bagian 30.5. Tapi sebelumnya, mari pelajari tentang method—fungsi(function) yang menjadi milik sebuah class.

30.4) Menambahkan Method Instance: Memahami self

Method adalah fungsi yang didefinisikan di dalam class yang beroperasi pada data instance. Method memberi class kamu perilaku, bukan cuma data.

Mendefinisikan Method Sederhana

Mari tambahkan method ke class Student kita:

python
class Student:
    def display_info(self):
        print(f"{self.name} - GPA: {self.gpa}")
 
# Buat sebuah instance dan tambahkan atribut
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
# Panggil method-nya
alice.display_info()  # Output: Alice Johnson - GPA: 3.8

Method display_info didefinisikan di dalam class menggunakan def, sama seperti fungsi biasa. Perbedaan utamanya adalah parameter pertama: self.

Memahami self

Parameter self adalah cara sebuah method mengakses instance spesifik yang sedang dioperasikannya. Saat kamu memanggil alice.display_info(), Python secara otomatis mengoper alice sebagai argumen pertama ke method tersebut. Di dalam method, self merujuk ke alice, jadi self.name mengakses alice.name dan self.gpa mengakses alice.gpa.

Ini yang terjadi di balik layar:

python
class Student:
    def display_info(self):
        print(f"{self.name} - GPA: {self.gpa}")
 
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
# Dua pemanggilan ini setara:
alice.display_info()           # Cara normal
Student.display_info(alice)    # Yang sebenarnya dilakukan Python
 
# Keduanya menghasilkan: Alice Johnson - GPA: 3.8

Saat kamu menulis alice.display_info(), Python menerjemahkannya menjadi Student.display_info(alice). Instance (alice) menjadi parameter self di dalam method.

Kenapa "self"?

Nama self adalah konvensi, bukan keyword. Secara teknis kamu bisa memakai nama apa pun:

python
class Student:
    def display_info(this):  # Berfungsi, tapi jangan lakukan ini
        print(f"{this.name} - GPA: {this.gpa}")

Namun, selalu gunakan self. Ini adalah konvensi Python universal yang membuat code kamu mudah dibaca oleh programmer Python lain. Menggunakan nama lain akan membingungkan pembaca dan melanggar standar komunitas.

Method dengan Banyak Instance

Kekuatan self terlihat jelas saat kamu punya banyak instance:

python
class Student:
    def display_info(self):
        print(f"{self.name} - GPA: {self.gpa}")
 
# Buat dua siswa
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
bob = Student()
bob.name = "Bob Smith"
bob.gpa = 3.5
 
# Method yang sama, data yang berbeda
alice.display_info()  # Output: Alice Johnson - GPA: 3.8
bob.display_info()    # Output: Bob Smith - GPA: 3.5

Saat kamu memanggil alice.display_info(), self adalah alice. Saat kamu memanggil bob.display_info(), self adalah bob. Code method yang sama bekerja untuk instance mana pun karena self menyesuaikan dengan instance mana yang memanggilnya.

alice.display_info

self = alice

bob.display_info

self = bob

Akses alice.name
alice.gpa

Akses bob.name
bob.gpa

Method Bisa Menerima Parameter Tambahan

Method bisa menerima parameter selain self:

python
class Student:
    def update_gpa(self, new_gpa):
        self.gpa = new_gpa
        print(f"Updated {self.name}'s GPA to {self.gpa}")
 
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
alice.update_gpa(3.9)  # Output: Updated Alice Johnson's GPA to 3.9
print(alice.gpa)       # Output: 3.9

Saat kamu memanggil alice.update_gpa(3.9), Python mengoper alice sebagai self dan 3.9 sebagai new_gpa. Signature method-nya adalah def update_gpa(self, new_gpa), tapi kamu hanya memberikan satu argumen saat memanggilnya—Python menangani self secara otomatis.

Method Bisa Mengembalikan Nilai

Method bisa mengembalikan nilai seperti fungsi biasa:

python
class Student:
    def is_honors(self):
        return self.gpa >= 3.5
    
    def get_status(self):
        if self.is_honors():
            return "Honors Student"
        else:
            return "Regular Student"
 
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
bob = Student()
bob.name = "Bob Smith"
bob.gpa = 3.2
 
print(alice.get_status())  # Output: Honors Student
print(bob.get_status())    # Output: Regular Student

Perhatikan bagaimana get_status memanggil method lain (is_honors) menggunakan self.is_honors(). Method bisa memanggil method lain pada instance yang sama.

Method vs Fungsi: Kapan Menggunakan Masing-Masing

Kamu mungkin bertanya-tanya kapan menggunakan method dibanding fungsi mandiri. Berikut pedomannya:

Gunakan method ketika operasi tersebut:

  • Perlu akses ke data instance (self.name, self.gpa, dan seterusnya)
  • Secara logis merupakan bagian dari tipe tersebut (ini adalah sesuatu yang dilakukan atau dimiliki Student)
  • Memodifikasi state instance

Gunakan fungsi mandiri ketika operasi tersebut:

  • Tidak perlu data instance
  • Bekerja dengan banyak tipe
  • Merupakan utilitas umum
python
class Student:
    # Method: butuh data instance
    def is_honors(self):
        return self.gpa >= 3.5
 
# Fungsi: utilitas umum, bekerja dengan nilai GPA apa pun
def calculate_letter_grade(gpa):
    if gpa >= 3.7:
        return "A"
    elif gpa >= 3.0:
        return "B"
    elif gpa >= 2.0:
        return "C"
    else:
        return "D"
 
alice = Student()
alice.gpa = 3.8
 
# Gunakan method untuk pengecekan spesifik instance
print(alice.is_honors())  # Output: True
 
# Gunakan fungsi untuk perhitungan umum
print(calculate_letter_grade(alice.gpa))  # Output: A
print(calculate_letter_grade(2.5))        # Output: C

Pola Method yang Umum

Berikut beberapa pola umum yang akan sering kamu gunakan:

Method getter (mengambil informasi hasil komputasi):

python
class Student:
    def get_full_info(self):
        return f"{self.name} ({self.student_id}) - GPA: {self.gpa}"

Method setter (mengubah atribut dengan validasi):

python
class Student:
    def set_gpa(self, new_gpa):
        if 0.0 <= new_gpa <= 4.0:
            self.gpa = new_gpa
        else:
            print("Invalid GPA: must be between 0.0 and 4.0")

Method query (menjawab pertanyaan ya/tidak):

python
class Student:
    def is_honors(self):
        return self.gpa >= 3.5
    
    def is_failing(self):
        return self.gpa < 2.0

Method aksi (melakukan operasi):

python
class Student:
    def add_grade(self, grade):
        self.grades.append(grade)
        # Hitung ulang GPA berdasarkan semua grade
        self.gpa = sum(self.grades) / len(self.grades)

30.5) Menginisialisasi Instance dengan __init__

Mengatur atribut secara manual setelah membuat instance itu melelahkan dan rawan error. Method __init__ menyelesaikan ini dengan memungkinkan kamu menginisialisasi instance dengan data saat instance dibuat.

Method __init__

Method __init__ (dibaca "dunder init" atau "init") adalah method khusus yang Python panggil secara otomatis saat kamu membuat instance baru:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
# Buat instance dengan data awal
alice = Student("Alice Johnson", "S12345", 3.8)
bob = Student("Bob Smith", "S12346", 3.5)
 
print(alice.name)  # Output: Alice Johnson
print(bob.gpa)     # Output: 3.5

Saat kamu menulis Student("Alice Johnson", "S12345", 3.8), Python:

  1. Membuat instance Student kosong yang baru
  2. Memanggil __init__ dengan instance tersebut sebagai self dan argumen yang kamu berikan
  3. Mengembalikan instance yang sudah terinisialisasi

Method __init__ tidak secara eksplisit mengembalikan nilai—ia memodifikasi instance secara in-place dengan mengatur atributnya. Jika kamu mencoba mengembalikan nilai dari __init__, Python akan memunculkan TypeError.

python
class Student:
    def __init__(self, name):
        self.name = name
        # Jangan mengembalikan apa pun dari __init__
        # return self  # Wrong! TypeError: __init__() should return None, not 'Student'

Cara Kerja __init__

Mari uraikan apa yang terjadi langkah demi langkah:

python
class Student:
    def __init__(self, name, student_id, gpa):
        print(f"Initializing student: {name}")
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
        print(f"Initialization complete")
 
alice = Student("Alice Johnson", "S12345", 3.8)
# Output:
# Initializing student: Alice Johnson
# Initialization complete
 
print(alice.name)  # Output: Alice Johnson

Parameter setelah self (name, student_id, gpa) menjadi argumen wajib saat membuat instance. Jika kamu tidak menyediakannya, Python memunculkan TypeError:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
# student = Student()  # TypeError: __init__() missing 3 required positional arguments
# student = Student("Alice")  # TypeError: __init__() missing 2 required positional arguments
student = Student("Alice Johnson", "S12345", 3.8)  # Correct

Ini jauh lebih baik daripada assignment atribut manual—Python memastikan setiap instance dimulai dengan data yang diperlukan.

Nilai Parameter Default di __init__

Kamu bisa menggunakan nilai parameter default di __init__, sama seperti fungsi biasa:

python
class Student:
    def __init__(self, name, student_id, gpa=0.0):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
# GPA bersifat opsional, default ke 0.0
alice = Student("Alice Johnson", "S12345", 3.8)
bob = Student("Bob Smith", "S12346")  # Menggunakan default gpa=0.0
 
print(alice.gpa)  # Output: 3.8
print(bob.gpa)    # Output: 0.0

Ini berguna untuk atribut yang punya default yang masuk akal tetapi bisa dikustomisasi saat diperlukan.

Validasi di __init__

Kamu bisa memvalidasi input di __init__ untuk memastikan instance dimulai dalam keadaan valid:

python
class Student:
    def __init__(self, name, student_id, gpa):
        if not name:
            print("Error: Name cannot be empty")
            self.name = "Unknown"
        else:
            self.name = name
        
        self.student_id = student_id
        
        if 0.0 <= gpa <= 4.0:
            self.gpa = gpa
        else:
            print(f"Warning: Invalid GPA {gpa}, setting to 0.0")
            self.gpa = 0.0
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(alice.gpa)  # Output: 3.8
 
bob = Student("", "S12346", 5.0)
# Output:
# Error: Name cannot be empty
# Warning: Invalid GPA 5.0, setting to 0.0
print(bob.name)  # Output: Unknown
print(bob.gpa)   # Output: 0.0

Ini memastikan bahwa bahkan jika seseorang memasukkan data tidak valid, instance tetap berada dalam kondisi yang wajar.

30.6) Representasi String dengan __str__ dan __repr__

Saat kamu mencetak sebuah instance dengan print() atau melihatnya di interactive shell, Python perlu mengonversinya ke string. Secara default, kamu akan mendapatkan sesuatu yang tidak membantu:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(alice)  # Output: <__main__.Student object at 0x...>

Output default menampilkan nama class dan alamat memori, tapi tidak ada tentang data Alice yang sebenarnya. Kamu bisa mengustomisasi ini dengan method khusus __str__ dan __repr__.

Method __str__

Method __str__ mendefinisikan bagaimana instance kamu dikonversi ke string oleh print() dan str():

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def __str__(self):
        return f"{self.name} ({self.student_id}) - GPA: {self.gpa}"
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(alice)  # Output: Alice Johnson (S12345) - GPA: 3.8
print(str(alice))  # Output: Alice Johnson (S12345) - GPA: 3.8

Method __str__ sebaiknya mengembalikan string yang mudah dibaca dan informatif untuk end user. Anggap ini sebagai representasi yang "ramah".

Method __repr__

Method __repr__ mendefinisikan representasi string "resmi" dari instance kamu, digunakan oleh REPL dan repr():

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def __repr__(self):
        return f"Student('{self.name}', '{self.student_id}', {self.gpa})"
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(repr(alice))  # Output: Student('Alice Johnson', 'S12345', 3.8)

Di REPL:

python
>>> alice = Student("Alice Johnson", "S12345", 3.8)
>>> alice
Student('Alice Johnson', 'S12345', 3.8)

Method __repr__ sebaiknya mengembalikan string yang terlihat seperti code Python yang valid untuk membuat ulang object tersebut. Anggap ini sebagai representasi "developer"—ia harus tidak ambigu dan berguna untuk debugging.

Menggunakan __str__ dan __repr__ Sekaligus

Kamu bisa mendefinisikan kedua method untuk tujuan yang berbeda:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def __str__(self):
        # Format yang ramah, mudah dibaca
        return f"{self.name} - GPA: {self.gpa}"
    
    def __repr__(self):
        # Format yang tidak ambigu, mirip code
        return f"Student('{self.name}', '{self.student_id}', {self.gpa})"
 
alice = Student("Alice Johnson", "S12345", 3.8)
 
print(alice)        # Menggunakan __str__
# Output: Alice Johnson - GPA: 3.8
 
print(repr(alice))  # Menggunakan __repr__
# Output: Student('Alice Johnson', 'S12345', 3.8)

Di REPL:

python
>>> alice = Student("Alice Johnson", "S12345", 3.8)
>>> alice  # Uses __repr__
Student('Alice Johnson', 'S12345', 3.8)
>>> print(alice)  # Uses __str__
Alice Johnson - GPA: 3.8

Kapan Mendefinisikan Method yang Mana

Berikut pedomannya:

  • Selalu definisikan __repr__: Ini digunakan oleh REPL dan alat debugging. Jika kamu hanya mendefinisikan satu, definisikan yang ini.
  • Definisikan __str__ saat kamu butuh format yang ramah untuk pengguna: Jika class kamu akan dicetak untuk end user, sediakan __str__ yang mudah dibaca.
  • Jika kamu hanya mendefinisikan __repr__: Python menggunakannya untuk repr(), dan str() juga akan fallback untuk menggunakan __repr__ (jadi print() juga menggunakannya).
  • Jika kamu hanya mendefinisikan __str__: print() menggunakan __str__, tetapi repr() dan REPL menggunakan __repr__ default (menampilkan alamat memori). Ini kenapa mendefinisikan __repr__ biasanya lebih penting.
python
# Hanya __repr__ yang didefinisikan
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price
    
    def __repr__(self):
        return f"Product('{self.name}', {self.price})"
 
item = Product("Laptop", 999.99)
print(item)        # Menggunakan __repr__ sebagai fallback
# Output: Product('Laptop', 999.99)
print(repr(item))  # Menggunakan __repr__
# Output: Product('Laptop', 999.99)

Representasi String di Dalam Koleksi

Saat instance berada di dalam koleksi (list, dict, dll.), Python menggunakan __repr__ untuk menampilkannya, bukan __str__:

python
class Student:
    def __init__(self, name, gpa):
        self.name = name
        self.gpa = gpa
    
    def __str__(self):
        return f"{self.name}: {self.gpa}"
    
    def __repr__(self):
        return f"Student('{self.name}', {self.gpa})"
 
students = [
    Student("Alice", 3.8),
    Student("Bob", 3.5),
    Student("Carol", 3.9)
]
 
# Mencetak list menggunakan __repr__ untuk setiap student
print(students)
# Output: [Student('Alice', 3.8), Student('Bob', 3.5), Student('Carol', 3.9)]
 
# Mencetak student satu per satu menggunakan __str__
for student in students:
    print(student)
# Output:
# Alice: 3.8
# Bob: 3.5
# Carol: 3.9

Inilah alasan __repr__ harus tidak ambigu—ini membantu kamu memahami apa yang ada di dalam struktur data saat debugging. Saat kamu mencetak sebuah list, Python pada dasarnya memanggil repr() pada setiap elemen untuk menunjukkan struktur dengan jelas.

30.7) Membuat Banyak Instance Independen

Salah satu aspek paling kuat dari class adalah kamu bisa membuat banyak instance independen, masing-masing dengan datanya sendiri. Mari kita jelajahi ini lebih dalam.

Setiap Instance Punya Datanya Sendiri

Saat kamu membuat banyak instance dari class yang sama, masing-masing mempertahankan atribut terpisahnya sendiri:

python
class BankAccount:
    def __init__(self, account_number, holder_name, balance=0.0):
        self.account_number = account_number
        self.holder_name = holder_name
        self.balance = balance
    
    def deposit(self, amount):
        self.balance += amount
        print(f"Deposited ${amount:.2f}. New balance: ${self.balance:.2f}")
    
    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
            print(f"Withdrew ${amount:.2f}. New balance: ${self.balance:.2f}")
            return True
        else:
            print(f"Insufficient funds. Balance: ${self.balance:.2f}")
            return False
    
    def __str__(self):
        return f"{self.holder_name}'s account ({self.account_number}): ${self.balance:.2f}"
 
# Buat tiga akun independen
alice_account = BankAccount("ACC-001", "Alice Johnson", 1000.0)
bob_account = BankAccount("ACC-002", "Bob Smith", 500.0)
carol_account = BankAccount("ACC-003", "Carol Davis", 2000.0)
 
# Operasi pada satu akun tidak memengaruhi yang lain
alice_account.deposit(500)
# Output: Deposited $500.00. New balance: $1500.00
 
bob_account.withdraw(200)
# Output: Withdrew $200.00. New balance: $300.00
 
# Setiap akun mempertahankan saldonya sendiri
print(alice_account)  # Output: Alice Johnson's account (ACC-001): $1500.00
print(bob_account)    # Output: Bob Smith's account (ACC-002): $300.00
print(carol_account)  # Output: Carol Davis's account (ACC-003): $2000.00

Kemandirian ini adalah hal mendasar dalam pemrograman berorientasi objek. Setiap instance adalah entitas terpisah dengan state-nya sendiri.

Instance di Dalam Koleksi

Kamu bisa menyimpan instance di list, dictionary, atau koleksi lain apa pun:

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def is_honors(self):
        return self.gpa >= 3.5
    
    def __repr__(self):
        return f"Student('{self.name}', '{self.student_id}', {self.gpa})"
 
# Buat list student
students = [
    Student("Alice Johnson", "S12345", 3.8),
    Student("Bob Smith", "S12346", 3.2),
    Student("Carol Davis", "S12347", 3.9),
    Student("David Lee", "S12348", 3.4)
]
 
# Temukan semua honors student
honors_students = []
for student in students:
    if student.is_honors():
        honors_students.append(student)
 
print("Honors students:")
for student in honors_students:
    print(f"  {student.name}: {student.gpa}")
# Output:
# Honors students:
#   Alice Johnson: 3.8
#   Carol Davis: 3.9
 
# Hitung rata-rata GPA
total_gpa = sum(student.gpa for student in students)
average_gpa = total_gpa / len(students)
print(f"Average GPA: {average_gpa:.2f}")  # Output: Average GPA: 3.58

Ini adalah pola yang umum: buat banyak instance, simpan dalam koleksi, lalu proses dengan loop dan comprehension.

Instance Bisa Merujuk ke Instance Lain

Instance bisa punya atribut yang merujuk ke instance lain, sehingga membuat relasi antar object:

python
class Course:
    def __init__(self, course_code, course_name):
        self.course_code = course_code
        self.course_name = course_name
    
    def __str__(self):
        return f"{self.course_code}: {self.course_name}"
 
class Student:
    def __init__(self, name, student_id):
        self.name = name
        self.student_id = student_id
        self.courses = []  # List dari instance Course
    
    def enroll(self, course):
        self.courses.append(course)
        print(f"{self.name} enrolled in {course.course_name}")
    
    def list_courses(self):
        print(f"{self.name}'s courses:")
        for course in self.courses:
            print(f"  {course}")
 
# Buat course
python_course = Course("CS101", "Introduction to Python")
data_course = Course("CS102", "Data Structures")
web_course = Course("CS103", "Web Development")
 
# Buat student dan daftarkan mereka ke course
alice = Student("Alice Johnson", "S12345")
alice.enroll(python_course)
alice.enroll(data_course)
# Output:
# Alice Johnson enrolled in Introduction to Python
# Alice Johnson enrolled in Data Structures
 
bob = Student("Bob Smith", "S12346")
bob.enroll(python_course)
bob.enroll(web_course)
# Output:
# Bob Smith enrolled in Introduction to Python
# Bob Smith enrolled in Web Development
 
# Tampilkan course tiap student
alice.list_courses()
# Output:
# Alice Johnson's courses:
#   CS101: Introduction to Python
#   CS102: Data Structures
 
bob.list_courses()
# Output:
# Bob Smith's courses:
#   CS101: Introduction to Python
#   CS103: Web Development

Perhatikan bahwa Alice dan Bob sama-sama terdaftar di python_course—mereka merujuk ke instance Course yang sama. Ini memodelkan relasi dunia nyata di mana banyak siswa bisa mengambil course yang sama.

Identitas dan Kesetaraan Instance

Setiap instance adalah object unik, meskipun datanya sama dengan instance lain:

python
class Student:
    def __init__(self, name, gpa):
        self.name = name
        self.gpa = gpa
 
alice1 = Student("Alice", 3.8)
alice2 = Student("Alice", 3.8)
 
# Object yang berbeda, meskipun datanya identik
print(alice1 is alice2)  # Output: False
print(id(alice1) == id(alice2))  # Output: False

Secara default, == juga mengecek identitas (apakah mereka object yang sama), bukan apakah mereka punya data yang sama. Di Bab 31, kita akan belajar cara mengustomisasi perbandingan kesetaraan dengan method khusus __eq__.


Bab ini sudah memperkenalkan kamu pada dasar-dasar pemrograman berorientasi objek di Python. Kamu sudah belajar cara mendefinisikan class, membuat instance, menambahkan method, menginisialisasi instance dengan __init__, mengontrol representasi string, dan bekerja dengan banyak instance independen. Konsep-konsep ini menjadi fondasi untuk fitur OOP yang lebih lanjut yang akan kita eksplorasi di Bab 31 dan 32.


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