8. Kesalahan *errors* dan Pengecualian *exceptions*
***************************************************

Sampai sekarang pesan kesalahan belum lebih dari yang disebutkan,
tetapi jika Anda telah mencoba contohnya, Anda mungkin telah melihat
beberapa. Ada (setidaknya) dua jenis kesalahan yang dapat dibedakan:
*syntax errors* dan *exceptions*.


8.1. Kesalahan Sintaksis
========================

Kesalahan sintaksis, juga dikenal sebagai kesalahan penguraian
*parsing*, mungkin merupakan jenis keluhan paling umum yang Anda
dapatkan saat Anda masih belajar Python:

   >>> while True print('Hello world')
     File "<stdin>", line 1
       while True print('Hello world')
                      ^
   SyntaxError: invalid syntax

Pengurai *parser* mengulangi baris yang menyinggung dan menampilkan
sedikit 'arrow' yang menunjuk pada titik paling awal di baris di mana
kesalahan terdeteksi. Kesalahan disebabkan oleh (atau setidaknya
terdeteksi pada) token *preceding* panah: dalam contoh, kesalahan
terdeteksi pada fungsi "print()", karena titik dua ("':'")) hilang
sebelum itu. Nama file dan nomor baris dicetak sehingga Anda tahu ke
mana harus mencari kalau-kalau masukan berasal dari skrip.


8.2. Pengecualian
=================

Bahkan jika suatu pernyataan atau ungkapan secara sintaksis benar, itu
dapat menyebabkan kesalahan ketika suatu usaha dilakukan untuk
mengeksekusinya. Kesalahan yang terdeteksi selama eksekusi disebut
*exceptions* dan tidak fatal tanpa syarat: Anda akan segera belajar
cara menanganinya dalam program Python. Namun, sebagian besar
pengecualian tidak ditangani oleh program, dan menghasilkan pesan
kesalahan seperti yang ditunjukkan di sini:

   >>> 10 * (1/0)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   ZeroDivisionError: division by zero
   >>> 4 + spam*3
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   NameError: name 'spam' is not defined
   >>> '2' + 2
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: can only concatenate str (not "int") to str

Baris terakhir dari pesan kesalahan menunjukkan apa yang terjadi.
Pengecualian ada berbagai jenis yang berbeda, dan tipe dicetak sebagai
bagian dari pesan: tipe dalam contoh adalah "ZeroDivisionError",
"NameError" dan "TypeError". String yang dicetak sebagai jenis
pengecualian adalah nama pengecualian bawaan yang terjadi. Ini berlaku
untuk semua pengecualian bawaan, tetapi tidak harus sama untuk
pengecualian yang dibuat pengguna (meskipun ini adalah konvensi yang
bermanfaat). Nama pengecualian standar adalah pengidentifikasi bawaan
(bukan kata kunci yang dipesan *reserved keyword*).

Sisa baris menyediakan detail berdasarkan jenis pengecualian dan apa
yang menyebabkannya.

The preceding part of the error message shows the context where the
exception occurred, in the form of a stack traceback. In general it
contains a stack traceback listing source lines; however, it will not
display lines read from standard input.

Built-in Exceptions memberikan daftar pengecualian bawaan dan artinya.


8.3. Menangani Pengecualian
===========================

Dimungkinkan untuk menulis program yang menangani pengecualian yang
dipilih. Lihatlah contoh berikut, yang meminta masukan dari pengguna
sampai integer yang valid telah dimasukkan, tetapi memungkinkan
pengguna untuk menghentikan program (menggunakan "Control-C" atau apa
pun yang didukung sistem operasi); perhatikan bahwa gangguan yang
dibuat pengguna ditandai dengan munculnya pengecualian
"KeyboardInterrupt".

   >>> while True:
   ...     try:
   ...         x = int(input("Please enter a number: "))
   ...         break
   ...     except ValueError:
   ...         print("Oops!  That was no valid number.  Try again...")
   ...

Pernyataan "try" berfungsi sebagai berikut.

* Pertama, *try clause* (pernyataan(-pernyataan) di antara kata kunci
  "try" dan "except") dieksekusi.

* Jika tidak ada pengecualian terjadi, *except clause* dilewati dan
  eksekusi pernyataan :keyword: *try* selesai.

