10 Praktik Baik dan Buruk saat Menulis Kode Python

Tips menulis kode yang indah

10 Praktik Baik dan Buruk saat Menulis Kode Python

Python adalah bahasa pemrograman multi-paradigma tingkat tinggi yang menekankan readability atau keterbacaan. Bahasa pemrograman ini dikembangkan, dipelihara, dan sering digunakan umumnya sesuai dengan coding convention yang disebut "The Zen of Python" atau PEP 20.

Hi! Pada artikel ini, kita akan coba melihat beberapa contoh praktik baik dan buruk dalam menulis kode Python yang mungkin sering Anda temui.

Gunakan Unpacking dan Chaining untuk Menulis Kode yang Ringkas

  • Unpacking

Packing dan unpacking adalah salah satu fitur Python yang sangat berguna. Kita dapat menggunakan unpacking untuk menetapkan nilai variabel kita:

>>> a, b = 2, 'my-string'
>>> a
2
>>> b
'my-string'

Dengan fitur ini, kita pun bisa menukar nilai variabel dengan cara yang mungkin paling ringkas dan elegan di seluruh dunia pemrograman:

>>> a, b = b, a
>>> a
'my-string'
>>> b
2

Luar biasa, bukan? Unpacking juga dapat digunakan untuk mengisi nilai ke beberapa variabel sekaligus dalam kasus yang lebih kompleks. Misalnya, Anda mungkin menginisialisasi variabel seperti ini:

>>> x = (1, 2, 4, 8, 16)
>>> a = x[0]
>>> b = x[1]
>>> c = x[2]
>>> d = x[3]
>>> e = x[4]
>>> a, b, c, d, e
(1, 2, 4, 8, 16)

Oke, mungkin ribet ya begitu? Kita akan menggunakan pendekatan yang lebih ringkas dan bisa dibilang lebih mudah dibaca:

>>> a, b, c, d, e = x
>>> a, b, c, d, e
(1, 2, 4, 8, 16)

Keren, kan? Tapi masih bisa lebih keren lagi:

>>> a, *y, e = x
>>> a, e, y
(1, 16, [2, 4, 8])

Intinya, variabel dengan prefix * 'mengumpulkan' nilai-nilai yang tidak diberikan kepada variabel lain.

  • Chaining

Dengan Python, kita bisa menghubungkan (chaining) operasi perbandingan. Jadi, kita tidak perlu periksa apakah dua atau lebih perbandingan bernilai True:

>>> x = 4
>>> x >= 2 and x <= 8
True

Sebagai gantinya, kita tulis kode di atas dalam bentuk yang lebih ringkas, persis seperti yang dilakukan ahli matematika:

>>> 2 <= x <= 8
True
>>> 2 <= x <= 3
False

Python juga mendukung assignment beruntun. Jadi, jika kita ingin mengisi nilai yang sama ke beberapa variabel, daripada kita melakukannya seperti ini:

>>> x = 2
>>> y = 2
>>> z = 2

Kita gunakan unpacking saja agar lebih elegan :

>>> x, y, z = 2, 2, 2

Penggunaan chained assignments juga bisa membuat pekerjaan kita lebih mudah:

>>> x = y = z = 2
>>> x, y, z
(2, 2, 2)

Tapi hati-hati dengan cara ini! Karena semua variabel merujuk ke instance yang sama, perubahan salah satu nilai variabel mengakibatkan nilai dua variabel lainnya pun ikut berubah. Oleh karena itu, cara unpacking bisa jadi alternatif yang lebih aman.

Membandingkan dengan Nol

Saat kita memiliki data numerik dan kita harus memeriksa apakah angkanya sama dengan 0 (nol), kita bisa tapi tidak harus memakai operator pembanding == dan != seperti ini:

>>> x = (1, 2, 0, 3, 0, 4)
>>> for item in x:
...     if item != 0:
...         print(item)
... 
1
2
3
4

