Exception and Context Manager
Sangatlah krusial untuk mempelajari caranya mendeteksi dan menangani errors. Dangat sangat bermanfaat untuk selalu meluangkan kan waktu untuk memikirkan error yang mungkin terjadi serta membuat respone atas error tersebut.
Pada bagian ini insyaAllah kita akan membahas dua hal, yaiut exception dan context manager
Exception
Ketika error terjadi saat eksekusi, error tersebut disebut dengan exception.
Umumnya, jika kita tidak membuat penanganan atas sebua exception, exception tersebut akan membuat aplikasi berhenti. Terkadang, kondisi tersebut la yang diinginkan, namun pada kasus lain, kita ingin mengehindar berhentinya aplikasi dengan menangani masalah tersebut. katakan, seperti jika terjadi error kita memberikan alert kepada user bahwa file yang ingin dibuka corrup sehingga user dapat memperbaiki atau mengupload file yang betul tanpa harus membuat aplikasi berhenti berkerja. Mari kita lihat contoh exception dibawah ini.
Code
gen = (a for a in range(1))
print(next(gen))
print(next(gen))
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Input In [5], in <cell line: 3>()
1 gen = (a for a in range(1))
2 print(next(gen))
----> 3 print(next(gen))
StopIteration:
Sebagaimana yang kita lihat, python shell (saya menggunakan jupyter lab) memberikan clue yang membantu kita, dapat kita lihat pada Traceback, pada tracebackkita dapat melihat informasi mengenai error yang terjadi. Namun shellitu sendiri berjalan dengan normmal. Ini adalah special behavior yang ada pada shell python. Akan tetapi pada umumnya, sendainya menjalankan kode diatas seluruhnya pada satu file maka python akan berhenti tepat setelah exception terjadi, dengan catatan kita tidak membuat penanganan terhadap exception. Mari kita lihat contoh dibawah ini.
Code
Karena kita tidak membuat penanganan maka baris kode yang menampilkan kalimat "This line will nerver be reached" tidak akan pernah ditampilkan.
Raising exception
Exception yang terjadi diatas sejauh ini di keluarkan oleh Python interpreter ketika error terdeteksi. Akan tetapi, kita juga dapat mengeluarkan expcetion sendiri, dimana ketika pada kondisi tertentu pada kita mengininkan python mengeluarkan exception. Kita dapat menggunakan statement raise
Code
raise NotImplementedError("error buatan")
---------------------------------------------------------------------------
NotImplementedError Traceback (most recent call last)
Input In [16], in <cell line: 1>()
----> 1 raise NotImplementedError("error buatan")
NotImplementedError: error buatan # (1)!
- Argumen pada exception class dimuat disini.
Kita dapata menggunakan tipe expcetion yang kita inginkan, namun yang terbaik adakan menggunakan tipe exception yang paling tepat menjelaskan kondisi yang menyebabkan error terjadi. Kitajuga dapat menggunakan tipe exception sendiri (yang inysaAllah akan ada dicatatan ini juga). Perhatikan pada argumen yang kita pakai pada class Exception yang kita gunakan juga di print pada bagian pesan error.
Defining your own exception
Untuk membuat class exception sendiri kita harus membuat class yang menurunkan class exception yang lain. Sebenarnya, semua exception class yang ada pada built-in bersumber pada class BaseException
, namun,class ini tidak diperuntukan secara langsung diturunkan, maka dari itu baiknya menurunkan dari class Exception
.
Info
Semua exception bawaan adalah turunan dari Exception
. Dan jika ada exception yang tidak turunan dari Exception
artinya digunakan untuk penggunaan internal oleh Python interpreter.
Code
class MyOwnException(Exception):
"""My Own exception class"""
pass
raise MyOwnException("Ini class exception buatan")
---------------------------------------------------------------------------
MyOwnException Traceback (most recent call last)
Input In [25], in <cell line: 5>()
2 """My Own exception class"""
3 pass
----> 5 raise MyOwnException("Ini class exception buatan")
MyOwnException: Ini class exception buatan
Traceback
Traceback yang ditampilkan oleh python sangat berguna untuk memahami apa yang terjadi dan yang menjadi penyebab terjadinya exception. Mari kita lihat traceback dan apa yang dapat kita gali dari informasi yang disediakan.
Code
def avoid_nol(n):
if n != 0:
return n
raise Exception("Nila dilarang nol yah")
def calcs_all(a,b,c):
return (avoid_nol(a)*2, avoid_nol(b) / 2, avoid_nol(c) ** 2)
print(calcs_all(1,0,3))
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
Input In [31], in <cell line: 9>()
6 def calcs_all(a,b,c):
7 return (avoid_nol(a)*2, avoid_nol(b) / 2, avoid_nol(c) ** 2)
----> 9 print(calcs_all(1,0,3))
Input In [31], in calcs_all(a, b, c)
6 def calcs_all(a,b,c):
----> 7 return (avoid_nol(a)*2, avoid_nol(b) / 2, avoid_nol(c) ** 2)
Input In [31], in avoid_nol(n)
2 if n != 0:
3 return n
----> 4 raise Exception("Nila dilarang nol yah")
Exception: Nila dilarang nol yah
Dari traceback diatas kita dapat ketahui, kesalahan terjadi pertama kali pada baris 9
yang terus mendalam hingga baris ke 4
dimana kita membuat sebuah raise exception jika nilai yang diberikan adalah 0.
Handling exception
Untuk menangani exception di python kita menggunakan try
statement.
try
statement terdiri dari try clause adalah pembuka statment yang diikut dengan satu atau lebih except cluase dan opsional diikut dengan else clause yang akan dijalankan jika try cluase berhasil berjalan tanpa adanya expcetion yang dikelarkan.
Setelah excep dan else cluase, kita juga bisa membuat finally clause (opsional) yang mana kode yang ada pada cluase ini akan dieksekusi dengan tidak memperhatikan apa yang terjadipada try statement
.
Tidak diharuskan menggunakan try
diikut dengan except
. Kita juga dapat hanya menggunakan cluase try
dan finally
. Cara ini berguna jika kita ingin exception ditangani ditempat lain namun kita ingin meng-cleanup membersihkan kode yang harus dieksekusi tidak melihat apakah exception terjadi atau tidak.
Urutan cluase sangat penting, harus berurutam
- try
- except
- else
- finally
Dan juga harus diingat, try
harus diikut dengan setidaknya satu buah except
atau satu buah finally
. Let us see an example:
Code
Kita juga dapat membuat lebih dari satu buah exception.
Code
Dan kita juga dapat menangani exception yang berbeda dengan penanganana yang berbeda
Code
Yang perlu diingat bahwa, exception akan ditangani dari block pertama yang cocok dengan exception class atau dari base classes nya. Dengan demikian, saat kita membuat multiple except clause sebagaimana contoh diatas, pastikan kita menggunakan class exception yang paling speisifk pada block yang paling atas dan semakin kebawah semakin generic. Pada pola OOP, children on top, grandparent at the bottom. Ingat, hanya satu buah expcetion yang akan dikeluarkan pada satubuah try clause.
Dibawah ini adalah hal yang kita hindari ketika membuat mutiple exception, dimana exception generic meniban exception class yang lebih spesifik
Code
Kita juga dapat mengeluarkan exception dari dalam except cluase. Contoh, kita ingin meniban built-in exception (atau dari third-party libaray) dengan costume exception kita. Ini adalah teknik yang sering ketika menulis sebuah pustaka (library) yang membantu penggunakan untuk mendapatkan informasi yang lebih detail jika terjadi sebuah error. Contohnya
Code
class NotFoundError(Exception):
pass
_dict = {'a':1,'b':2}
try:
_dict['c']
except KeyError as ke:
error = f'Dictionary {ke.args[0]} tidak tersedia' # (1)!
raise NotFoundError(error)
- Mengambil argumen daru KeyError
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
Input In [106], in <cell line: 6>()
6 try:
----> 7 _dict['c']
8 except KeyError as ke:
KeyError: 'c'
During handling of the above exception, another exception occurred:
NotFoundError Traceback (most recent call last)
Input In [106], in <cell line: 6>()
8 except KeyError as ke:
9 error = f'Dictionary {ke.args[0]} tidak tersedia'
---> 10 raise NotFoundError(error)
NotFoundError: Dictionary c tidak tersedia
Asalnya, Python mengasumsikan exception yang terjadi didalam except cluase adalah error yang tidak terduga dan python membatu kita dengan mengelarkan tracebacks untuk keda exception tersebut. Kita juga dapat memberitahu interpreter bahwa kita dengan sengaja mengeluarkan exception baru menggunakan raise from statement
.
Code
try:
_dict['c']
except KeyError as ke:
error = f'Dictionary {ke.args[0]} tidak tersedia'
raise NotFoundError(error) from ke
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
Input In [107], in <cell line: 6>()
6 try:
----> 7 _dict['c']
8 except KeyError as ke:
KeyError: 'c'
The above exception was the direct cause of the following exception:
NotFoundError Traceback (most recent call last)
Input In [107], in <cell line: 6>()
8 except KeyError as ke:
9 error = f'Dictionary {ke.args[0]} tidak tersedia'
---> 10 raise NotFoundError(error) from ke
NotFoundError: Dictionary c tidak tersedia
Pesan errornya berubah, namun kita masih mendapatkan kedua tracebacks, yang mana sebenernya sangat dibutuhkan saat debugging. Jika kita ingin benar menekan-kan pada exception aslinya, kita harus menggunakan None
dari pada menggunakan from e
.
Code
try:
_dict['c']
except KeyError as ke:
error = f'Dictionary {ke.args[0]} tidak tersedia'
raise NotFoundError(error) from None
---------------------------------------------------------------------------
NotFoundError Traceback (most recent call last)
Input In [109], in <cell line: 1>()
3 except KeyError as ke:
4 error = f'Dictionary {ke.args[0]} tidak tersedia'
----> 5 raise NotFoundError(error) from None
NotFoundError: Dictionary c tidak tersedia
Membuat programm menggunakan exception sangat tricky. Anda bisa secara tidak sengaja menyembunyikan bug dengan cara penggunaan exception yang buruk yang mana seharunsya memberitahukita bahwa ada bugs tersebut, bukan malah menyembunyikannya. Gunakan exception dengan baik, ikut araha ini inysaAllah membuat kode program kita menjadi lebih baik.
-
Buatlah try clause se simple mungkin. Clause tersebut harus berisi hanya kode yang hanya dapat menimbulkan expcetion(s) yang ingin kita tangani.
-
Buatlah sebuah except clause sespesifik mungkin. Mungkin akan membuat menulis banyak kode saat menulis excepton, namun akan mempermudah anda nantinya jika terjadi error yang tidak terduga.
-
Gunakan
tests
untuk memastika error ditanganin dengan baik, error yang diduga dan tidak diduga. InsyaAllah dibagian selanjutnya ada catatan tentang Testing.
Context managers
Ketika kita menggunakan external resources atau global state, sering kali kita harus melakukan pembersihan (clean satate) yaitu membuang semua resource atau mengembalikan ke kondisi semula (restoring the original state) setelah selesai dibutuhkan. Jika proses restoring tidak dilakukan dengan baik akan menimbulkan bugs. Makadari itu, kita harus memastikan cleanup code dieksekusi walaupun terjadi sebuah exception. Kita dapat memanfaatkan try/finnaly
statement, namun menggunakan statement tersebut tidak selalu tepat jika berhadapan dengan kasus ini, karena kita harus menulis statement tersebut berulang-ulang jika berhadapat dengan jenis resource yang perlu untuk di clean up. Disinilah context manager hadir sebagai solusi, yaitu dengan membuat execution context kita dapat menggunakan resource atau memodifikasi state dan secara otomatis akan melakukan cleanup ketika kita keluar dari context tersebut, bahkan jika exception muncul.
Contoh dari global state yang mungkin kita butuhkan hanya sesaat adalah presisi dari perhitungan desimal. Katakan kita ingin melakukan perhitungan untuk spesifik presisi pada beberapa perhitungan tertentu dan mengambalikan ke default setelahnya.
Code
from decimal import getcontext, setcontext, Decimal, Context
default_context = getcontext()
costume_context = Context(prec=3)
bil_1 = Decimal(10)
bil_2 = Decimal(3)
# Value of Default state
print(bil_1/bil_2)
# Set Costume state
setcontext(costume_context)
# value of costume state
print(bil_1/bil_2)
# Cleanup
setcontext(default_context) # (1)!
- Cleanup context disini
dari kode diatas kita menyimpan state bawaan pada variabel default_context
dan kita membuat state dengan presisi 3 pada variable costume_context
. Dapat kita lihat hasil dari dua kali pembagian pada kondisi state original dan costume diatas mengeluarkan hasil yang berbeda tergantung state tertentu. Dan pada akhir baris kode kita mengembalikan ke presisi bawaan dari python. Akan tetapi, jika terjadi exception sebelum sampai baris #python setcontext(default_context)
maka dapat menimbulkan komputasi yang tidak sesuai yang kita harapkan. Jika demikian kita harus menggunakan try/finally
statement.
Code
kode diatas jauh lebih aman dari bugs tak terduga. Kode diatas akan me-restore original context tidak melihat apapun yang terjadi pada try block statement. Namun ada cara yang lebih baik, yaitu menggunakan context managers. Module decimal
menyediakan local context manager yang mana menagani pengaturan dan pengembalian (restoring) context.
Code
with
statement digunakan untuk masuk kedalam runtime context
yang didefinisikan oleh context manager. Setelah kode keluar dari with
statement, operasi cleanup yang terdefinisi oleh context manager secara otomatis akan dieksekusi.
Dan sangat memungkinkan mengkombinasi beberapa context manager dalam satu buah with
statement. Cara tersebut sangat berguna dimana anda harus menggunakan dua resource pada waktu yang bersamaan.
Code
Kode diatas, kita masuk ke local context dan membuka sebuah file (berlaku sebagai context manager) dalam sebuah with
statement. Kita melakukan kalkulasi dan menulis hasinya ke sebuah file. Ketika selai melakukan operasi, file otomatis ditutup dan default decimal context dikembalikan.
Selain decimal contexts dan file, banyak objek di python (standard) dapat menggunakan context managers. Contohnya: * Socket objects, yang mana mengimplementasi low-level networking interface, dapat digunakan sebagai context manager yang secara otomatis menutup networking connections. * Lock classes digunakan untuk synkronasi pada concurrent programming menggunakan context manager protocol agar otomatis menghapus locks.
Class-based context managers
COntext managers berkerja melalui dua magic method, __enter__
dan __exit__
. Fungsi __enter__()
akan dipanggil tepat sebelum masuk ke bada dari with
statement sedangkan __exit__()
akan dipanggil tepat kode keluar dari badan with
statement. Dengan begitu kita dapat membuat costume context manager dengan mengimplementasi kedua method tersebut.
Code
class MyContextManager:
def __init__(self):
print(f'INITIALIZE : {self.__class__.__name__} {id(self)}')
def __enter__(self):
print('Display before enter with body')
return self
def __exit__(self,exc_type,exc_value,exc_tb):
if exc_type != None:
print(f'Exit context')
print(f'{exc_type=} {exc_value=} {exc_tb=}')
print('Exiting with context')
return False
return True
cm = MyContextManager()
with cm as cm:
print('Inside with')
print(f'hasil return method __enter__ : {cm}') # Diambil dari return value pada method __start__()
raise(Exception('Exception inside with'))
print('This line will never reached')
print('Outside with')
INITIALIZE : MyContextManager 139990031845600
Display before enter with body
Inside with
hasil return method __enter__ : <__main__.MyContextManager object at 0x7f51f81ec8e0>
Exit context
exc_type=<class 'Exception'> exc_value=Exception('Exception inside with') exc_tb=<traceback object at 0x7f51e8f64a00>
Exiting with context
Kode diatas kita membuat sebuah class MyContextManager yangmana untuk tujuan membuat context manager sendiri. Class tersebut mengimplementasi 3 sepcial method buah;
-
__init__
, method yang digunakan untuk membuat sebuah instance dari class. pada method ini menampilkan nama class dan id dari class tersebut. -
__start__
, method harus diimplementasi untuk membuat sebuah context manager. Method ini dapat mengambilikan nilai apapun, pada kode diatas kita mengambalikan statementself
. Nilai kembalian ini dapat diakses melalui instance didalam badan dariwith
statement. -
__exit__
, method ini pun harus diimplementasi, pada method ini memliki 3 buah parameter, paramater pertama adalah jenis exception (exception type) jika terjadi (jika tidak mengambalikanNone
), parameter kedua adalah nilai dari exception (exception value) jika terjadi (jika tidak mengambalikanNone
), dan parameter ketiga adalah exception traceback jika terjadi (jika tidak mengambalikanNone
). Method ini mengambilakn tipe data booleanTrue
atauFalse
. Jika method ini mengembalikan nilaiTrue
maka jika terjadi exception maka tidak akan menampilkan pesan error, sedangkanFalse
akan menampilkan pesan error, Traceback.