* Jika pengecualian terjadi selama eksekusi klausa *try*, sisa klausa
  dilewati. Kemudian jika jenisnya cocok dengan pengecualian yang
  dinamai dengan kata kunci "exception", klausa *except* dioperasikan,
  dan kemudian eksekusi berlanjut setelah pernyataan "try".

* Jika terjadi pengecualian yang tidak cocok dengan pengecualian yang
  disebutkan dalam klausa kecuali, itu diteruskan ke luar pernyataan
  "try"; jika tidak ada penangan yang ditemukan, ini adalah *unhandled
  exception* dan eksekusi berhenti dengan pesan seperti yang
  ditunjukkan di atas.

Pernyataan "try" mungkin memiliki lebih dari satu klausa *except*,
untuk menentukan penangan dari berbagai pengecualian. Paling banyak
satu penangan akan dieksekusi. Penangan hanya menangani pengecualian
yang terjadi pada klausa *try* yang sesuai, bukan pada penangan lain
yang sama pernyataan "try". Klausa *except* dapat menyebutkan beberapa
pengecualian sebagai tanda kurung *tuple*, misalnya:

   ... except (RuntimeError, TypeError, NameError):
   ...     pass

Kelas dalam klausa "except" kompatibel dengan pengecualian jika itu
adalah kelas yang sama atau kelas basisnya (tapi bukan sebaliknya ---
sebuah klausa *except* dari daftar kelas turunan tidak kompatibel
dengan kelas). Misalnya, kode berikut akan mencetak B, C, D dalam
urutan itu:

   class B(Exception):
       pass

   class C(B):
       pass

   class D(C):
       pass

   for cls in [B, C, D]:
       try:
           raise cls()
       except D:
           print("D")
       except C:
           print("C")
       except B:
           print("B")

Perhatikan bahwa jika klausa *except* dibalik (dengan "except B"
dahulu), itu akan dicetak B, B, B --- pencocokan pertama klausa
*except* dipicu.

Klausa *except* terakhir dapat menghilangkan nama-(nama) pengecualian,
untuk berfungsi sebagai *wildcard*. Gunakan ini dengan sangat hati-
hati, karena mudah untuk menutupi kesalahan nyata pemrograman dengan
cara ini! Ini juga dapat digunakan untuk mencetak pesan kesalahan dan
kemudian menimbulkan kembali pengecualian (memungkinkan pemanggil
untuk menangani pengecualian juga)

   import sys

   try:
       f = open('myfile.txt')
       s = f.readline()
       i = int(s.strip())
   except OSError as err:
       print("OS error: {0}".format(err))
   except ValueError:
       print("Could not convert data to an integer.")
   except:
       print("Unexpected error:", sys.exc_info()[0])
       raise

Pernyataan "try" ... >>:keywird:`except`<< memiliki opsi *else
clause*, yang, jika ada, harus mengikuti semua klausa *except*. Ini
berguna untuk kode yang harus dijalankan jika klausa *try* tidak
menimbulkan pengecualian. Sebagai contoh:

   for arg in sys.argv[1:]:
       try:
           f = open(arg, 'r')
       except OSError:
           print('cannot open', arg)
       else:
           print(arg, 'has', len(f.readlines()), 'lines')
           f.close()

Penggunaan klausa "else" lebih baik daripada menambahkan kode tambahan
ke klausa "try" karena menghindari secara tidak sengaja menangkap
pengecualian yang tidak dimunculkan oleh kode yang dilindungi oleh
pernyataan "try" ... :keyword: *!except*.

Ketika pengecualian terjadi, itu mungkin memiliki nilai terkait, juga
dikenal sebagai *argument* pengecualian. Kehadiran dan jenis argumen
tergantung pada jenis pengecualian.

Klausa except dapat menentukan variabel setelah nama pengecualian.
Variabel terikat pada *instance* pengecualian dengan argumen yang
disimpan dalam "instance.args". Untuk kenyamanan, *instance*
pengecualian mendefinisikan "__str__()" sehingga argumen dapat dicetak
langsung tanpa harus merujuk ".args". Seseorang juga dapat membuat
instansiasi pengecualian terlebih dahulu sebelum menimbulkannya dan
menambahkan atribut apa pun yang diinginkan.

   >>> try:
   ...     raise Exception('spam', 'eggs')
   ... except Exception as inst:
   ...     print(type(inst))    # the exception instance
   ...     print(inst.args)     # arguments stored in .args
   ...     print(inst)          # __str__ allows args to be printed directly,
   ...                          # but may be overridden in exception subclasses
   ...     x, y = inst.args     # unpack args
   ...     print('x =', x)
   ...     print('y =', y)
   ...
   <class 'Exception'>
   ('spam', 'eggs')
   ('spam', 'eggs')
   x = spam
   y = eggs

