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.

* If an exception occurs during execution of the "try" clause, the
  rest of the clause is skipped.  Then, if its type matches the
  exception named after the "except" keyword, the *except clause* is
  executed, and then execution continues after the try/except block.

* If an exception occurs which does not match the exception named in
  the *except clause*, it is passed on to outer "try" statements; if
  no handler is found, it is an *unhandled exception* and execution
  stops with a message as shown above.

A "try" statement may have more than one *except clause*, to specify
handlers for different exceptions.  At most one handler will be
executed. Handlers only handle exceptions that occur in the
corresponding *try clause*, not in other handlers of the same "try"
statement.  An *except clause* may name multiple exceptions as a
parenthesized tuple, for example:

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

A class in an "except" clause is compatible with an exception if it is
the same class or a base class thereof (but not the other way around
--- an *except clause* listing a derived class is not compatible with
a base class). For example, the following code will print B, C, D in
that order:

   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")

Note that if the *except clauses* were reversed (with "except B"
first), it would have printed B, B, B --- the first matching *except
clause* is triggered.

All exceptions inherit from "BaseException", and so it can be used to
serve as a wildcard. Use this with extreme caution, since it is easy
to mask a real programming error in this way!  It can also be used to
print an error message and then re-raise the exception (allowing a
caller to handle the exception as well):

   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 BaseException as err:
       print(f"Unexpected {err=}, {type(err)=}")
       raise

Alternatively the last except clause may omit the exception name(s),
however the exception value must then be retrieved from
"sys.exc_info()[1]".

The "try" ... "except" statement has an optional *else clause*, which,
when present, must follow all *except clauses*.  It is useful for code
that must be executed if the *try clause* does not raise an exception.
For example:

   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.

The *except clause* may specify a variable after the exception name.
The variable is bound to an exception instance with the arguments
stored in "instance.args".  For convenience, the exception instance
defines "__str__()" so the arguments can be printed directly without
having to reference ".args".  One may also instantiate an exception
first before raising it and add any attributes to it as desired.

   >>> 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.

Exception handlers don't just handle exceptions if they occur
immediately in the *try clause*, but also if they occur inside
functions that are called (even indirectly) in the *try clause*. For
example:

   >>> 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
=======================

If an unhandled exception occurs inside an "except" section, it will
have the exception being handled attached to it and included in the
error message:

   >>> try:
   ...     open("database.sqlite")
   ... except OSError:
   ...     raise RuntimeError("unable to handle error")
   ...
   Traceback (most recent call last):
     File "<stdin>", line 2, in <module>
   FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'

   During handling of the above exception, another exception occurred:

   Traceback (most recent call last):
     File "<stdin>", line 4, in <module>
   RuntimeError: unable to handle error

To indicate that an exception is a direct consequence of another, the
"raise" statement allows an optional "from" clause:

   # 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 ConnectionError
   ...
   >>> try:
   ...     func()
   ... except ConnectionError 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
   ConnectionError

   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

It also allows disabling automatic exception chaining using the "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.
