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:
# 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:
- Tanpa validasi: Tidak ada yang mencegah kamu mengatur
gpake nilai tidak valid seperti-5.0atau"excellent" - Tanpa perilaku terkait: Operasi seperti menghitung status honors atau memformat informasi siswa menjadi fungsi terpisah yang tersebar di seluruh code kamu
- 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:
# 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: TrueBab 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.
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:
- Mendefinisikan class dengan keyword
class - Membuat instance dan mengakses atributnya
- Menambahkan method yang beroperasi pada data instance
- Memahami
selfdan bagaimana method mengakses data instance - Menginisialisasi instance dengan method
__init__ - Mengontrol representasi string dengan
__str__dan__repr__ - 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:
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:
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 konstantaKonvensi 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:
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: TrueSetiap 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:
-
Kejelasan konsep: Memahami bahwa class hanyalah tipe baru, terpisah dari data dan perilakunya, membantu kamu menangkap konsep mendasar sebelum menambah kompleksitas.
-
Kegunaan praktis: Bahkan class kosong bisa berguna sebagai penanda atau placeholder. Misalnya, kamu bisa mendefinisikan tipe exception kustom:
class InvalidGradeError:
pass
class StudentNotFoundError:
pass
# Class kosong ini berfungsi sebagai tipe error yang berbedaNamun, 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:
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.8Operator 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:
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:
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.comFleksibilitas 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:
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:
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) # AttributeErrorIni 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:
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.8Method 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:
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.8Saat 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:
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:
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.5Saat 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.
Method Bisa Menerima Parameter Tambahan
Method bisa menerima parameter selain self:
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.9Saat 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:
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 StudentPerhatikan 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
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: CPola Method yang Umum
Berikut beberapa pola umum yang akan sering kamu gunakan:
Method getter (mengambil informasi hasil komputasi):
class Student:
def get_full_info(self):
return f"{self.name} ({self.student_id}) - GPA: {self.gpa}"Method setter (mengubah atribut dengan validasi):
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):
class Student:
def is_honors(self):
return self.gpa >= 3.5
def is_failing(self):
return self.gpa < 2.0Method aksi (melakukan operasi):
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:
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.5Saat kamu menulis Student("Alice Johnson", "S12345", 3.8), Python:
- Membuat instance
Studentkosong yang baru - Memanggil
__init__dengan instance tersebut sebagaiselfdan argumen yang kamu berikan - 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.
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:
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 JohnsonParameter setelah self (name, student_id, gpa) menjadi argumen wajib saat membuat instance. Jika kamu tidak menyediakannya, Python memunculkan TypeError:
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) # CorrectIni 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:
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.0Ini 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:
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.0Ini 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:
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():
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.8Method __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():
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:
>>> 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:
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:
>>> alice = Student("Alice Johnson", "S12345", 3.8)
>>> alice # Uses __repr__
Student('Alice Johnson', 'S12345', 3.8)
>>> print(alice) # Uses __str__
Alice Johnson - GPA: 3.8Kapan 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 untukrepr(), danstr()juga akan fallback untuk menggunakan__repr__(jadiprint()juga menggunakannya). - Jika kamu hanya mendefinisikan
__str__:print()menggunakan__str__, tetapirepr()dan REPL menggunakan__repr__default (menampilkan alamat memori). Ini kenapa mendefinisikan__repr__biasanya lebih penting.
# 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__:
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.9Inilah 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:
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.00Kemandirian 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:
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.58Ini 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:
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 DevelopmentPerhatikan 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:
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: FalseSecara 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.