Jika pengecualian memiliki argumen, mereka dicetak sebagai bagian
terakhir ('detail') dari pesan untuk pengecualian yang tidak
ditangani.

Penangan pengecualian tidak hanya menangani pengecualian jika mereka
muncul segera di klausa *try*, tetapi juga jika mereka terjadi di
dalam fungsi yang disebut (bahkan secara tidak langsung) di klausa
*try*. Sebagai contoh:

   >>> def this_fails():
   ...     x = 1/0
   ...
   >>> try:
   ...     this_fails()
   ... except ZeroDivisionError as err:
   ...     print('Handling run-time error:', err)
   ...
   Handling run-time error: division by zero


8.4. Memunculkan Pengecualian
=============================

Pernyataan "raise" memungkinkan programmer untuk memaksa pengecualian
yang ditentukan terjadi. Sebagai contoh:

   >>> raise NameError('HiThere')
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   NameError: HiThere

Satu-satunya argumen untuk "raise" menunjukkan pengecualian yang
dimunculkan. Ini harus berupa *instance* pengecualian atau kelas
pengecualian (kelas yang berasal dari "Exception"). Jika kelas
pengecualian dikirimkan, itu akan secara implisit diinstansiasi dengan
memanggil pembangunnya *constructor* tanpa argumen:

   raise ValueError  # shorthand for 'raise ValueError()'

Jika Anda perlu menentukan apakah pengecualian muncul tetapi tidak
bermaksud menanganinya, bentuk yang lebih sederhana dari pernyataan
"raise" memungkinkan Anda untuk memunculkan kembali pengecualian:

   >>> try:
   ...     raise NameError('HiThere')
   ... except NameError:
   ...     print('An exception flew by!')
   ...     raise
   ...
   An exception flew by!
   Traceback (most recent call last):
     File "<stdin>", line 2, in <module>
   NameError: HiThere


8.5. Exception Chaining
=======================

The "raise" statement allows an optional "from" which enables chaining
exceptions. For example:

   # exc must be exception instance or None.
   raise RuntimeError from exc

This can be useful when you are transforming exceptions. For example:

   >>> def func():
   ...     raise IOError
   ...
   >>> try:
   ...     func()
   ... except IOError as exc:
   ...     raise RuntimeError('Failed to open database') from exc
   ...
   Traceback (most recent call last):
     File "<stdin>", line 2, in <module>
     File "<stdin>", line 2, in func
   OSError

   The above exception was the direct cause of the following exception:

   Traceback (most recent call last):
     File "<stdin>", line 4, in <module>
   RuntimeError: Failed to open database

Exception chaining happens automatically when an exception is raised
inside an "except" or "finally" section. Exception chaining can be
disabled by using "from None" idiom:

>>> try:
...     open('database.sqlite')
... except OSError:
...     raise RuntimeError from None
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError

For more information about chaining mechanics, see Built-in
Exceptions.


8.6. Pengecualian yang Ditentukan Pengguna
==========================================

Program dapat memberi nama pengecualian mereka sendiri dengan membuat
kelas pengecualian baru (lihat *tut-class* untuk informasi lebih
lanjut tentang kelas Python). Pengecualian biasanya berasal dari kelas
"Exception", baik secara langsung atau tidak langsung.

Exception classes can be defined which do anything any other class can
do, but are usually kept simple, often only offering a number of
attributes that allow information about the error to be extracted by
handlers for the exception.

Sebagian besar pengecualian didefinisikan dengan nama yang diakhiri
dengan "Error", mirip dengan penamaan pengecualian standar.

Banyak modul standar menentukan pengecualian mereka sendiri untuk
melaporkan kesalahan yang mungkin terjadi pada fungsi yang mereka
tetapkan. Informasi lebih lanjut tentang kelas disajikan dalam bab
*tut-class*.


8.7. Mendefinisikan Tindakan Pembersihan
========================================

Pernyataan "try" memiliki klausa opsional lain yang dimaksudkan untuk
menentukan tindakan pembersihan yang harus dijalankan dalam semua
keadaan. Sebagai contoh:

   >>> try:
   ...     raise KeyboardInterrupt
   ... finally:
   ...     print('Goodbye, world!')
   ...
   Goodbye, world!
   Traceback (most recent call last):
     File "<stdin>", line 2, in <module>
   KeyboardInterrupt

