Tur pustaka standar I: modul andalan

Perkenalan collections, itertools, dan functools sebagai modul andalan pustaka standar.

Daftar isi

Pustaka standar Python mengikuti semboyan batteries included, yaitu sebagian besar kebutuhan umum sudah disediakan tanpa pemasangan tambahan. Dari ratusan modulnya, tiga nama muncul hampir di setiap proyek, yaitu collections untuk struktur data tambahan, itertools untuk alat bantu iterasi, dan functools untuk fungsi tingkat tinggi. Topik ini memperkenalkan alat yang paling sering dipakai dari ketiganya beserta situasi yang paling tepat menggunakannya, sebagai bekal sebelum alat bantu pihak ketiga dipasang.

Modul collections: Counter dan defaultdict#

Modul collections menyediakan tipe data container, yaitu struktur penampung lebih dari satu nilai, yang melengkapi list, tuple, dan dictionary bawaan. Dua yang paling sering dipakai adalah Counter dan defaultdict. Counter adalah subclass dictionary yang menghitung kemunculan nilai, sedangkan defaultdict adalah dictionary yang menyiapkan nilai default secara otomatis untuk kunci yang belum ada. Keduanya menyelesaikan pola pemrograman yang pada dictionary biasa membutuhkan pemeriksaan kunci yang panjang dan berulang.

python Contoh: menghitung kemunculan kata dengan Counter
from collections import Counter

kata = "satu dua tiga dua dua satu".split()
hitungan = Counter(kata)
print(hitungan)
print(hitungan.most_common(2))
print(hitungan["dua"])
print(hitungan["empat"])
text Keluaran
Counter({'dua': 3, 'satu': 2, 'tiga': 1})
[('dua', 3), ('satu', 2)]
3
0

Metode most_common(2) mengembalikan dua pasangan terbanyak, diurutkan dari jumlah terbesar. Perhatikan baris terakhir, yaitu akses pada kunci empat yang tidak ada: Counter mengembalikan 0 tanpa menimbulkan KeyError, sehingga pembacaan hitungan aman dilakukan tanpa pemeriksaan keberadaan kunci. Perilaku ini berbeda dari dictionary biasa, dan perbedaannya harus diingat karena salah ketik nama kunci tidak pernah memunculkan pesan error.

python Contoh: mengelompokkan data dengan defaultdict
from collections import defaultdict

kelompok = defaultdict(list)
jadwal = [("senin", "Ali"), ("selasa", "Budi"), ("senin", "Citra")]
for hari, nama in jadwal:
    kelompok[hari].append(nama)

print(dict(kelompok))
print(kelompok["rabu"])
text Keluaran
{'senin': ['Ali', 'Citra'], 'selasa': ['Budi']}
[]

Pada defaultdict(list), setiap kunci baru otomatis diisi list kosong sebelum elemen ditambahkan, sehingga perulangan cukup berisi append tanpa pemeriksaan kunci terlebih dahulu. Baris terakhir menunjukkan sisi lain perilaku itu, yaitu pembacaan kunci rabu yang belum ada justru membuat entri baru berisi list kosong. Perilaku itu diinginkan pada pola pengelompokan, sedangkan untuk pembacaan semata metode get() lebih tepat karena tidak membuat entri baru.

Modul itertools: alur data tanpa list perantara#

Modul itertools menghimpun alat bantu yang membangun iterator, yaitu objek yang menghasilkan satu nilai pada satu waktu sesuai permintaan. Karena tidak menyimpan seluruh hasil di memori, alat-alat ini hemat memori pada data yang besar, dan list() dipakai ketika hasilnya memang ingin ditampung penuh. Dua alat yang paling sering terpakai adalah accumulate dan product.

python Contoh: accumulate dan product
from itertools import accumulate, product

print(list(accumulate([10, 20, 30, 40])))
print(list(product("AB", repeat=2)))
text Keluaran
[10, 30, 60, 100]
[('A', 'A'), ('A', 'B'), ('B', 'A'), ('B', 'B')]

Fungsi accumulate menghasilkan total berjalan, yaitu penjumlahan yang menumpuk dari kiri ke kanan, dan pola ini sering muncul pada rekap saldo atau penghitungan posisi. Fungsi product menghasilkan kombinasi kartesian, yaitu semua pasangan yang mungkin dari kedua masukannya, sehingga perulangan bersarang dua tingkat dapat digantikan satu baris. Modul ini masih menyimpan banyak alat lain seperti chain, islice, dan groupby, yang seluruhnya mengikuti pola iterator yang sama.

Modul functools: menyimpan hasil dengan lru_cache#

Modul functools berisi alat bantu untuk fungsi, dan yang paling terkenal adalah decorator @lru_cache, yaitu cache hasil pemanggilan yang menyimpan nilai kembalian berdasarkan argumennya. Nama LRU adalah singkatan dari least recently used, yaitu kebijakan pembuangan entri yang paling lama tidak dipakai. Manfaatnya paling terlihat pada fungsi rekursif yang menghitung ulang hal yang sama berkali-kali.

python Contoh: jumlah pemanggilan tanpa cache
pemanggilan = 0

def fibonacci(n):
    global pemanggilan
    pemanggilan += 1
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(30))
print(pemanggilan)
text Keluaran
832040
2692537

