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