Jika ada klausa "finally", klausa untuk "finally" akan dijalankan
sebagai tugas terakhir sebelum pernyataan untuk "try" selesai. Klausa
untuk "finally" dapat berjalan bai atau tidak apabila pernyataan "try"
menghasilkan suatu pengecualian. Poin-poin berikut membahas kasus yang
lebih kompleks saat pengecualian terjadi:

* Jika pengecualian terjadi selama eksekusi klausa untuk :keyword:
  *!try*, maka pengecualian tersebut dapat ditangani oleh klausa
  "except". Jika pengecualian tidak ditangani oleh klausa :keyword:
  *!except*, maka pengecualian dimunculkan kembali setelah klausa
  "finally" dieksekusi.

* Pengecualian dapat terjadi selama pelaksanaan klausa "except" atau
  "else". Sekali lagi, pengecualian akan muncul kembali setelah klausa
  "finally" telah dieksekusi.

* If the "finally" clause executes a "break", "continue" or "return"
  statement, exceptions are not re-raised.

* Jika pernyataan klausa untuk "try" mencapai klausa "break",
  "continue" atau :keyword:` return` maka, pernyataan untuk klausa
  "finally" akan dieksekusi sebelum  "break", "continue" atau "return"
  dieksekusi.

* Jika klausa untuk :keyword:!finally`  telah menyertakan pernyataan
  "return", nilai yang dikembalikan akan menjadi salah satu dari
  pernyataan untuk "finally" dan dari klausa         "return", bukan
  nilai dari "try" pernayataan untuk "return".

Sebagai contoh:

   >>> def bool_return():
   ...     try:
   ...         return True
   ...     finally:
   ...         return False
   ...
   >>> bool_return()
   False

Contoh yang lebih rumit:

   >>> def divide(x, y):
   ...     try:
   ...         result = x / y
   ...     except ZeroDivisionError:
   ...         print("division by zero!")
   ...     else:
   ...         print("result is", result)
   ...     finally:
   ...         print("executing finally clause")
   ...
   >>> divide(2, 1)
   result is 2.0
   executing finally clause
   >>> divide(2, 0)
   division by zero!
   executing finally clause
   >>> divide("2", "1")
   executing finally clause
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
     File "<stdin>", line 3, in divide
   TypeError: unsupported operand type(s) for /: 'str' and 'str'

Seperti yang Anda lihat, klausa "finally" dieksekusi dalam peristiwa
apa pun. "TypeError" yang ditimbulkan dengan membagi dua string tidak
ditangani oleh klausa "except" dan karenanya kembali muncul setelah
klausa "finally" telah dieksekusi.

Dalam aplikasi dunia nyata, klausa "finally" berguna untuk melepaskan
sumber daya eksternal (seperti berkas atau koneksi jaringan), terlepas
dari apakah penggunaan sumber daya tersebut berhasil.


8.8. Tindakan Pembersihan yang Sudah Ditentukan
===============================================

Beberapa objek mendefinisikan tindakan pembersihan standar yang harus
dilakukan ketika objek tidak lagi diperlukan, terlepas dari apakah
operasi menggunakan objek berhasil atau gagal. Lihatlah contoh
berikut, yang mencoba membuka berkas dan mencetak isinya ke layar.

   for line in open("myfile.txt"):
       print(line, end="")

Masalah dengan kode ini adalah bahwa ia membiarkan berkas terbuka
untuk jumlah waktu yang tidak ditentukan setelah bagian kode ini
selesai dieksekusi. Ini bukan masalah dalam skrip sederhana, tetapi
bisa menjadi masalah untuk aplikasi yang lebih besar. Pernyataan
"with" memungkinkan objek seperti berkas digunakan dengan cara yang
memastikan mereka selalu dibersihkan secepatnya dan dengan benar.

   with open("myfile.txt") as f:
       for line in f:
           print(line, end="")

Setelah pernyataan dieksekusi, file *f* selalu ditutup, bahkan jika
ada masalah saat pemrosesan baris-baris. Objek yang, seperti berkas-
berkas, memberikan tindakan pembersihan yang telah ditentukan, akan
menunjukkan ini dalam dokumentasinya.