Menghitung fibonacci(30) tanpa cache membutuhkan 2692537 pemanggilan fungsi, karena cabang kiri dan kanan rekursi terus menghitung ulang nilai yang sama. Versi berikut menambahkan satu baris decorator dan menghilangkan hampir seluruh pekerjaan itu.

python Contoh: lru_cache memangkas pemanggilan berulang
from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(30))
print(fibonacci.cache_info())
text Keluaran
832040
CacheInfo(hits=28, misses=31, maxsize=None, currsize=31)

Nilai misses=31 berarti hanya ada 31 perhitungan nyata, yaitu satu untuk setiap nilai dari fibonacci(0) sampai fibonacci(30), sedangkan hits=28 adalah pemanggilan yang langsung terjawab oleh cache. Metode cache_info() melaporkan keadaan cache tersebut dan berguna untuk menilai apakah cache benar-benar dipakai. Perubahan satu baris ini memangkas jutaan pemanggilan menjadi puluhan, dan pola yang sama berlaku untuk fungsi murni, yaitu fungsi yang hasilnya hanya ditentukan oleh masukan, dengan argumen yang dapat dihitung nilai hash-nya.

Memilih alat yang tepat#

Ketiga modul tersebut menyelesaikan kebutuhan yang berbeda, sehingga pemilihannya mengikuti bentuk masalahnya. Tabel berikut merangkum padanannya.

KebutuhanAlatModul
Menghitung kemunculan nilaiCountercollections
Mengelompokkan data per kuncidefaultdictcollections
Menggabungkan dan memotong alur datachain, isliceitertools
Menghasilkan kombinasi dan permutasiproduct, combinationsitertools
Menyimpan hasil pemanggilan fungsilru_cachefunctools

Ketiganya bagian dari pustaka standar, sehingga seluruhnya dipakai hanya dengan satu baris import tanpa pemasangan apa pun. Kebiasaan memeriksa pustaka standar sebelum memasang package pihak ketiga menghemat ketergantungan proyek, dan pemeriksaan itu cepat karena dokumentasinya tersedia lengkap pada situs resmi Python. Contoh di atas juga menunjukkan bahwa alat sederhana dari ketiga modul ini sering menggantikan perulangan manual yang panjang dengan satu baris yang jelas maksudnya.

Kesalahan umum#

Kesalahan pertama adalah menganggap iterator itertools dapat diulang berkali-kali. Iterator mengalirkan nilai sekali saja, sehingga pemakaian kedua menghasilkan list kosong tanpa pesan error apa pun.

python Contoh: iterator itertools hanya dapat dipakai sekali
from itertools import chain

aliran = chain([1, 2], [3])
print(list(aliran))
print(list(aliran))
text Keluaran
[1, 2, 3]
[]

Baris kedua kosong karena iterator sudah terhabiskan pada pemanggilan list() pertama. Perbaikannya adalah menyimpan hasil ke list biasa bila nilai perlu dipakai beberapa kali, atau membangun ulang iterator dari sumbernya setiap kali aliran baru dibutuhkan.

Kesalahan kedua adalah memberi argumen yang tidak dapat dihitung hash-nya pada fungsi ber-cache. lru_cache menyimpan hasil dalam dictionary yang kuncinya berupa argumen, sehingga pemanggilan dengan argumen berupa list gagal sebelum badan fungsi dijalankan.

python Contoh: argumen list pada fungsi ber-cache
from functools import lru_cache

@lru_cache(maxsize=None)
def jumlah_semua(nilai):
    return sum(nilai)

try:
    jumlah_semua([1, 2, 3])
except TypeError as e:
    print(type(e).__name__, str(e))
text Keluaran
TypeError unhashable type: 'list'

Perbaikannya adalah mengubah masukan menjadi tuple, yaitu tipe yang dapat dihitung hash-nya, misalnya jumlah_semua((1, 2, 3)). Aturan yang sama berlaku untuk dictionary sebagai argumen, yang sebaiknya dikirim dalam bentuk pasangan tuple yang terurut.

Kesalahan ketiga adalah mengandalkan Counter untuk memvalidasi kunci. Karena akses kunci yang tidak ada mengembalikan 0 tanpa error, salah ketik nama kunci tidak pernah terdeteksi dan hitungan nol diterima sebagai kenyataan. Pemeriksaan keberadaan kunci dengan operator in tetap diperlukan apabila keberadaan kunci itu sendiri merupakan informasi yang penting.

Ringkasan#

  • Counter menghitung kemunculan nilai dan mengembalikan 0 untuk kunci yang tidak ada, sedangkan defaultdict membuat nilai default dari fungsi pabrik yang ditetapkan untuk setiap kunci baru.
  • Alat itertools menghasilkan iterator yang hemat memori dan hanya dapat dihabiskan satu kali, dengan list() sebagai penampung bila hasilnya perlu dipakai ulang.
  • @lru_cache menyimpan hasil pemanggilan berdasarkan argumennya, dan cache_info() melaporkan jumlah hit serta misses-nya.
  • Pemeriksaan pustaka standar sebelum memasang package pihak ketiga mengurangi ketergantungan proyek secara nyata.

Referensi#