Kita gunakan cara yang pythonic dengan mengingat bahwa nol ditafsirkan sebagai False (falsy value) dalam konteks Boolean, sementara semua angka lainnya dianggap True:

>>> bool(0)
False
>>> bool(-1), bool(1), bool(20), bool(28.4)
(True, True, True, True)

Dengan mengingat konsep ini, kita bisa gunakan if item alih-alih if item != 0:

>>> for item in x:
...     if item:
...         print(item)
... 
1
2
3
4

Kita bisa mengikuti logika yang sama dan menggunakan if not item alih-alih if item == 0.

Mengecek Nilai None

None adalah objek khusus dan unik dalam Python yang juga memiliki fungsi yang sama seperti null dalam bahasa yang mirip seperti C (C#, Javascript dkk.).

Kita bisa memeriksa apakah suatu variabel adalah None dengan operator perbandingan == dan !=:

>>> x, y = 2, None
>>> x == None
False
>>> y == None
True
>>> x != None
True
>>> y != None
False

Namun, cara yang lebih pythonic dan umum adalah menggunakan keyword is dan not:

>>> x is None
False
>>> y is None
True
>>> x is not None
True
>>> y is not None
False

Selain itu, kita lebih baik menggunakan cara is (x is None) daripada alternatifnya, is not, yang kurang mudah dibaca (x is not None).

Iterasi pada Sequences dan Mappings

Kita dapat menerapkan iterasi atau looping dengan Python dalam beberapa cara. Python menyediakan beberapa class bawaan untuk memfasilitasinya.

Di hampir semua kasus, kita bisa menggunakan range() untuk mendapatkan iterator yang menghasilkan integer:

>>> x = [1, 2, 4, 8, 16]
>>> for i in range(len(x)):
...     print(x[i])
... 
1
2
4
8
16

Namun, ada cara yang lebih baik untuk mengiterasi sebuah sequence:

>>> for item in x:
...     print(item)
... 
1
2
4
8
16

Bagaimana bila kita ingin mengiterasi sequence dalam urutan terbalik? Tentu saja, kita bisa gunakan opsi range() lagi:

>>> for i in range(len(x)-1, -1, -1):
...     print(x[i])
... 
16
8
4
2
1

Eits! Kita balikkan saja urutan sequence-nya untuk meringkas kode:

>>> for item in x[::-1]:
...     print(item)
... 
16
8
4
2
1

Supaya lebih pythonic, kita gunakan reversed() untuk mendapatkan iterator yang menghasilkan item sebuah sequence dalam urutan terbalik:

>>> for item in reversed(x):
...     print(item)
... 
16
8
4
2
1

Acapkali, kita membutuhkan sebuah item suatu sequence beserta indeksnya sekaligus:

>>> for i in range(len(x)):
...     print(i, x[i])
... 
0 1
1 2
2 4
3 8
4 16

Kalau begini kasusnya, lebih baik gunakan enumerate() untuk mendapatkan iterator yang menghasilkan tuple dengan indeks sekaligus dengan itemnya:

>>> for i, item in enumerate(x):
...     print(i, item)
... 
0 1
1 2
2 4
3 8
4 16

Wah, keren ya? Tapi bagaimana jika kita ingin mengiterasi dua atau lebih sequence? Tentu saja, kita andalkan range() lagi:

>>> y = 'abcde'
>>> for i in range(len(x)):
...     print(x[i], y[i])
... 
1 a
2 b
4 c
8 d
16 e

Untuk kasus seperti ini, Python juga menawarkan solusi yang lebih baik. Kita dapat menerapkan zip() dan mendapatkan tuple yang berisi item sesuai dengan urutan masing-masing sequence yang diiterasi:

>>> for item in zip(x, y):
...     print(item)
... 
(1, 'a')
(2, 'b')
(4, 'c')
(8, 'd')
(16, 'e')

Bahkan, kita bisa mengombinasikannya dengan unpacking:

>>> for x_item, y_item in zip(x, y):
...     print(x_item, y_item)
... 
1 a
2 b
4 c
8 d
16 e

Harap diingat bahwa range() bisa sangat berguna. Namun, ada kasus (seperti yang ditunjukkan di atas) di mana ada alternatif yang lebih mudah. Contohnya, iterasi terhadap dictionary menghasilkan key-nya:

>>> z = {'a': 0, 'b': 1}
>>> for k in z:
... print(k, z[k])
... 
a 0
b 1

Daripada begitu, kita pakai saja method .items() untuk mendapatkan tuple dengan key dan value sekaligus:

>>> for k, v in z.items():
...     print(k, v)
... 
a 0
b 1

Kita bisa juga menggunakan method .keys() dan .values() untuk mengiterasi masing-masing key dan value pada sebuah dictionary.

Hindari Penggunaan Objek Mutable pada Argumen Opsional

Sistem Python sangatlah fleksibel dalam hal memberikan argumen pada fungsi dan method. Argumen opsional merupakan bagian dari kelebihan ini. Tapi hati-hati! Kita seharusnya tidak menggunakan argumen opsional yang bersifat mutable. Perhatikan contoh berikut:

>>> def f(value, seq=[]):
...     seq.append(value)
...     return seq

Di sini kita bisa lihat, sepertinya jika kita tidak mengisi seq, f() akan menambahkan value ke list kosong dan mengembalikan sesuatu seperti [value]:

>>> f(value=2)
[2]

Tampaknya tidak ada masalah, 'kan? Tentu saja ada! Perhatikan contoh berikut:

>>> f(4)
[2, 4]
>>> f(8)
[2, 4, 8]
>>> f(16)
[2, 4, 8, 16]

Terkejut? Bingung? Jika iya, Anda bukan satu-satunya. Ini terjadi karena instance yang sama dari argumen opsional (list seq[] dalam kasus ini) dirujuk setiap kali fungsi di atas dipanggil. Oke, mungkin tujuan Anda memang seperti apa yang dilakukan kode di atas. Namun, umumnya Anda harus menghindarinya. Kita dapat menghindarinya dengan beberapa logika tambahan. Salah satu caranya adalah seperti ini:

>>> def f(value, seq=None):
...     if seq is None:
...         seq = []
...     seq.append(value)
...     return seq

Versi yang lebih pendeknya bisa dibuat dengan mengganti bagian if seq is None dengan if not seq. Sekarang, kita mendapatkan perilaku yang berbeda:

>>> f(value=2)
[2]
>>> f(value=4)
[4]
>>> f(value=8)
[8]
>>> f(value=16)
[16]

Dalam kebanyakan kasus, beginilah yang kita diinginkan.

Hindari Getter dan Setter Klasik

Python membolehkan kita mendefinisikan method getter dan setter sama seperti C++ dan Java:

>>> class C:
...     def get_x(self):
...         return self.__x
...     
...     def set_x(self, value):
...         self.__x = value

Begini cara kita menggunakannya untuk mendapatkan dan mengatur state suatu objek:

>>> c = C()
>>> c.set_x(2)
>>> c.get_x()
2

Pada kasus tertentu, inilah cara terbaik untuk menyelesaikan masalah. Namun, agar lebih elegan kita akan mendefinisikannya dengan menggunakan property, terutama dalam kasus sederhana:

...     @property
...     def x(self):
...         return self.__x
... 
...     @x.setter
...     def x(self, value):
...         self.__x = value

Properti dianggap lebih pythonic daripada getter dan setter klasik. Kita bisa menggunakannya dengan cara yang sama seperti di C#, yaitu seperti atribut data biasa:

>>> c = C()
>>> c.x = 2
>>> c.x
2

Jadi pada umumnya, penggunaan properti merupakan praktik yang baik. Gunakan getter dan setter seperti C++ saat memang diperlukan.

Hindari Mengakses Protected Class Members

Python tidak memiliki class member privat. Namun, ada coding convention yang menyatakan bahwa kita tidak boleh mengakses atau memodifikasi member yang dimulai dengan garis bawah (_) di luar instance mereka. Tidak ada jaminan bahwa mereka akan mempertahankan perilaku yang ada.

Misalnya, perhatikan kode berikut:

>>> class C:
...     def __init__(self, *args):
...         self.x, self._y, self.__z = args
... 
>>> c = C(1, 2, 4)

Instance kelas C ini memiliki tiga member data: .x, ._y, dan ._C__z. Kenapa malah ada ._C__z? Ke mana perginya .__z? Jika nama member dimulai dengan double underscore (dunder), namanya malah jadi kacau, alias dirombak otomatis oleh python. Itulah sebabnya kita punya ._C__z alih-alih .__z. OK sekarang kita bisa mengakses atau merubah .x secara langsung:

>>> c.x  # OK
1

Kita juga bisa mengakses atau memodifikasi ._y dari luar instance-nya, tapi ini dianggap sebagai praktik yang buruk:

>>> c._y  # Bisa, tapi ini praktik buruk!
2

Kita tidak bisa lagi mengakses .__z karena namanya sudah dirubah Python jadi ._C__z. Jadi kita bisa mengakses atau memodifikasi ._C__z:

>>> c.__z  # Error!
Traceback (most recent call last):
File "", line 1, in 
AttributeError: 'C' object has no attribute '__z'
>>> c._C__z  # Bisa, tapi begini malah lebih buruk!
4

Anda harus benar-benar menghindari ini. Penulis class ini kemungkinan besar memulai nama atribut dengan garis bawah untuk memberi tahu Anda, "jangan digunakan!".

Bebaskan Sumber Daya Memori Menggunakan Context Managers

Kadang kala kita perlu menulis kode untuk mengelola sumber daya dengan tepat. Kasus ini sering terjadi saat bekerja dengan file, koneksi database, atau entitas lain dengan sumber daya yang tak terkelola. Misalnya, kita membuka file dan memprosesnya:

>>> my_file = open('filename.csv', 'w')
>>> # do something with `my_file`

Untuk mengelola memori dengan semestinya, kita harus menutup file ini terlebih dahulu setelah menyelesaikan pekerjaan sebelum melanjutkan ke proses berikutnya:

>>> my_file = open('filename.csv', 'w')
>>> # do something with `my_file` and..
>>> my_file.close()

Baiklah, cara ini lebih baik daripada tidak melakukannya sama sekali. Namun, bagaimana jika sebuah exception terjadi saat memproses file ini sehingga my_file.close() tidak pernah dieksekusi? Kita dapat mencegah hal ini dengan sintaks exception handling atau dengan context managers. Cara kedua berarti kita memasukkan kode ke dalam blok with:

>>> with open('filename.csv', 'w') as my_file:
...     # do something with `my_file`

Dengan menggunakan blok with, method spesial .enter() dan .exit() akan dipanggil, bahkan ketika terjadi exception. Metode ini dirancang untuk menjaga sumber daya memori. Kita pun dapat mengembangkan sistem yang kokoh dengan mengombinasikan context managers dan exception handling.

Saran Gaya Penulisan

Kode Python harus elegan, ringkas, dan mudah dibaca. Intinya harus indah.

Referensi utama tentang cara menulis kode Python yang indah adalah Style Guide for Python Code atau PEP 8. Anda tentu harus membacanya jika ingin menulis kode atau ngoding Python.

Akhir Kata

Artikel ini memberikan beberapa saran tentang cara menulis kode yang lebih efisien, lebih mudah dibaca, dan lebih ringkas. Singkatnya, menunjukkan cara menulis kode yang Pythonic. Sebagai referensi, PEP 8 menyediakan panduan gaya untuk kode Python, dan PEP 20 menerangkan prinsip-prinsip bahasa Python.

Nikmati menulis kode yang Pythonic, bermanfaat, dan indah!

Terima kasih telah membaca.