bute yang diubah yaitu class attribute, dan perubahan
dilakukan lewat instance object? Jawabannya: nilai baru hasil operasi
assignment tersebut akan ditampung sebagai nilai instance attribute dan
efeknya hanya ada pada object saja (tidak berefek ke class). Contoh:
class Pencil:
def __init__(self):
self.note = "A class type to represent a pencil"
pencil1 = Pencil()
pencil1.note = "A pencil"
pencil2 = Pencil()
print(f"Object pencil1 note: {pencil1.note}")
# output ➜ Object pencil1 note: A pencil
print(f"Object pencil2 note: {pencil2.note}")
# output ➜ Object pencil2 note: A class type to represent a pencil
class Book:
note = "A class type to represent a book"
book1 = Book()
book2 = Book()
book2.note = "A book"
print(f"Class Book note: {Book.note}")
Bisa dilihat pada bagian statement book2.note = "A book" efek
peruabahannya hanya pada instance object-nya ( book2 ). Class attribute
Book.note nilainya tetap.
â—‰ Perubahan nilai class attribute secara langsung
Beda lagi untuk kasus dimana attribute yang diubah nilainya yaitu class
attribute dengan perubahan dilakukan secara langsung dari class-nya.
Perubahan tersebut akan berefek ke semua object dan class itu sendiri.
Sebagai contoh, pada kode berikut, object book1 dan book2 dibuat dari class
Book . Kemudian class attribute Book.note diubah nilainya, efeknya: class
attribute dalam book1 dan book2 juga ikut berubah.
class Book:
note = "A class type to represent a book"
book1 = Book()
book2 = Book()
Book.note = "A book"
print(f"Class Book note: {Book.note}")
# output ➜ Class Book note: A book
print(f"Object book1 note: {book1.note}")
# output ➜ Object book1 note: A book
print(f"Object book2 note: {book2.note}")
# output ➜ Object book2 note: A book
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../instance-
attribute-class-attribute
â—‰ Chapter relevan lainnya
• OOP ➜ Class & Object
• OOP ➜ Property Visibility
â—‰ TBA
• list-type attribute behaviour on class attribute vs instance attribute
â—‰ Referensi
• https://docs.python.org/3/tutorial/classes.html
A.38. Python OOP ➜ Class
Method
Pada chapter ini kita akan belajar tentang jenis method lain yang tersedia di
Python, yaitu class method, serta perbedaannya dibanding instance method
dan constructor.
A.38.1. Pengenalan Class method
Class method yaitu method yang pemiliknya yaitu class dengan
pengaksesan yaitu via class, berbeda dibanding instance method yang
diperuntukan untuk instance object. Jika instance method memiliki parameter
self yang isinya yaitu instance object, maka class method memiliki
parameter cls yang isinya yaitu tipe data class.
Jika dilihat dari segi deklarasinya, class method dan instance method memiliki
perbedaan berikut:
• Instance method memiliki parameter pertama bernama self , dengan isi
yaitu instance object.
• Class method memiliki parameter pertama bernama cls yang merupakan
alias dari class dimana method tersebut dideklarasikan (misalnya class
method dideklarasikan dalam class Car , maka paramter cls berisi tipe
data class Car ).
• Selain perbedaan di atas, class method dideklariskan dengan menuliskan
decorator @classmethod
Pembahasan detail mengenai decorator dibahas pada chapter Decorator
Agar lebih jelas silakan pelajari kode berikut lalu praktekan. Disini sebuah class
bernama ClanHouse dibuat dengan isi 3 buah fungsi:
• Constructor __init__() dengan overload parameter name dan house
• Class method create() digunakan untuk membuat instance object
ClassHouse
• Instance method info() digunakan untuk menampilkan data name dan
house
Bisa dilihat perbedaan deklarasi instance method dan class method di contoh
tersebut. Method create() memiliki parameter pertama cls yang
merupakan alias untuk tipe data class ClanHouse . Lewat cls , dibuat sebuah
instance object bernama obj kemudian dijadikan nilai balik method
create() .
Statement obj = cls() dalam method create() yaitu ekuivalen
dengan obj = ClanHouse() ,
karena dalam method tersebut nilai cls yaitu tipe data class dimana
class ClanHouse:
def __init__(self, name = "", house = ""):
self.name = name
self.house = house
@classmethod
def create(cls):
obj = cls()
return obj
def info(self):
print(f"{self.name} of {self.house}")
method dideklarasikan, yaitu ClanHouse
Selanjutnya, dari class ClanHouse akan dibuat 3 buah instance object berikut:
• Object p1 dibuat memakai constructor
• Object p2 dibuat memakai constructor juga, dengan parameter di-
overload
• Object p3 dibuat memakai class method create() . Class method
tersebut diakses dari class, bisa dilihat dari syntax-nya yaitu
ClanHouse.create() .
Parameter cls bisa disebut dengan parameter implicit atau implisit
karena kita tidak berinteraksi secara langsung saat pengisian nilai. Nilai
cls otomatis terisi saat class method diakses.
p1 = ClanHouse()
p1.name = "Paul Atriedes"
p1.house = "House of Atriedes"
p1.info()
# output ➜ Paul Atriedes of House of Atriedes
p2 = ClanHouse("Lady Jessica", "Bene Gesserit")
p2.info()
# output ➜ Lady Jessica of Bene Gesserit
p3 = ClanHouse.create()
p3.name = "Baron Vladimir Harkonnen"
p3.house = "House of Harkonnen"
p3.info()
# output ➜ Baron Vladimir Harkonnen of House of Harkonnen
A.38.2. Class method parameter
Class method juga bisa memiliki parameter seperti umumnya fungsi. Jika pada
instance method dan constructor parameter yaitu ditulis setelah self , pada
class method parameter ditulis setelah cls . Contoh:
Dari kode di atas bisa dilihat bahwa parameter cls milik class method
diperlakukan mirip seperti parameter self milik constructor dan instance
method.
class ClanHouse:
def __init__(self, name = "", house = ""):
self.name = name
self.house = house
@classmethod
def create(cls, name = "", house = ""):
obj = cls()
obj.name = name
obj.house = house
return obj
def info(self):
print(f"{self.name} of {self.house}")
p2 = ClanHouse("Lady Jessica", "Bene Gesserit")
p2.info()
# output ➜ Lady Jessica of Bene Gesserit
p4 = ClanHouse.create("Glossu Rabban", "House of Harkonnen")
p4.info()
# output ➜ Glossu Rabban of House of Harkonnen
• Pada saat pengaksesan instance method atau constructor, parameter
self yaitu diabaikan karena otomatis berisi instance object.
• Sifat yang sama juga berlaku pada parameter cls pada class method.
Saat diakses via class (contoh: ClanHouse.create() ), parameter cls
diabaikan.
Parameter cls pada method create() berisi tipe data class ClanHouse ,
dan pembuatan instance object selalu via pemanggilan nama class, maka dari
sini pemanggilan cls() dalam method create() juga bisa diikuti dengan
pengisian argument parameter.
Sebagai perbandingan, kedua bentuk pemanggilan constructor via cls()
berikut yaitu ekuivalen:
• Cara 1: variabel cls digunakan dipanggil sebagai constructor tanpa
parameter
• Cara 2: variabel cls digunakan dipanggil sebagai constructor dengan diisi
argument parameter
class ClanHouse:
def __init__(self, name = "", house = ""):
self.name = name
self.house = house
@classmethod
def create(cls, name = "", house = ""):
obj = cls()
obj.name = name
obj.house = house
return obj
A.38.3. Pengaksesan class method via
instance object
Sampai sini penulis rasa bisa dipahami perbedaan cara pengaksesan antara
instance method dan class method. Instance method diakses via instance
object, dan class method diakses via class.
Selain cara tersebut, sebenarnya class method bisa juga diakses via instance
object lho, dan hal seperti ini diperbolehkan penerapannya. Caranya bisa
dilihat pada kode berikut:
class ClanHouse:
def __init__(self, name = "", house = ""):
self.name = name
self.house = house
@classmethod
def create(cls, name = "", house = ""):
obj = cls(name, house)
return obj
class ClanHouse:
def __init__(self, name = "", house = ""):
self.name = name
self.house = house
def info(self):
print(f"{self.name} of {self.house}")
@classmethod
def create(cls, name = "", house = ""):
Dari kode di atas bisa dilihat perbedaan dari sisi pembuatan object dan
pengaksesan method antara p2 dan p4 .
• Instance object p2 dibuat via constructor ClanHouse()
• Instance object p4 dibuat via class method create()
• Dari kedua object, diakses method info()
Yang menarik untuk dibahas yaitu p5 . Object p5 dibuat dari pemanggilan
class method create() namun pengaksesannya yaitu via instance object
p2 . Penulisan seperti itu diperbolehkan. Parameter cls pada class method
create() akan terisi dengan nilai tipe data class object p2 (yaitu
ClanHouse ).
A.38.4. Pengaksesan instance method
via class
Jika class method bisa diakses via instance object, instance method juga bisa
diakses via Class. Caranya cukup panggil instance method via class lalu isi
parameter self dengan instance object. Contoh:
p2 = ClanHouse("Lady Jessica", "Bene Gesserit")
ClanHouse.info(p2)
# output ➜ Lady Jessica of Bene Gesserit
p4 = ClanHouse.create("Glossu Rabban", "House of Harkonnen")
ClanHouse.info(p4)
# output ➜ Glossu Rabban of House of Harkonnen
p5 = p2.create("Irulan Corrino", "Corrino Empire")
ClanHouse.info(p5)
# output ➜ Irulan Corrino of Corrino Empire
Pengaksesan instance method via class mengharuskan parameter self milik
method untuk diisi dengan object. Hal ini berbeda dibanding pengaksesan
instance method via instance object dimana parameter self otomatis terisi
nilai instance object.
A.38.5. Pengaksesan class attribute viacls
Pada chapter sebelumnya, OOP ➜ Instance Attribute & Class Attribute, kita
telah mempelajari tentang perbedaan instance attribute dibanding class
attribute.
Class attribute bisa diakses via instance object maupun class. Dalam konteks
class method dimana cls yaitu berisi tipe data class, pengaksesan class
attribute memungkinkan untuk dilakukan via variabel cls .
Contoh penerapannya bisa dilihat pada kode berikut:
class ClanHouse:
note = "ClanHouse: a class to represent clan house in Dune universe"
def __init__(self, name = "", house = ""):
self.name = name
self.house = house
@classmethod
def create(cls, name = "", house = ""):
print("#1", cls.note)
obj = cls(name, house)
print("#2", obj.note)
Output program:
A.38.6. Summary
Dari banyak hal yang telah dipelajari di chapter ini, secara garis besar
perbedaan antara constructor, instance method, dan class method bisa dilihat
di bawah ini:
â—‰ Constructor
• Fungsi dideklarasikan di dalam block class
• Deklarasinya memakai nama fungsi __init__()
• Parameter pertama harus self , berisi instance object
• Pemanggilan constructor mengembalikan instance object
• Pengaksesannya via pemanggilan nama class, contoh: ClanHouse()
â—‰ Instance method
• Fungsi dideklarasikan di dalam block class
• Parameter pertama harus self , berisi instance object
• Pengaksesan instance method:
â—¦ Via instance object, contoh: p2.info()
â—¦ Via class dengan menyisipkan instance object sebagai argument
pemanggilan. contoh: ClanHouse.info(p2)
â—‰ Class method
• Fungsi dideklarasikan di dalam block class
• Fungsi memiliki decorator @classmethod
• Parameter pertama harus cls , berisi tipe data class
• Pengaksesan class method:
â—¦ Via class, contoh: ClanHouse.create()
â—¦ Via instance object, contoh: p2.create()
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../class-
method
â—‰ Chapter relevan lainnya
• OOP ➜ Class & Object
• OOP ➜ Instance Method
• OOP ➜ Constructor
• OOP ➜ Static Method
• Function ➜ Decorator
• OOP ➜ Abstract Method
â—‰ Referensi
• https://docs.python.org/3/tutorial/classes.html
A.39. Python OOP ➜ Static
Method
Chapter ini membahas tentang static method beserta penggunaan dan
perbedaannya dibanding jenis method lainnya.
A.39.1. Pengenalan static method
Telah kita pelajari bahwa suatu fungsi agar dikenali sebagai method harus
dideklarasikan di dalam block class dan memiliki parameter implicit self
(untuk instance method) dan cls (untuk class method).
Selain dua method tersebut, ada lagi jenis method lain bernama static method
atau method statis, yang ciri khasnya yaitu memiliki decorator
@staticmethod dan tidak memiliki parameter implicit self maupun cls .
Static method bisa diakses via instance object maupun via class. Contoh
penerapannya bisa dilihat pada kode berikut:
class Person:
def __init__(self, name = ""):
self.name = name
def info(self):
print(f"{self.name}")
@classmethod
def create(cls, name = ""):
obj = cls()
Di contoh di atas, ada dua buah static method dideklarasikan:
• Method say_hello() dideklarasikan tanpa parameter
• Method say_something() dideklarasikan dengan 2 buah parameter
Kedua method tersebut diakses untuk memunculkan 5 buah output berbeda
via instance object maupun via class Person :
• Method say_hello() dipanggil 2x via instance object edward dan via
class Person
• Method say_something() juga sama, diakses via instance object 2x dan
diakses via class 1x
A.39.2. Fungsi staticmethod()
Python menyediakan fungsi bernama staticmethod() yang kegunaannya
yaitu untuk mengkonversi fungsi biasa (yang dideklarasikan di luar class)
menjadi static method milik suatu class.
Sebagai contoh, kode praktek yang telah ditulis kita refator menjadi seperti ini.
Fungsi say_hello() dan say_something() dideklarasikan sebagai fungsi
biasa. Kemudian dijadikan sebagai class method milik class Person via
peneparan staticmethod() .
def say_hello():
print("hello")
def say_something(message, name = None):
if name != None:
print(f"{name} said: {message}")
else:
print(message)
Cara penerapan fungsi staticmethod() yaitu dengan cukup memanggilnya
untuk membungkus fungsi biasa, lalu nilai baliknya ditampung sebagai
attribute class.
Attribute say_hello dan say_something keduanya menjadi static method.
Nama class attribute penampung pemanggilan fungsi staticmethod() bisa
nama apapun, tidak harus sama dengan nama fungsi aslinya. Contohnya bisa
dilihat pada kode berikut, fungsi say_something() dijadikan sebagai class
method bernama greet() milik class Person .
class Person:
def __init__(self, name = ""):
self.name = name
say_hello = staticmethod(say_hello)
say_something = staticmethod(say_something)
def say_something(message, name = None):
if name != None:
print(f"{name} said: {message}")
else:
print(message)
class Person:
def __init__(self, name = ""):
self.name = name
greet = staticmethod(say_something)
p5 = Person("Ezio Auditore da Firenze")
p5.greet("hello", p5.name)
# output ➜ Ezio Auditore da Firenze said: hello
Fungsi say_something() sendiri tetap bisa digunakan secara normal
meskipun telah dijadikan sebagai static method milik class Person .
A.39.3. Summary
Perbedaan antara constructor, instance method, class method, dan static
method bisa dilihat di bawah ini:
â—‰ Constructor
• Fungsi dideklarasikan di dalam block class
• Deklarasinya memakai nama fungsi __init__()
• Parameter pertama harus self , berisi instance object
• Pemanggilan constructor mengembalikan instance object
• Pengaksesannya via pemanggilan nama class, contoh: Person()
â—‰ Instance method
• Fungsi dideklarasikan di dalam block class
• Parameter pertama harus self , berisi instance object
• Pengaksesan instance method:
â—¦ Via instance object, contoh: p2.info()
â—¦ Via class dengan menyisipkan instance object sebagai argument
pemanggilan. contoh: Person.info(p2)
â—‰ Class method
• Fungsi dideklarasikan di dalam block class
• Fungsi memiliki decorator @classmethod
• Parameter pertama harus cls , berisi tipe data class
• Pengaksesan class method:
â—¦ Via class, contoh: Person.create()
â—¦ Via instance object, contoh: p2.create()
â—‰ Static method
• Fungsi dideklarasikan di dalam block class
• Fungsi memiliki decorator @staticmethod
• Tidak memiliki implicit parameter self maupun cls
• Pengaksesan static method:
â—¦ Via class, contoh: Person.say_hello()
â—¦ Via instance object, contoh: p1.say_hello()
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../static-
method
â—‰ Chapter relevan lainnya
• OOP ➜ Class & Object
• OOP ➜ Instance Method
• OOP ➜ Constructor
• OOP ➜ Class Method
• Function ➜ Decorator
• OOP ➜ Abstract Method
â—‰ Referensi
• https://docs.python.org/3/tutorial/classes.html
A.40. Python Decorator
Chapter ini membahas tentang decorator, yaitu syntax yang penulisannya
diawali dengan huruf @ dituliskan tepat sebelum deklarasi fungsi atau method.
A.40.1. Pengenalan decorator
Pada dua chapter sebelumnya (OOP ➜ Class Method dan OOP ➜ Static Method)
kita telah mempelajari 2 buah decorator, yaitu @classmethod dan
@staticmethod .
Decorator tersebut merupakan bawaan dari Python Standard Library, kita bisa
langsung memakai nya sesuai kebutuhan.
Setiap decorator memiliki tugas spesifik yang berbeda satu sama lain,
misalnya:
• Decorator @classmethod digunakan untuk menandai suatu method yaitu
class method
• Decorator @staticmethod digunakan untuk menandai suatu method
yaitu static method
Selain dua decorator tersebut, ada juga beberapa lainnya yang nantinya akan
dibahas ketika sudah masuk topik yang relevan dengan kegunaan masing-
masing decorator.
Selain memakai decorator bawaan Python, kita juga bisa membuat custom
decorator sendiri yang bisa kita desain sesuai kebutuhan. Pada chapter ini kita
akan mempelajari caranya.
A.40.2. Custom decorator
Decorator sebenarnya yaitu sebuah fungsi, yang menerima parameter berupa
fungsi, yang isinya juga mengembalikan fungsi/closure.
Agar lebih jelas, silakan lihat kode berikut. Terdapat sebuah fungsi bernama
inspeksi() yang parameter dan nilai baliknya yaitu fungsi. Fungsi
inspeksi() ini dipergunakan sebagai decorator fungsi lain yaitu
say_hello() .
Bisa dilihat di kode, bahwa fungsi inspeksi bisa langsung digunakan sebagai
decorator dengan cukup menambahkan karakter @ sebagai prefix lalu
dituliskan tepat sebelum deklarasi fungsi.
Untuk nama closure tidak ada aturan yang mengikat harus dinamai apa.
Pada contoh di atas closure fungsi inspeksi() penulis namai
inner_func() .
Fungsi say_hello() ditempeli decorator @inspeksi , artinya pemanggilan
def inspeksi(func):
def inner_func():
return func()
return inner_func
@inspeksi
def say_hello():
print("hello world")
say_hello()
# output ➜ hello world
fungsi tersebut yaitu ekuivalen dengan statement ke-2 kode berikut:
Penerapan decorator @inspeksi membuat eksekusi fungsi yang ditempelinya
menjadi terbungkus oleh fungsi inspeksi() .
Ok, sekarang kita breakdown lebih detail lagi tentang fungsi inspeksi() yang
sudah dibuat. Kegunaan dari fungsi ini sebenarnya apa? Jawabannya yaitu
tidak ada, tidak ada gunanya sama sekali, karena ini hanyalah contoh versi
sederhana tentang penerapan custom decorator.
Agar fungsi inspeksi() lebih berguna, mari kita tambahkan sesuatu. Tepat
sebelum dan sesudah pemanggilan parameter closure func milik fungsi
inspeksi() , tambahkan statement print.
say_hello()
# output ➜ hello world
inspeksi(say_hello)()
# output ➜ hello world
def inspeksi(func):
def inner_func():
print("fungsi ini akan dipanggil", func)
res = func()
print("fungsi telah selesai dieksekusi, nilai baliknya:", res)
return res
return inner_func
@inspeksi
def say_hello():
print("hello world")
say_hello()
Jalankan program, outpunya kurang lebih seperti ini:
Bisa dilihat sekarang ada pesan muncul sebelum string hello world di-print.
Hal ini karena kita telah menambahkan 2 statement print yang ditempatkan
sebelum dan setelah eksekusi fungsi.
Penulis akan cukup sering memakai istilah fungsi decorator pada
chapter ini, dan istilah tersebut mengacu ke fungsi yang digunakan
sebagai decorator (bukan mengacu ke fungsi yang ditempeli decorator).
Pada contoh di atas, fungsi decorator yang dimaksud yaitu fungsi
inspeksi() .
Fungsi say_hello() tidak memiliki nilai balik, jadinya fungsi tersebut
mengembalikan tipe data None . Bisa dilihat pada pesan ke-2 nilai balik
pemanggilan func() disitu yaitu None .
Jika diilustrasikan statement print-nya saja, program di atas mengeksekusi 3
statement ini:
Sampai sini semoga cukup jelas. Selanjutnya kita akan praktek penerapan
decorator untuk case yang tidak sesederhana contoh di atas, dengan harapan
pemahaman pembaca mengenai topik decorator ini makin mantab.
print("fungsi ini akan dipanggil", func)
print("hello world")
print("fungsi telah selesai dieksekusi, nilai baliknya:", res)
A.40.3. Contoh penerapan custom
decorator
Pada praktek selanjutnya ini, kita akan membuat program yang memunculkan
list berisi angka random. Kemudian dari list tersebut dibentuk sebuah list baru
berisi elemen unik, lalu darinya dibuat list baru lagi yang isi elemen uniknya
diurutkan secara descending.
â—‰ Tahap 1: Program awal
Pertama, tulis kode berikut kemudian jalankan:
import random
def generate_random_list(length):
r = []
for _ in range(0, length):
n = random.randint(0, 10)
r.append(n)
return r
def unique_list(data):
s = set(data)
r = list(s)
return r
def reverse_list(data):
data.sort(reverse=True)
return data
data = generate_random_list(15)
print("data:", data)
O iya, karena disini module random digunakan untuk generate elemen list, bisa
jadi hasil generate di lokal masing-masing yaitu berbeda dengan angka yang
muncul di tutorial ini.
â—‰ Tahap 2: Decorator unique & reverse
Selanjutnya, kita refactor fungsi unique_list() dan reverse_list() yang
sudah di tulis menjadi decorator.
• Fungsi unique_list() diubah menjadi fungsi decorator bernama
decorator_unique_list()
• Fungsi reverse_list() diubah menjadi fungsi decorator bernama
decorator_reverse_list()
def decorator_unique_list(func):
def execute(length):
data = func(length)
s = set(data)
r = list(s)
return r
return execute
def decorator_reverse_list(func):
def execute(length):
data = func(length)
data.sort(reverse=True)
return data
return execute
Bisa dilihat pada kedua decorator yang telah ditulis, kode utama masing-masing
decorator dituliskan setelah statement data = func(length) . Variabel data
disitu isinya yaitu hasil pemanggilan method dimana decorator ditempelkan
nantinya. Variabel tersebut kemudian diolah sesuai dengan kebutuhan.
• Decorator decorator_unique_list() menghasilkan data list berisi elemen
unik
• Decorator decorator_reverse_list() menghasilkan data list berisi
elemen dengan urutan terbalik
Disini penulis memakai prefix decorator_ pada nama fungsi untuk
membedakan mana fungsi biasa dan mana fungsi decorator.
â—‰ Tahap 3: Decorator dipergunakan
Selanjutnya, dua buah fungsi baru dibuat yang masing-masing memakai
decorator yang telah dipersiapkan:
• Fungsi generate_random_unique_list() ditempeli decorator
@decorator_unique_list , membuat data nilai balik fungsi ini diteruskan ke
proses pencarian list berisi elemen unik.
Fungsi generate_random_unique_list() menghasilkan proses yang
ekuivalen dengan kode berikut:
@decorator_unique_list
def generate_random_unique_list(length):
return generate_random_list(length)
print(generate_random_unique_list(15))
# output ➜ [0, 3, 4, 5, 6, 7, 8, 9, 10]
• Fungsi generate_random_reverse_sorted_list() ditempeli decorator
@decorator_reverse_list , membuat data nilai balik fungsi ini diteruskan
ke proses perubahan pengurutan elemen menjadi terbalik.
Fungsi generate_random_reverse_sorted_list() menghasilkan proses
yang ekuivalen dengan kode berikut:
A.40.4. Chaining decorator
Chaining decorator yaitu istilah untuk penerapan lebih dari satu decorator
pada sebuah fungsi. Pada contoh di atas, fungsi ditempeli hanya satu decorator
saja. Pada praktiknya, fungsi bisa saja memakai lebih dari 1 decorator.
Misalnya pada program berikut, kita buat fungsi baru yang memakai
decorator @decorator_unique_list dan juga @decorator_reverse_list .
data = generate_random_list(length)
res = unique_list(data)
print(res)
@decorator_reverse_list
def generate_random_reverse_sorted_list(length):
return generate_random_list(length)
print(generate_random_reverse_sorted_list(15))
# output ➜ [10, 10, 10, 9, 8, 8, 8, 8, 8, 7, 4, 4, 2, 0, 0]
data = generate_random_list(length)
res = reverse_list(data)
print(res)
Output eksekusi program:
def decorator_unique_list(func):
def execute(length):
print("decorator_unique_list | before", func)
data = func(length)
print("decorator_unique_list | after")
s = set(data)
r = list(s)
return r
return execute
def decorator_reverse_list(func):
def execute(length):
print("decorator_reverse_list | before", func)
data = func(length)
print("decorator_reverse_list | after")
data.sort(reverse=True)
return data
return execute
@decorator_reverse_list
@decorator_unique_list
def generate_random_unique_reverse_sorted_list(length):
return generate_random_list(length)
print("result:", generate_random_unique_reverse_sorted_list(15))
Bisa dilihat, data list yang dihasilkan yaitu unik dan urutannya terbalik,
menandakan dua decorator yang kita pasang ke fungsi
generate_random_unique_reverse_sorted_list() bekerja dengan baik.
Pada chaining decorator, urutan eksekusi fungsi decorator yaitu dari yang
paling bawah kemudian ke atas. Ilustrasi eksekusi fungsi dan decorator pada
contoh yang telah dipraktikan kurang lebih ekuivalen dengan kode di bawah ini:
A.40.5. *args & **kwargs pada decorator
Idealnya, sebuah decorator dibuat dengan desain parameter se-fleksibel
mungkin, karena bisa saja decorator diterapkan pada fungsi dengan berbagai
macam skema parameter.
Pada contoh yang telah dipraktekan, closure nilai balik fungsi decorator
memiliki parameter yang sangat spesifik, yaitu length . Dari sini berarti
decorator tersebut hanya bisa digunakan pada fungsi yang parameternya
sesuai.
Ada tips atau best practice dalam mendesain fungsi decorator. Gunakan
parameter *args & **kwargs pada deklarasi inner function, kemudian saat
memanggil func lakukan operasi unpacking. Dengan ini parameter apapun
data =
decorator_reverse_list(decorator_unique_list(generate_random_list(length)))
print(data)
# ... atau ...
data1 = generate_random_list(length)
data2 = decorator_unique_list(data1)
data3 = decorator_reverse_list(data2)
print(data3)
yang disisipkan di fungsi yang ditempeli decorator, akan di-pass ke decorator
sesuai dengan aslinya.
Pembahasan detail mengenai teknik unpacking ada di chapter Packing &
Unpacking
Source code final:
import random
def generate_random_list(length):
r = []
for _ in range(0, length):
n = random.randint(0, 10)
r.append(n)
return r
def decorator_unique_list(func):
def execute(*args, **kwargs):
data = func(*args, **kwargs)
s = set(data)
r = list(s)
return r
return execute
def decorator_reverse_list(func):
def execute(*args, **kwargs):
data = func(*args, **kwargs)
data.sort(reverse=True)
return data
return execute
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../decorator
â—‰ Chapter relevan lainnya
• Function ➜ Positional, Optional, Keyword Arguments
• OOP ➜ Class Method
• OOP ➜ Static Method
• OOP ➜ Abstract Method
• OOP ➜ Data Class
â—‰ Referensi
• https://peps.python.org/pep-0318/
• https://peps.python.org/pep-0448/
• https://python101.pythonlibrary.org/chapter25_decorators.html
• https://stackoverflow.com/questions/6392739/what-does-the-at-symbol-do-
in-python
A.41. Python OOP ➜ Class
Inheritance
Chapter ini membahas tentang salah satu aspek penting dalam pemrograman
OOP, yaitu inheritance atau pewarisan sifat, dimana sifat yang dimaksud
yaitu property seperti attribute, method, dan lainnya.
A.41.1. Pengenalan Inheritance
Untuk mewujudkan inheritance setidaknya dua buah class dibutuhkan:
• Super class / parent class, yaitu class yang property-nya ingin diwariskan
atau diturunkan ke class dibawahnya.
• Sub class / derrived class, yaitu class yang mewarisi property dari parent
class.
Misalkan, ada sebuah class bernama Vehicle dan class ini memiliki property
berikut:
1. Constructor
2. Class attribute note
3. Instance attribute name
4. Instance attribute number_of_wheels
5. Instance method drive_sound()
Class Vehicle kemudian dijadikan sebagai parent class pada class
ElectricCar .
Class ElectricCar kita desain tidak memiliki attribute. Namun karena dia
merupakan sub class dari Vehicle maka secara implicit mewarisi semua
property yang ada di class Vehicle . Maka via object bertipe class
ElectricCar nantinya kita bisa mengakses property class Vehicle .
Class ElectricCar memiliki satu buah method bernama info() yang isinya
yaitu menampilkan data property yang diwarisi oleh class Vehicle
Ilustrasi diagram UML-nya seperti ini:
Dari gambar di atas, secara teori, object yang dibuat dari class ElectricCar
bisa mengakses property class itu sendiri serta property lain yang diwarisi
super class.
Sedangkan object dari class Vehicle hanya bisa mengakses property class itu
sendiri saja.
Ok, sekarang mari kita terapkan skenario di atas di Python. Definisikan class
Vehicle dan class ElectricCar beserta isi masing-masing property.
Pada deklarasi class ElectricCar penulisannya ada yang unik. Nama class
ditulis dengan notasi ElectricCar(Vehicle) yang artinya yaitu class
ElectricCar dideklarasikan sebagai sub class class Vehicle .
INFO
Notasi umum penulisan inheritance kurang lebih seperti ini:
Dari dua class yang telah dideklarasikan, selanjutnya buat beberapa instance
object lalu akses property-nya. Setelah itu coba run program dan lihat
outputnya.
class Vehicle:
note = "class to represent a car"
def __init__(self):
self.name = "common vehicle"
self.number_of_wheels = 4
def drive_sound(self):
return "vroom vroooommmm"
class ElectricCar(Vehicle):
def info(self):
print(self.name, "has", self.number_of_wheels, "wheels. engine
sound:", self.drive_sound())
class SuperClass:
pass
class SubClass(SuperClass):
pass
Bisa dilihat dari contoh, bahwa property milik class Vehicle bisa diakses via
instance object yang dibuat dari class itu sendiri maupun dari object yang
dibuat dari subclass ElectricCar .
A.41.2. Class object inheritance
Python memiliki class bawaan bernama object yang pada praktiknya
otomatis menjadi super class dari semua class bawaan Python maupun custom
class yang kita buat sendiri.
Contohnya class Vehicle dan ElectricCar yang telah dibuat, kedua class
tersebut otomatis juga menjadi sub class dari class object ini.
Untuk membuktikan, silakan test saja memakai kombinasi seleksi kondisi
dan fungsi isinstance() .
v1 = Vehicle()
print(v1.name, "has", v1.number_of_wheels, "wheels. engine sound:",
v1.drive_sound())
# output ➜ common vehicle has 4 wheels. engine sound: vroom vroooommmm
v2 = ElectricCar()
v2.name = "electric car"
print(v2.name, "has", v2.number_of_wheels, "wheels. engine sound:",
v2.drive_sound())
# output ➜ electric car has 4 wheels. engine sound: vroom vroooommmm
v3 = ElectricCar()
v3.name = "electric car"
v3.info()
# output ➜ electric car has 4 wheels. engine sound: vroom vroooommmm
class Vehicle:
A.41.3. Constructor overriding
Overriding yaitu istilah pemrograman OOP untuk menimpa/mengganti suatu
method dengan method baru yang nama dan strukturnya sama tapi isinya
berbeda.
Pada section ini, teknik overriding kita akan terapkan pada constructor.
Constructor Vehicle yang secara implicit diwariskan ke class ElectricCar ,
di sub class-nya kita replace dengan constructor baru. Silakan pelajari kode
berikut agar lebih jelas:
class Vehicle:
note = "class to represent a car"
def __init__(self):
self.name = "common vehicle"
self.number_of_wheels = 4
def drive_sound(self):
return "vroom vroooommmm"
class ElectricCar(Vehicle):
def __init__(self):
self.name = "electric car"
def info(self):
print(self.name, "has", self.number_of_wheels, "wheels. engine
sound:", self.drive_sound())
v1 = Vehicle()
print(v1.name, "has", v1.number_of_wheels, "wheels. engine sound:",
v1.drive_sound())
v2 = ElectricCar()
Perbedaan kode ini dibanding sebelumnya ada di bagian constructor class
ElectricCar . Disitu untuk setiap object baru yang dibuat, nilai attribute
name -nya diisi dengan string electric car .
Secara teori, idealnya program di atas bisa jalan normal. Maka mari coba run
saja dan lihat hasilnya:
Oops! Statement pengaksesan property object v1 berjalan normal, namun
error muncul pada statement print ke-2 dimana property object v2 diakses.
Pesan errornya kurang lebih menginformasikan bahwa class ElectricCar
tidak memiliki attribute number_of_wheels . Aneh, padahal secara teori
property tersebut diwariskan oleh super class yaitu Vehicle , namun setelah
ditambahkan kode constructor baru yang meng-override constructor parent
class, programnya malah error.
Perlu diketahui bahwa penerapan operasi override mengakibatkan kode pada
super class benar-benar dihapus dan diganti dengan kode baru. Pada contoh
yang sudah ditulis, di constructor Vehicle ada dua buah property
dideklarasikan, yaitu name dan number_of_wheels . Sedangkan pada class
ElectricCar , hanya property name dideklarasikan.
Constructor baru milik class ElectricCar menimpa constructor milik super
class-nya. Dan pada constructor baru ini property number_of_wheels tidak
dideklarasikan. Efeknya, property tersebut menjadi tidak ada, menyebabkan
pesan error seperti berikut:
Solusi permasalahan di atas ada pada penjelasan section berikut ini.
A.41.4. Fungsi super()
Fungsi super() yaitu salah satu fungsi istimewa bawaan python, yang
ketika diakses di dalam suatu instance method maka pemanggilannya
mengarah ke variabel self milik super class (bukan variabel self milik
class itu sendiri).
Misalnya statement super() ditulis pada constructor class ElectricCar ,
maka dari fungsi tersebut kita mendapatkan akses ke object self milik super
class yaitu class Vehicle . Kemudian dari object self , property super class
class Vehicle:
def __init__(self):
self.name = "common vehicle"
self.number_of_wheels = 4
# ... vs ...
class ElectricCar(Vehicle):
def __init__(self):
self.name = "electric car"
AttributeError: 'ElectricCar' object has no attribute
'number_of_wheels'
bisa diakses dengan mudah. Termasuk constructor super class juga bisa
diakses.
Ok, sekarang mari coba tambahkan statement super() pada constructor
ElectricCar , lalu dari nilai balik fungsi, chain lagi dengan mengakses
constructor __init__() milik super class.
Terapkan perubahan tersebut lalu jalankan ulang program. Sekarang error
tidak akan muncul.
class Vehicle:
note = "class to represent a car"
def __init__(self):
self.name = "common vehicle"
self.number_of_wheels = 4
def drive_sound(self):
return "vroom vroooommmm"
class ElectricCar(Vehicle):
def __init__(self):
super().__init__()
self.name = "electric car"
def info(self):
print(self.name, "has", self.number_of_wheels, "wheels. engine
sound:", self.drive_sound())
v1 = Vehicle()
print(v1.name, "has", v1.number_of_wheels, "wheels. engine sound:",
v1.drive_sound())
# output ➜ common vehicle has 4 wheels. engine sound: vroom vroooommmm
v2 = ElectricCar()
print(v2.name, "has", v2.number_of_wheels, "wheels. engine sound:",
v2.drive_sound())
Jika dianalogikan, bisa dibilang kode di atas yaitu ekuivalen dengan kode ke-2
berikut:
• Kode setelah perubahan:
• Ekuivalen dengan kode berikut:
Sampai sini semoga cukup jelas.
class Vehicle:
def __init__(self):
self.name = "common vehicle"
self.number_of_wheels = 4
class ElectricCar(Vehicle):
def __init__(self):
super().__init__()
self.name = "electric car"
class Vehicle:
def __init__(self):
self.name = "common vehicle"
self.number_of_wheels = 4
class ElectricCar(Vehicle):
def __init__(self):
# statement berikut terpanggil dari __init__() milik super
class
self.name = "common vehicle"
self.number_of_wheels = 4
# kemudian statement berikut dieksekusi setelahnya
self.name = "electric car"
A.41.5. Alternatif cara mengakses super
class constructor
Selain memakai super().__init__() ada cara lain untuk memanggil
konstruktor super class, yaitu dengan mengakses method __init__() via
class secara langsung. Contoh:
Statement Vehicle.__init__(self) pada kode di atas yaitu ekuivalen
dengan kode super().__init__() pada program sebelumnya.
Teknik pemanggilan constructor via class ini lebih sering digunakan pada class
yang memiliki parent class lebih dari satu. Lebih jelasnya akan kita bahas di
bawah.
class Vehicle:
note = "class to represent a car"
def __init__(self):
self.name = "common vehicle"
self.number_of_wheels = 4
def drive_sound(self):
return "vroom vroooommmm"
class ElectricCar(Vehicle):
def __init__(self):
Vehicle.__init__(self)
self.name = "electric car"
def info(self):
print(self.name, "has", self.number_of_wheels, "wheels. engine
sound:", self.drive_sound())
A.41.6. Method overriding
Tidak hanya constructor, method super class juga bisa di-override dengan
method baru. Pada kode berikut, method drive_sound() di-override dengan
isi mengembalikan nilai string berbeda, yang sebelumnya vroom vroooommmm
kini menjadi zzzzzzz .
Coba aplikasikan perubahan berikut lalu run ulang programnya.
class Vehicle:
note = "class to represent a car"
def __init__(self):
self.name = "common vehicle"
self.number_of_wheels = 4
def drive_sound(self):
return "vroom vroooommmm"
class ElectricCar(Vehicle):
def __init__(self):
super().__init__()
self.name = "electric car"
def info(self):
print(self.name, "has", self.number_of_wheels, "wheels. engine
sound:", self.drive_sound())
def drive_sound(self):
return "zzzzzzz"
v1 = Vehicle()
print(v1.name, "has", v1.number_of_wheels, "wheels. engine sound:",
v1.drive_sound())
# output ➜ common vehicle has 4 wheels. engine sound: vroom vroooommmm
Bisa dilihat pada statement ke-2, sekarang bunyi mesin berubah menjadi
zzzzzzz .
Dalam konteks inheritance, ketika di sub class terdapat method dengan nama
yang sama persis dengan super class, maka pemanggilan method tersebut
dari object yang dibuat via sub class yaitu mengarah ke method yang ada di
sub class. Konsep ini disebut dengan method overriding.
• Object v1 dibuat via class Vehicle , pengaksesan method
drive_sound() mengarah ke method milik class tersebut
• Object v2 dibuat via class ElectricCar , pengaksesan method
drive_sound() (yang dilakukan secara implisit via method info() )
mengarah ke method milik class tersebut, dan bukan milik super class.
A.41.7. Aturan overriding
Setiap bahasa pemrograman yang mengadopsi OOP, aturan penerapan
method overriding berbeda satu sama lain. Di Python sendiri, method
dianggap meng-override suatu method atau constructor super class jika
namanya yaitu dideklarasikan sama persis. Perihal apakah skema parameter-
nya diubah, atau return type-nya diubah, itu tidak menjadi syarat wajib
overriding.
Agar lebih jelas silakan lihat dan pelajari kode berikut:
class Vehicle:
note = "class to represent a car"
def __init__(self):
self.name = "common vehicle"
self.number_of_wheels = 4
Method drive_sound() di-override dengan diubah skema parameternya, dari
yang tidak memiliki parameter sekarang menjadi memiliki parameter sound .
Selain itu tipe datanya juga diubah, dari yang sebelumnya string menjadi
tuple.
A.41.8. Nested inheritance
Penerapan inheritance tidak hanya terbatas pada dua buah class saja,
melainkan bisa lebih. Class bisa diturunkan, kemudian turunannya diturunkan
lagi, dan seterusnya.
Contoh pengaplikasiannya bisa dilihat pada kode berikut dimana ada class
Vehicle , Car , dan ElectricCar ; yang ketiganya menjalin hubungan
inheritance dengan hirarki seperti ini:
Source code implementasi:
class Vehicle:
note = "class to represent a car"
def __init__(self, name = "common vehicle", number_of_wheels = 4):
self.name = name
self.number_of_wheels = number_of_wheels
def drive_sound(self):
return "vroom vroooommmm"
class Car(Vehicle):
pass
class ElectricCar(Car):
A.41.9. Special name ➜ class attribute__mro__
Setiap class memiliki class attribute __mro__ yang berisi informasi hirarki
class itu sendiri. Attribute tersebut bertipe data tuple. Dari nilai balik attribute
tersebut gunakan perulangan untuk mengiterasi seluruh elemennya.
print("hirarki class ElectricCar:")
for cls in ElectricCar.__mro__:
print(f"➜ {cls.__name__}")
print("hirarki class Car:")
for cls in Car.__mro__:
print(f"➜ {cls.__name__}")
print("hirarki class Vehicle:")
for cls in Vehicle.__mro__:
print(f"➜ {cls.__name__}")
# output ↓
#
# hirarki class ElectricCar:
# ➜ ElectricCar
# ➜ Car
# ➜ Vehicle
# ➜ object
#
# hirarki class Car:
# ➜ Car
# ➜ Vehicle
# ➜ object
#
# hirarki class Vehicle:
# ➜ Vehicle
Hirarki paling atas semua class selalu class object .
MRO sendiri merupakan kependekan dari istilah Method Resolution
Order
A.41.10. Multiple inheritance
Suatu class tidak dibatasi hanya bisa menjadi sub class dari 1 buah class saja.
Bisa jadi ada lebih dari 1 class yang diturunkan dengan level hirarki yang
sama.
Sebagai contoh kita buat penerapan inheritance dengan hirarki seperti
diagram berikut:
Source code:
class Vehicle:
note = "class to represent a car"
def __init__(self, name = "common vehicle", number_of_wheels = 4):
self.name = name
self.number_of_wheels = number_of_wheels
from typing import Final
ENGINE_ELECTRIC: Final = "electric engine"
ENGINE_PETROL: Final = "petrol engine"
ENGINE_DIESEL: Final = "diesel engine"
class Engine:
note = "class to represent engine"
def __init__(self, engine_name):
self.engine_name = engine_name
def drive_sound(self):
if self.engine_name == ENGINE_ELECTRIC:
return "zzzzzzz"
elif self.engine_name == ENGINE_PETROL:
return "vroom vroooommmm"
elif self.engine_name == ENGINE_DIESEL:
return "VROOM VROOM VROOOOMMM"
class ElectricCar(Vehicle, Engine):
def __init__(self):
Vehicle.__init__(self, "electric car", 4)
Engine.__init__(self, ENGINE_ELECTRIC)
def info(self):
print(self.name, "has", self.number_of_wheels, "wheels. engine
sound:", self.drive_sound())
Khusus untuk penerapan inheritance dengan lebih dari 1 super class,
dianjurkan untuk tidak memakai fungsi super() untuk mengakses self
milik parent class, karena self disitu mengarah ke object self milik super
class urutan pertama (yang pada contoh yaitu class Vehicle ).
Dianjurkan untuk memanggil constructor super class secara langsung via
ClassName.__init__() sesuai kebutuhan. Contohnya bisa dilihat di kode di
atas, Vehicle.__init__() dan Engine.__init__() keduanya diakses pada
constructor class ElectricCar .
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../class-
inheritance
â—‰ Chapter relevan lainnya
• OOP ➜ Class & Object
• OOP ➜ Abstract Method
• OOP ➜ Data Class
â—‰ Referensi
• https://docs.python.org/3/tutorial/classes.html#inheritance
• https://docs.python.org/3/library/stdtypes.html#class.__mro__
• https://en.wikipedia.org/wiki/Unified_Modeling_Language
A.42. Python OOP ➜
Abstract Method
Pada chapter ini kita akan mempelejari tentang apa itu abstract method
beserta bagaimana penerapannya pada pemrograman OOP memakai
Python.
A.42.1. Pengenalan abstract method
Abstract method merupakan jenis method yang dideklarasikan dengan isi tidak
melakukan apa-apa, hanya statement pass . Nantinya ketika class (dimana
method tersebut berada) di-inherit ke sub class lain, maka sub-class harus
meng-override method milik super class tersebut.
Abstract method umum digunakan pada situasi dimana ada beberapa class
yang memiliki method yang sama, namun isinya berbeda satu sama lain. Agar
seragam, maka beberapa class tersebut harus menjadi sub class dari sebuah
super class yang sama. Super class sendiri berisi abstract method, dibuat
sebagai acuan spesifikasi untuk sub class.
Pada pemrograman secara umum, fungsi tanpa isi biasa disebut dengan
header function
Agar lebih mudah untuk memahami konsep dan penerapan abstract method,
kita akan mulai pembelajaran dengan praktek tanpa penerapan abstract
method terlebih dahulu.
Ok, siapkan sebuah class bernama Object2D dengan isi satu buah method
bernama calculate_area() . Kemudian class tersebut diturunkan ke dua class
baru bernama Triangle dan Circle .
Class Triangle dan Circle keduanya memiliki bentuk implementasi
calculate_area() berbeda satu sama lain karena memang secara aturan
rumus perhitungan luas segitiga dan lingkaran yaitu berbeda.
Alasan kenapa ada deklarasi method calculate_area() di parent class
yaitu agar sub class Triange dan Circle memiliki method
calculate_area() dengan skema seragam.
Berikut yaitu source code-nya:
class Object2D:
def calculate_area(self):
pass
class Triangle(Object2D):
def __init__(self, b, h):
self.b = b
self.h = h
def calculate_area(self):
return 1/2 * self.b * self.h
class Circle(Object2D):
def __init__(self, r):
self.r = r
def calculate_area(self):
return 3.14 * self.r * self.r
obj1 = Triangle(4, 10)
area = obj1.calculate_area()
print(f"area of {type(obj1).__name__}: {area}")
# output ➜ area of Triangle: 20.0
Kode di atas berjalan normal sesuai harapan, namun memiliki kekurangan,
yaitu ketika class Object2D diturunkan ke suatu class, bisa saja sub class
tidak meng-override method calculate_area() .
Contoh, pada kode berikut dibuat class baru bernama Square yang tidak
meng-override method calculate_area() :
Output:
Kode di atas ketika di-run tidak menghasilkan error, berjalan normal, hanya
saja outputnya tidak sesuai harapan karena class Square tidak mempunyai
method calculate_area() . Tanpa adanya method tersebut, maka
pemanggilan calculate_area() mengarah ke method super class yang isinya
mengembalikan nilai None .
Di real life, ukuran source code yang kita maintain bisa saja berisi ratusan atau
bahkan puluhan ribu baris dengan jumlah file sangat banyak. Di case yang
seperti itu cukup susah mengecek mana class yang implementasinya sudah
sesuai spesifikasi dan mana yang belum, karena saat program dijalankan tidak
class Square(Object2D):
def __init__(self, s):
self.s = s
obj3 = Square(6)
area = obj3.calculate_area()
print(f"area of {type(obj3).__name__}: {area}")
# output ➜ area of Square: None
ada error atau warning. Untuk mengatasi masalah tersebut, solusinya yaitu
dengan mengimplementasikan abstract method.
A.42.2. Praktek abstract method
Di Python versi 3.4+, suatu method menjadi abstract method ketika memenuhi
kriteria berikut:
• Super class meng-inherit class bawaan Python bernama ABC milik module
abc .
• Method yang dijadikan acuan (yang nantinya wajib di-override) perlu
ditambahi decorator @abstractmethod milik module abc .
ABC merupakan kependekan dari Abstract Base Class, sebuah module
bawaan Python Standard Library yang berisi banyak property untuk
keperluan abstraction.
Sekarang, aplikasikan 2 hal di atas ke kode yang telah ditulis. Kurang lebih
hasil akhirnya seperti ini. Perbedaannya ada pada deklarasi class Object2D
dan deklarasi method calculate_area() .
from abc import ABC, abstractmethod
class Object2D(ABC):
@abstractmethod
def calculate_area(self):
pass
class Triangle(Object2D):
def __init__(self, b, h):
self.b = b
Selanjutnya, coba jalankan, pasti muncul error karena class Square tidak
berisi implementasi method calculate_area() .
Untuk memperbaiki error, override method calculate_area() milik class
Square agar sesuai spesifikasi.
Output program setelah diperbaiki:
class Square(Object2D):
def __init__(self, s):
self.s = s
def calculate_area(self):
return self.s * self.s
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../abstract-
class
â—‰ Chapter relevan lainnya
• OOP ➜ Class & Object
• OOP ➜ Instance Method
• OOP ➜ Constructor
• OOP ➜ Class Method
• OOP ➜ Static Method
• Function ➜ Decorator
• OOP ➜ Class Inheritance
â—‰ Referensi
• https://docs.python.org/3/library/abc.html
• https://stackoverflow.com/questions/13646245/is-it-possible-to-make-
abstract-classes-in-python/13646263#13646263
A.43. Python OOP ➜
DataClass
Data class yaitu class yang didesain khusus untuk tujuan penyimpanan data.
Python menyediakan module bernama dataclasses dengan isi beberapa API
untuk mempermudah pembuatan data class. Pada chapter ini kita akan
mempelajarinya.
A.43.1. Pengenalan Data Class
Di bawah ini dicontohkan class bernama Planet disiapkan untuk pembuatan
data berisi informasi planet. Class ini memiliki 3 buah instance attribute, yaitu
name , diameter , dan natural_sattelites . Deklarasi attribute ditulis dalam
fungsi __init__() .
class Planet:
def __init__(self, name, diameter, natural_sattelites):
self.name = name
self.diameter = diameter
self.natural_sattelites = natural_sattelites
planets = [
Planet("Mercury", 4879, []),
Planet(name="Venus", diameter=12104, natural_sattelites=[]),
Planet(diameter=12742, name="Earth", natural_sattelites=["Moon"]),
]
for p in planets:
print(f"{p.name} | {p.diameter} km | {len(p.natural_sattelites)}
moons")
Contoh di atas menurut penulis cukup jelas dan mudah dipahami karena jika
pembaca mengikuti pembahasan secara urut, maka sudah familiar dengan
class, attributes, dan object.
Tidak semua class dibuat untuk keperluan operasi terhadap data, ada yang
didesain sebagai utilitas, wrapper, atau lainnya. Khusus untuk class yang
memang disiapkan untuk pengelolahan data, penulisannya akan lebih praktis
memakai decorator @dataclass milik module dataclasses .
Class Planet yang telah dibuat di atas, deklarasinya diubah memakai
dataclass, hasilnya kurang lebih seperti ini:
from dataclasses import dataclass
@dataclass
class Planet:
name: str
diameter: float
natural_sattelites: list[str]
planets = [
Planet("Mercury", 4879, []),
Planet("Venus", 12104, []),
Planet("Earth", 12742, ["Moon"]),
]
for p in planets:
print(f"{p.name} | {p.diameter} km | {len(p.natural_sattelites)}
moons")
# output ↓
#
# Mercury | 4879 km | 0 moons
# Venus | 12104 km | 0 moons
# Earth | 12742 km | 1 moons
Cukup tambahkan decorator @dataclass saat deklarasi class, lalu tulis
attribute seperti penulisan class attribute, tak lupa tentukan tipe data tiap-tiap
attribute. Dengan itu maka class otomatis menjadi data class, attribute-nya
menjadi instance attribute dan bisa langsung diisi nilainya (via argument
constructor) saat pembuatan object.
• Penulisan data class
• Penulisan class biasa
A.43.2. Attribute mutability
Selayaknya seperti class biasa, instance attribute dataclass yaitu mutable
atau bisa diubah nilainya. Contoh penerapan:
from dataclasses import dataclass
@dataclass
class Planet:
name: str
diameter: float
natural_sattelites: list[str]
class Planet:
def __init__(self, name, diameter, natural_sattelites):
self.name = name
self.diameter = diameter
self.natural_sattelites = natural_sattelites
mars = Planet("Mars", 4, ["Phobos", "Deimos"])
mars.name = "Red Planet"
mars.diameter = 6779
A.43.3. Instance method
Data class bisa memiliki instance method dengan penulisan deklarasi sama
persis seperti deklarasi method pada umumnya. Contohnya bisa dilihat di
bawah ini, dimana ada data class bernama Country berisi 3 buah instance
attribute dan satu buah instance method bernama info() .
@dataclass
class Country:
name: str
seasons: list
number_of_populations: float
def info(self) -> str:
return f"{self.name} | {len(self.seasons)} seasons |
{self.number_of_populations} million population"
countries = [
Country("Indonesia", ["Rainy", "Dry"], 275.5),
Country("Palestine", ["Winter", "Summer", "Autumn", "Spring"], 5.044),
Country("Mongolia", ["Winter", "Summer", "Autumn", "Spring"], 3.398),
]
for c in countries:
print(c.info())
# output ↓
#
# Indonesia | 2 seasons | 275.5 million population
# Palestine | 4 seasons | 5.044 million population
# Mongolia | 4 seasons | 3.398 million population
A.43.4. Attribute default value
Attribute data class bisa ditentukan nilai defaultnya memakai operator
assignment = (penulisannya seperti deklarasi variabel). Dengan memberikan
nilai default pada attribute, menjadikan parameter konstruktor menjadi
opsional. Contoh:
A.43.5. Frozen attribute
Frozen data class yaitu data class yang immutable, artinya setelah
dideklarasikan maka tidak bisa diubah nilai attribute-nya. Cara meng-enable
frozen attribute yaitu dengan menambahkan frozen=True pada decorator
@dataclass .
@dataclass
class Country:
name = "Indonesia"
seasons = ["Rainy", "Dry"]
number_of_populations = 275.5
def info(self) -> str:
return f"{self.name} | {len(self.seasons)} seasons |
{self.number_of_populations} million population"
c = Country()
print(c.info())
# output ➜ Indonesia | 2 seasons | 275.5 million population
from dataclasses import dataclass
@dataclass(frozen=True)
Kode di atas menghasilkan error karena semua attribute class Fruit
immutable.
A.43.6. Inheritance
Data class bisa diturunkan seperti umumnya class dengan cara penulisan
masih sama. Contoh:
from dataclasses import dataclass
@dataclass
class Animal:
name: str
@dataclass
class Bird(Animal):
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../dataclass
â—‰ Chapter relevan lainnya
• OOP ➜ Class & Object
• OOP ➜ Instance Method
• OOP ➜ Constructor
• OOP ➜ Instance Attribute & Class Attribute
• OOP ➜ Class Inheritance
• Function ➜ Decorator
â—‰ Referensi
• https://docs.python.org/3/library/dataclasses.html
A.44. Python Duck Typing
vs Structural Typing
Pada chapter ini kita akan belajar salah satu konsep yang ada di bahasa
pemrograman dinamis, yaitu duck typing, beserta perbandingannya dengan
structural typing
A.44.1. Duck typing
Istilah duck typing berasal dari kalimat If it looks like a duck and quacks like a
duck, it's a duck.
Duck typing yaitu konsep yang menjelaskan bahwa compiler/interpreter tidak
perlu tau apakah suatu fungsi itu merupakan method, lambda, atau berasal
dari class tertentu, atau apapun lainnya; selama fungsi tersebut saat diakses
memenuhi kriteria (nama dan skema parameter-nya sama) maka fungsi
dianggap valid secara logika.
Mari kita praktekan agar lebih jelas maksudnya apa. Pertama, siapkan sebuah
fungsi bernama do_the_math() . Tugas fungsi ini sangat sederhana, yaitu
menerima parameter obj , kemudian lewat variabel tersebut method
calculate_area() diakses.
Selanjutnya yaitu bagian terpenting dari pembelajaran di chapter ini, fungsi
yang sudah dibuat akan di test memakai beberapa skenario.
def do_the_math(obj):
area = obj.calculate_area()
print(f"area of {type(obj).__name__}: {area}")
â—‰ Skenario 1: Instance method
Buat class baru untuk operasi perhitungan luas segitiga. Operasi
perhitungannya disiapkan di instance method bernama calculate_area() .
Dari sini, object buatan class ini harusnya bisa dipergunakan sebagai argument
fungsi do_the_math() .
Hasilnya: OK ✅
â—‰ Skenario 2: Attribute berisi closure
Berikutnya, siapkan class baru lagi dengan attribute bernama
calculate_area , lalu isi nilai attribute tersebut dengan closure. Disini
dicontohkan closure-nya yaitu fungsi number_10() yang tugasnya
mengembalikan nilai numerik 10 .
class Triangle:
def __init__(self, b, h):
self.b = b
self.h = h
def calculate_area(self):
return 1/2 * self.b * self.h
obj1 = Triangle(4, 10)
do_the_math(obj1)
# output ➜ area of Triangle: 20.0
def number_10():
return 10
class AreaOf2x10:
Hasilnya: OK ✅
Fungsi do_the_math() berjalan sesuai harapan tanpa melihat tipe data dan
struktur dari argument-nya seperti apa. Selama class memiliki property
bernama calculate_area dan bisa diakses dalam bentuk notasi fungsi, maka
bukan masalah.
â—‰ Skenario 3: Attribute berisi lambda
Pada skenario ini, sebuah class bernama AreaOfRandomInt dibuat disertai
dengan attribute bernama calculate_area yang berisi operasi perkalian
angka random yang ditulis dalam syntax lambda.
Hasilnya: OK ✅
â—‰ Skenario 4: Class method
Bisa dibilang skenario ini yang paling unik. Buat sebuah class baru berisi class
method calculate_area() . Lalu jadikan class tersebut sebagai argument
pemanggilan fungsi do_the_math() . Jadi disini kita tidak memakai
instance object sama sekali.
import random
class AreaOfRandomInt:
def __init__(self) -> None:
self.calculate_area = lambda : random.randint(0, 10) * 2
obj3 = AreaOfRandomInt()
do_the_math(obj3)
# output ➜ 16
Hasilnya: OK ✅
Fungsi do_the_math() tetap bisa menjalankan tugasnya dengan baik, bahkan
untuk argument yang bukan instance object sekalipun. Selama argument
memiliki fungsi calculate_area() maka semuanya aman terkendali.
A.44.2. Structural typing
Structural typing bisa diibaratkan sebagai duck typing tapi versi yang lebih
ketat. Structural typing mengharuskan suatu fungsi atau method untuk
memilki spesifikasi yang sama persis sesuai yang dideklarasikan. Misalnya ada
suatu object berisi method dengan hanya nama fungsi dan skema
parameternya saja yang sama dibanding yang dibutuhkan, maka itu tidak
cukup dan error pasti muncul.
Cara penerapan structural typing yaitu dengan menentukan tipe data
parameter secara explicit. Mari coba praktekan via kode berikut agar lebih
jelas.
Pertama, siapkan sebuah class bernama Object2D yang memiliki abstract
method calculate_area() . Lalu buat juga fungsi do_the_math() tapi kali ini
argument nya bertipe data Object2D .
class NotReallyA2dObject:
@classmethod
def calculate_area(cls):
return "where is the number?"
do_the_math(NotReallyA2dObject)
# output ➜ where is the number?
Dari sini terlihat bahwa untuk bisa memakai fungsi do_the_math() data
argument harus bertipe Object2D atau class turunannya. Inilah bagaimana
structural typing diaplikasikan di Python.
Selanjutnya, buat class implementasinya, tak lupa panggil fungsi
do_the_math() , dan isi argument-nya memakai instance object. Jalankan
program, hasilnya tidak akan error, karena saat pemanggilan fungsi
do_the_math() argument yang disisipkan tipe datanya sesuai spesifikasi,
yaitu bertipe Object2D atau class turunannya.
from abc import ABC, abstractmethod
class Object2D(ABC):
@abstractmethod
def calculate_area(self):
pass
def do_the_math(obj: Object2D):
area = obj.calculate_area()
print(f"area of {type(obj).__name__}: {area}")
class Triangle(Object2D):
def __init__(self, b, h):
self.b = b
self.h = h
def calculate_area(self):
return 1/2 * self.b * self.h
class Circle(Object2D):
def __init__(self, r):
self.r = r
def calculate_area(self):
return 3.14 * self.r * self.r
Selanjutnya, coba test fungsi do_the_math() memakai argument berisi
data yang bukan bertipe Object2D dan juga bukan turunannya.
Silakan cek di editor masing-masing, pada statement do_the_math() terlihat
ada warning.
Python merupakan bahasa pemrograman dinamis yang dukungan
terhadap structural typing tidak terlalu bagus. Keterangan tidak valid
pada gambar di atas hanyalah warning, tidak benar-benar error. Kode
program sendiri tetap bisa dijalankan.
class NotReallyA2dObject:
@classmethod
def calculate_area(cls):
return "where is the number?"
do_the_math(NotReallyA2dObject)
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../duck-
typing-vs-structural-typing
â—‰ Chapter relevan lainnya
• Tipe Data
• OOP ➜ Abstract Method
â—‰ Referensi
• https://stackoverflow.com/questions/4205130/what-is-duck-typing
• https://docs.python.org/3/glossary.html#term-duck-typing
A.45. Python Pattern
Matching ➜ match
Pada chapter ini, kita akan membahas tentang teknik seleksi kondisi
pencocokan pola di Python yang cukup advance untuk mengenali banyak
variasi pola, yaitu structural pattern matching.
A.45.1. Pengenalan pattern matching
Pattern matching merupakan teknik pencocokan pola memakai kombinasi
keyword match dan case . Penggunaan dasarnya mirip seperti seleksi kondisi
memakai keyword if .
Silakan lihat 2 kode berikut untuk mengetahui perbedaan syntax-nya:
• Kode seleksi kondisi:
command = input("Enter command: ")
if command == "greet":
name = input("Your name: ")
print("hello", name)
elif command == "find sum of numbers":
numbers = input("The numbers separated by space: ")
total = 0
for str in numbers.split(' '):
total = total + int(str)
print("total:", total)
elif command == "lucky number":
• Kode yang sama dengan penerapan pattern matching:
Kode diatas ketika di-run akan meminta sebuah inputan ke user untuk
kemudian disimpan di variabel command . Dari inputan tersebut kemudian
dilakukan sebuah proses sesuai command yang dipilih:
• Jika command yaitu greet , maka program meminta inputan lagi untuk
disimpan di variabel nama . Lalu output hello {nama} ditampilkan ke
layar.
• Jika command yaitu sum_numbers , inputan angka dengan pembatas
karakter spasi harus diisi, kemudian totalnya dimunculkan ke layar.
command = input("Enter command: ")
match command:
case "greet":
name = input("Your name: ")
print("hello", name)
case "sum_numbers":
numbers = input("The numbers separated by space: ")
total = 0
for str in numbers.split(' '):
total = total + int(str)
print("total:", total)
case "lucky_number":
import random
n = random.randint(0, 100)
print("your lucky number:", n)
case _:
print("unrecognized command")
• Jika command yaitu lucky_number , maka sebuah angka random
ditampilkan.
• Jika command yaitu selain 3 pilihan diatas, pesan unrecognized
command muncul.
Test run program:
Pada penerapannya, keyword match ditulis diikuti oleh suatu data yang ingin
dicek nilainya, kemudian di dalam block ditentukan kondisi atau pola
pencocokan via keyword case .
Gunakan kondisi case _: untuk menangkap kondisi yang tidak terpenuhi
(seperti else pada seleksi kondisi if ). Kondisi yang selalu terpenuhi ini
biasa disebut dengan wildcard, yang idealnya ditulis di paling akhir.
A.45.2. Pencocokan pola data sequence
Kode yang telah dipraktekan kita akan refactor, dimodifikasi agar inputan bisa
menerima command sekaligus argument (ditandai dengan karakter spasi).
Inputan command yang sebelumnya string, kini di-split memakai karakter
spasi, menghasilkan data list. Data list tersebut dicek memakai keyword
match .
Pada kode berikut, program didesain untuk menerima 3 jenis command:
• Command greet , tugasnya masih sama seperti pada program
sebelumnya.
• Command greet thrall , memunculkan pesan ke Thrall
• Command sum_numbers , tugasnya masih sama seperti pada program
sebelumnya.
Source code:
command = input("Enter command: ")
inputs = command.split(' ')
match inputs:
case ["greet"]:
name = input("Your name: ")
print("hello", name)
case ["greet", "thrall"]:
print("hello Thrall, how is the horde?")
case ["sum_numbers"]:
numbers = input("The numbers separated by space: ")
total = 0
for str in numbers.split(' '):
total = total + int(str)
print("total:", total)
Output program:
Dengan teknik pattern matching, Python bisa mengenali pola list secara
cerdas. Kondisi seperti: ketika lebar list yaitu x dengan element yaitu y bisa
diterapkan via teknik ini.
â—‰ Pencocokan sebagian pola
Pencocokan juga bisa dilakukan dengan kondisi sebagian saja, misalnya, jika
data yaitu list dengan index pertama berisi greet . Dengan pola seperti ini,
nilai element lainnya bisa ditampung dalam suatu variabel.
Contoh penerapannya bisa dilihat di kode berikut:
command = input("Enter command: ")
match command.split(' '):
case ["greet"]:
name = input("Your name: ")
print("hello", name)
Output program:
Terlihat di output inputan greet sylvanas dan greet voljin cocok dengan
pola case ["greet", name] .
â—‰ Pencocokan pola *args
Teknik pattern matching mendukung pola dimana pencocokan dilakukan untuk
sebagian pola dan sisanya disimpan di satu variabel saja, misalnya *args . Ini
merupakan kombinasi penerapan patern matching dan teknik packing data
sequence.
Pada kode berikut, ada 2 buah kondisi pola sum_numbers , yang pertama ketika
inputan hanya sum_numbers saja, dan yang kedua ketika inputan
sum_numbers diikuti oleh data lain (yang nilainya diharapkan berbentuk
numerik karena akan dihitung totalnya).
Output program:
command = input("Enter command: ")
match command.split(' '):
case ["greet"]:
name = input("Your name: ")
print("hello", name)
case ["greet", "thrall"]:
print("hello noval, how is the horde?")
case ["greet", name]:
print("hello", name)
case ["sum_numbers"]:
numbers = input("The numbers separated by space: ")
total = 0
for str in numbers.split(' '):
total = total + int(str)
print("total:", total)
case ["sum_numbers", *args]:
total = 0
for str in args:
total = total + int(str)
print("total:", total)
case _:
print("unrecognized command")
Penjelasan eksekusi program:
• Eksekusi ke-1: inputan yaitu sum_numbers , cocok dengan kondisi case
["sum_numbers"] .
• Eksekusi ke-2: inputan yaitu sum_numbers 22 32 71 22 , cocok dengan
kondisi case ["sum_numbers", *args] . Nilai numerik disitu disimpan
pada variabel *args .
• Lebih detailnya mengenai *args dibahas pada chapter Function ➜
Args & Kwargs
• Lebih detailnya mengenai teknik packing dibahas pada chapter Pack
Unpack ➜ Tuple, List, Set, Dict
â—‰ Pencocokan pola wildcard
Kondisi case _ bisa difungsikan sebagai else pada pattern matching.
Semua kondisi yang tidak terpenuhi masuk ke block tersebut. Alternatif lainnya
yaitu memakai case namaVariabel .
Pada kode berikut dua buah kondisi baru ditambahkan:
• Kondisi case [other] , akan cocok jika inputan yaitu list dengan lebar 1
item.
Contoh inputan yang memenuhi kriteria ini: hello , makan , kesedihan ,
dan kata-kata tanpa spasi lainnya. Hal ini karena inputan di-split
memakai spasi ( ) dengan hasil pasti berupa list dengan lebar 1
elemen. Isi dari variabel other yaitu inputan string.
• Kondisi case other , cocok dengan pola inputan apapun. Semua inputan
ditampung di variabel other dalam bentuk list.
command = input("Enter command: ")
match command.split(' '):
case ["greet"]:
name = input("Your name: ")
print("hello", name)
case ["greet", "thrall"]:
print("hello noval, how is the horde?")
case ["greet", name]:
print("hello", name)
case ["sum_numbers"]:
numbers = input("The numbers separated by space: ")
total = 0
for str in numbers.split(' '):
total = total + int(str)
print("total:", total)
case ["sum_numbers", *args]:
total = 0
for str in args:
total = total + int(str)
print("total:", total)
case [other]:
Output program:
A.45.3. Pencocokan pola + seleksi
kondisi
â—‰ Pencocokan pola + logika OR
Kondisi or bisa digunakan pada 1 block case , caranya dengan menuliskan
inputan diapit tanda () dan didalam kondisi ditulis memakai pembatas
karakter pipe | .
Pada kode berikut, ada 3 buah kondisi baru ditambahkan:
• Kondisi case ["greet", name, ("morning" | "afternoon" | "evening")
as t]
Hanya akan terpenuhi jika inputan pertama yaitu greet dan inputan
ke-3 yaitu morning atau afternoon atau evening . Inputan ke-3
ditampung ke variabel bernama t (syntax as digunakan disitu).
• Kondisi case ["greet", name] | ["hello", name]
Hanya akan terpenuhi jika inputan yaitu 2 kata dan kata pertama yaitu
greet atau hello .
• Kondisi case ([other] | other) as o
Hanya akan terpenuhi jika inputan yaitu list dengan lebar 1 element atau
inputan selain yang dikenali oleh kondisi-kondisi sebelumnya (wildcard).
Catatan: kondisi ini redundan, harusnya case other: saja sudah cukup
untuk difungsikan sebagai else . Namun pada contoh ini tetap ditulis
hanya untuk menunjukan format penulisan kondisi pola dengan logika OR.
Source code setelah dimodifikasi:
command = input("Enter command: ")
match command.split(' '):
case ["greet"]:
name = input("Your name: ")
print("hello", name)
case ["greet", "thrall"]:
print("hello noval, how is the horde?")
case ["greet", name, ("morning" | "afternoon" | "evening") as t]:
print("hello", name, "good", t)
case ["greet", name] | ["hello", name]:
print("hello", name)
case ([other] | other) as o:
print(f"command {o} is not recognized")
â—‰ Pencocokan pola + keyword if
Keyword if boleh dituliskan setelah kondisi case . Penambahannya
menjadikan pencocokan pola diikuti oleh seleksi kondisi.
Pada kode berikut, kondisi case ["greet", name] if name == "thrall"
ditambahkan. Kondisi tersebut hanya akan terpenuhi jika:
• Inputan berisi 2 buah kata dengan pembatas spasi
• Kata pertama yaitu greet
• Kata kedua yaitu thrall (pengecekannya via seleksi kondisi if )
Source code:
A.45.4. Pencocokan pola tipe data
command = input("Enter command: ")
match command.split(' '):
case ["greet"]:
name = input("Your name: ")
print("hello", name)
case ["greet", name] if name == "thrall":
print("hello noval, how is the horde?")
case ["greet", name, ("morning" | "afternoon" | "evening") as t]:
print("hello", name, "good", t)
case other:
print(f"command {other} is not recognized")
lainnya
Tidak hanya tipe data string dan list saja yang bisa digunakan pada pattern
matching, melainkan banyak sekali tipe data lainnya juga bisa digunakan.
Termasuk diantaranya yaitu tipe data sequence seperti tuple dan
dictionary , dan juga tipe data dari custom class.
Pada contoh berikut, ada 3 buah program dibuat yang kesemuanya berisi flow
dan pattern matching yang sama persis. Perbedaannya:
• Pada program pertama, tuple digunakan pada pattern matching
• Program ke-2 memakai tipe data dict
• Data custom class digunakan pada program ke-3
Di dalam program, data list dibuat berisi element bertipe tuple atau dict
atau object class yang menyimpan data koordinat x dan y .
Pattern matching digunakan pada setiap elemen list untuk dicek posisi x dan
y -nya, apakah di tengah, di kanan, di atas, atau di posisi lainnya.
â—‰ Pencocokan pola data tuple
Source code:
import random
def coords_generator(n = 5):
coords = []
for _ in range(0, n):
coords.append((random.randint(-5, 5), random.randint(-5, 5)))
return coords
Output program:
â—‰ Pencocokan pola data dictionary
Source code:
import random
def coords_generator(n = 5):
coords = []
for _ in range(0, n):
coords.append({
"x": random.randint(-5, 5),
"y": random.randint(-5, 5)
})
return coords
data = coords_generator(8)
for c in data:
match c:
case {"x": 0, "y": 0}:
print(f"coord {c} is positioned at the center")
case {"x": x, "y": 0} if x < 0:
print(f"coord {c} is positioned at the left horizontally
center")
case {"x": x, "y": 0} if x > 0:
Output program:
â—‰ Pencocokan pola data object class
Source code:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def info(self):
return f"({x},{y})"
import random
def coords_generator(n = 5):
coords = []
for _ in range(0, n):
coords.append(Point(random.randint(-5, 5), random.randint(-5, 5)))
return coords
data = coords_generator(8)
for c in data:
match c:
case Point(x=0, y=0):
print(f"coord {c.info()} is positioned at the center")
case Point(x=x, y=0) if x < 0:
Output program:
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../pattern-
matching
â—‰ Chapter relevan lainnya
• Seleksi kondisi ➜ if, elif, else
â—‰ Referensi
• https://peps.python.org/pep-0622/
• https://peps.python.org/pep-0636/
A.46. Python Error &
Exception
Secara teknis Python interpreter mengenal dua jenis error, yaitu syntax error
dan exception. Sebenarnya ada lagi satu jenis error lainnya, yaitu yang
munculnya hanya di level linter (di editor) namun tidak membuat eksekusi
program menjadi gagal.
Pada chapter ini kita akan membahas tentang topik tersebut.
A.46.1. Syntax error
Syntax error yaitu salah satu jenis error yang ada di Python, yang jika muncul
maka bisa dipastikan eksekusi program yaitu gagal atau terhenti. Syntax
error disebabkan oleh kesalahan penulisan.
Misalnya ada typo pada pemanggilan fungsi print yang tidak sengaja tertulis
sebagai prind() , sedangkan di source code sendiri tidak ada fungsi lain yang
dideklarasikan dengan nama itu, maka pada situasi seperti ini terjadi syntax
error.
Output program:
prind("hello world")
Satu-satunya solusi untuk mengatasi syntax error yaitu dengan memperbaiki
kode, memastikan semua penulisannya benar sesuai aturan di Python.
A.46.2. Exception
Exception yaitu jenis error yang muncul saat runtime (saat program
dijalankan). Berbeda dengan syntax error yang munculnya saat proses
eksekusi program (sebelum program benar-benar running).
Salah satu exception yang umumnya ada di bahasa pemrograman yaitu zero
division error. Error ini muncul ketika ada operasi aritmatika pembagian
suatu bilangan numerik terhadap bilangan 0 .
Contoh kasus exception:
Output program:
n1 = int(input("Enter the 1st number: "))
n2 = int(input("Enter the 2nd number: "))
res = n1 / n2
print(f"{n1} / {n2} = {res}")
Exception bisa diantisipasi dengan menambahkan validasi, misalnya untuk
kasus di atas bisa dengan ditambahkan seleksi kondisi pengecekan nilai n2 .
Jika nilainya yaitu 0 , maka program dihentikan dan pesan peringatan
dimunculkan.
Output program:
n1 = int(input("Enter the 1st number: "))
n2 = int(input("Enter the 2nd number: "))
if n2 == 0:
print("we do not allow the value of 0 on the 2nd number")
else:
res = n1 / n2
print(f"{n1} / {n2} = {res}")
Alternatif solusi lainnya untuk mengatasi exception yaitu dengan
pengaplikasian kombinasi keyword try dan catch . Lebih detailnya akan
dibahas di chapter berikutnya, di chapter Exception Handling (try, catch,
finally).
A.46.3. Throw exception
Di atas kita belajar salah satu cara antisipasi exception, yaitu dengan
penambahan validasi sesuai kebutuhan, jika kondisi berpotensi menghasilkan
exception maka pesan custom error dimunculkan.
Selanjutnya, kita akan belajar cara untuk dengan sengaja membuat atau
melempar exception (istilah umumnya throwing exception). Dengan melempar
exception, program akan terhenti secara paksa.
Pada program berikut ini, sejumlah baris angka dimunculkan sesuai inputan.
Jika inputannya 0 atau bilangan negatif maka exception dilempar, membuat
program terhenti.
print("this program prints number from 0 to N")
n = int(input("enter the value of N: "))
if n <= 0:
Outputnya:
Cara membuat exception yaitu dengan memakai keyword raise diikuti
dengan pemanggilan class Exception() yang argument-nya diisi dengan
custom error.
Pada contoh di atas, exception dimunculkan dengan pesan error we do not
allow 0 or negative number .
A.46.4. Linter error / warning
Linter yaitu suatu program utilisa yang berguna untuk melakukan
pengecekan kualitas kode saat pengembangan (penulisan kode). Linter akan
memunculkan error atau warning jika ditemukan pada beberapa bagian kode
yang ditulis yaitu kurang baik.
Di Python, jika pembaca memakai VSCode editor dan sudah meng-install
extension Python, linter akan otomatis bekerja saat menulis kode.
Linter error yaitu warning yang muncul di editor saat kode tidak sesuai baik
secara syntactic maupun secara semantic. Error yang muncul karena alasan
semantik tidak akan membuat program terhenti atau gagal running. Program
tetap bisa jalan normal saat di-run.
Salah satu contoh linter error yaitu ketika ada suatu fungsi yang saat
pemanggilannya diisi oleh tipe data dengan tipe yang tidak sesuai dibading
dengan yang sudah dideklarasikan. Pada situasi seperti ini error muncul di
editor, ada highlight merah di situ.
Meskipun tidak membuat program terhenti saat running, ada baiknya untuk
selalu menulis kode dengan baik dan benar sesuai aturan.
Catatan chapter 📑📑
â—‰ Source code praktik
github.com/novalagung/dasarpemrogramanpython-example/../error-
exception
â—‰ Chapter relevan lainnya
• Exception Handling ➜ try, except, else, finally
â—‰ Referensi
• https://docs.python.org/3/tutorial/errors.html
A.47. Python Exception
Handling (try, except,
else, finally)
Chapter ini membahas tentang penanganan exception via keyword try ,
except , else , dan finally . Metode exception handler ini sangat efektif
karena tidak membutuhkan validasi error secara manual satu per satu
memakai seleksi kondisi.
Pembahasan tentang apa itu exception sendiri ada di chapter Error &
Exception
A.47.1. Keyword try & except
Kita mulai pembelajaran dengan sebuah kode sederhana untuk menghitung
pembagian pisang ke warga. Hasil operasi pembagian tersebut kemudian di-
print.
def calculate_banana_distribution(total_banana, total_people):
res = total_banana / total_people
print(f"in fair distribution, each person shall receive {res:.0f}
banana")
total_banana = 75
total_people = 3
calculate_banana_distribution(total_banana, total_people)
# output ➜ in fair distribution, each person shall receive 25 banana
Sekilas tidak ada yang aneh dari kode di atas, kode akan berjalan normal
ketika di-run.
Sekarang coba set nilai total_people menjadi 0 kemudian re-run. Pasti
muncul exception karena ada operasi pembagian numerik terhadap nilai 0 .
Salah satu solusi penyelesaian error di atas bisa dengan penambahan seleksi
kondisi. Alternatif solusi lainnya yaitu dengan mengaplikasikan kombinasi
keyword try dan except . Caranya:
• Tempatkan statement (yang berpotensi memunculkan exception) ke dalam
block try
• Tambahkan block except dimana isinya yaitu hanlder ketika exception
muncul
Contoh penerapan:
try:
print("1st calculation")
calculate_banana_distribution(75, 5)
print("2nd calculation")
calculate_banana_distribution(25, 0)
print("3rd calculation")
Output program:
Cara kerja try dan except yaitu Python akan mencoba untuk
mengeksekusi statement dalam block try terlebih dahulu. Kemudian jika ada
exception, maka program dihentikan dan block except dijalankan.
Kurang lebih alur eksekusi program di atas yaitu seperti ini:
1. Statement pemanggilan calculate_banana_distribution() pertama
tidak menghasilkan exception. Hasilnya normal.
2. Statement pemanggilan yang ke-2 menghasilkan exception.
i. Kemudian eksekusi statement dalam block try dihentikan secara
paksa.
ii. Kemudian block except dieksekusi.
3. Statement calculate_banana_distribution() ke-3 tidak akan dijalankan.
Bisa dilihat di output, pesan oops! unable to distribute banana because
there is no person available muncul, menandai akhir eksekusi block try
& except .
Terkait penempatan block try & except sendiri bisa di bagian dimana fungsi
dipanggil, atau di dalam fungsi itu sendiri. Contoh:
def calculate_banana_distribution(total_banana, total_people):
try:
Kode di atas menghasilkan output yang berbeda dibanding sebelumnya.
Karena exception handler-nya ada di dalam fungsi
calculate_banana_distribution() , maka eksekusi try & catch hanya
terhenti di dalam fungsi tersebut saja. Di bagian pemanggilan fungsi sendiri,
eksekusinya tetap berlanjut. Efeknya statement pemanggilan fungsi
calculate_banana_distribution() ke-3 tetap berjalan.
Output program:
Silakan gunakan block try & except sesuai kebutuhan, tempatkan di bagian
kode yang memang dirasa paling pas.
A.47.2. Explicit exception handler
Suatu exception bisa ditangkap secara spesifik dengan menuliskan varian
exception-nya setelah keyword except . Contoh penerapannya bisa di lihat
pada kode berikut, dimana exception ZeroDivisionError perlu ditangkap
ketika muncul.
try:
total_banana = int(input("total banana: "))
total_people = int(input("number of person: "))
res = total_banana / total_people
Kode akan di-test dengan dijalankan dua kali dengan skenario berikut:
• Eksekusi ke-1: nilai pembagi di-set 6
• Eksekusi ke-2: salah satu inputan di-set huruf, efeknya muncul exception
ValueError
Output program:
Bisa dilihat di eksekusi pertama, block exception handler berjalan sesuai
ekspektasi. Namun pada eksekusi ke-2 ketika inputan diisi dengan huruf, ada
exception baru muncul dan tidak tertangkap. Hal ini karena di kode ditentukan
secara eksplisit hanya exception ZeroDivisionError yang ditangkap.
Untuk menangkap exception lain caranya bisa dengan menambahkan block
except baru. Pada kode berikut ada 2 exception yang akan ditangkap, yang
keduanya memunculkan pesan berbeda.
try:
total_banana = int(input("total banana: "))
total_people = int(input("number of person: "))
res = total_banana / total_people
print(f"in fair distribution, each person shall receive {res:.0f}
Output program:
Sampai sini semoga cukup jelas.
â—‰ Menangkap banyak exception sekaligus
Bagaimana jika 2 exception yang ditangkap didesain untuk memunculkan
pesan sama? Maka gunakan notasi penulisan berikut. Tulis saja exceptions
yang dingin di tangkap sebagai element tuple.
Pada kode di atas, ketika exception ValueError atau ZeroDivisionError
muncul, maka pesan oops! something wrong ditampilkan.
â—‰ Menangkap semua exception
Jika exception yang ingin ditangkap yaitu semua varian exception, maka
try:
total_banana = int(input("total banana: "))
total_people = int(input("number of person: "))
res = total_banana / total_people
print(f"in fair distribution, each person shall receive {res:.0f}
banana")
except (ValueError, ZeroDivisionError):
print("oops! something wrong")
cukup tulis except: saja, atau gunakan class except Exception: disitu.
Contoh:
• memakai except:
• memakai except Exception:
Tipe data Exception sendiri merupakan class bawaan Python Standard
Library yang dimana di-inherit oleh semua varian exception. Contohnya seperti
ValueError dan ZeroDivisionError keduanya merupakan sub class dari
class Exception .
â—‰ Memunculkan pesan exception
Biasanya dalam penangkapan exception, pesan exception aslinya juga perlu
dimunculkan mungkin untuk keperluan debugging. Hal seperti ini bisa
try:
total_banana = int(input("total banana: "))
total_people = int(input("number of person: "))
res = total_banana / total_people
print(f"in fair distribution, each person shall receive {res:.0f}
banana")
except:
print("oops! something wrong")
try:
total_banana = int(input("total banana: "))
total_people = int(input("number of person: "))
res = total_banana / total_people
print(f"in fair distribution, each person shall receive {res:.0f}
banana")
except Exception:
print("oops! something wrong")
dilakukan dengan menambahkan keyword as setelah statement except
kemudian diikuti variabel penampung data exception.
Penulisannya bisa dilihat di kode berikut:
Kode di atas akan menangkap 3 macam exception:
• Ketika ada ValueError , maka dimunculkan pesan oops! not valid
number detected diikut dengan pesan error aslinya bawaan exception
ValueError .
• Ketika ada ZeroDivisionError , maka dimunculkan pesan oops! unable
to distribute banana because there is no person available diikut
dengan pesan error aslinya bawaan exception ZeroDivisionError .
• Ketika ada exception apapun itu (selain dua di atas), maka dimunculkan
pesan oops! something wrong diikut dengan pesan error aslinya bawaan
exception.
try:
total_banana = int(input("total banana: "))
total_people = int(input("number of person: "))
res = total_banana / total_people
print(f"in fair distribution, each person shall receive {res:.0f}
banana")
except ValueError as err:
print(f"oops! not valid number detected. {err}")
except ZeroDivisionError as err:
print(f"oops! unable to distribute banana because there is no person
available. {err}")
except Exception as err:
print(f"oops! something wrong. {err}")
Bisa dilihat pada gambar di atas, error bawaan exception dimunculkan juga
setelah custom message yang kita buat.
â—‰ Alternatif penulisan exception
Dalam operasi penangkapan lebih dari 1 varian exception, penulisannya bisa
cukup dalam satu block except saja tetapi didalamnya perlu ada seleksi
kondisi untuk mengecek spesifik exception yang muncul yang mana. Contoh:
A.47.3. Keyword try , except & else
Keyword else bisa dikombinasikan dengan try dan except . Block else
try:
total_banana = int(input("total banana: "))
total_people = int(input("number of person: "))
res = total_banana / total_people
print(f"in fair distribution, each person shall receive {res:.0f}
banana")
except Exception as err:
if err == ValueError:
print(f"oops! not valid number detected. {err}")
elif err == ZeroDivisionError:
print(f"oops! unable to distribute banana because there is no
person available. {err}")
else:
print(f"oops! something wrong. {err}")
tersebut hanya akan dieksekusi ketika tidak terjadi exception.
Pada praktek yang sudah ditulis, statement print(f"in fair
distribution...") yang merupakan output kalkulasi, ideal untuk ditulis pada
block else karena statement tersebut hanya muncul ketika tidak ada
exception.
Kode sebelumnya jika di-refactor jadi seperti ini:
Penjelasan alur program di atas:
1. Program diawali dengan eksekusi statement dalam block try
2. Jika terjadi exception ValueError , maka dimunculkan pesan error
3. Jika terjadi exception ZeroDivisionError , maka dimunculkan pesan error
4. Jika terjadi exception lainnya, maka dimunculkan pesan error
5. Jika tidak terjadi exception sama sekali, maka block else dijalankan
Block else mengenali semua variabel yang dideklarasikan di block try .
Oleh karena itu variabel res bisa langsung di-print di block tersebut.
try:
total_banana = int(input("total banana: "))
total_people = int(input("number of person: "))
res = total_banana / total_people
except ValueError as err:
print(f"oops! not valid number detected. {err}")
except ZeroDivisionError as err:
print(f"oops! unable to distribute banana because there is no person
available. {err}")
except Exception as err:
print(f"oops! something wrong. {err}")
else:
print(f"in fair distribution, each person shall receive {res:.0f}
banana")
A.47.4. Keyword try , except & finally
Keyword finally yaitu keyword yang berguna untuk menandai bahwa
eksekusi suatu block try & except telah selesai. Block finally hanya
dieksekusi ketika deretan block selesai, tanpa mengecek apakah ada exception
atau tidak.
Sebagai contoh, kode sebelumnya dimodifikasi lagi menjadi seperti ini:
Penjelasan alur program di atas:
1. Program diawali dengan eksekusi statement dalam block try
2. Jika terjadi exception ValueError , maka dimunculkan pesan error
3. Jika terjadi exception ZeroDivisionError , maka dimunculkan pesan error
4. Jika terjadi exception lainnya, maka dimunculkan pesan error
5. S