Kamu punya file access.log berisi dua juta baris dan diminta laporan sepuluh IP paling aktif. Pakai dict biasa, kamu tulis loop, cek if ip in counts, lalu tambah satu per satu. Enam baris yang ditulis ulang di setiap proyek. Modul collections bawaan Python menaruh pola-pola semacam ini ke struktur data siap pakai: Counter untuk menghitung, defaultdict untuk menghindari pengecekan kunci, deque untuk antrean cepat, dan namedtuple untuk tuple yang field-nya punya nama. Semuanya bisa langsung diimpor tanpa instalasi tambahan.
Counter: Menghitung Tanpa Loop Manual
Counter adalah dict yang nilai default-nya nol. Kamu memberinya sebuah iterable, dan dia menghitung frekuensi tiap elemen. Tidak ada loop, tidak ada pengecekan kunci. Contoh paling klasik: menghitung kata dalam teks.
from collections import Counter
kata = "the quick brown fox jumps over the lazy dog the fox".split()
hitungan = Counter(kata)
print(hitungan)
# Counter({'the': 3, 'fox': 2, 'quick': 1, 'brown': 1,
# 'jumps': 1, 'over': 1, 'lazy': 1, 'dog': 1})
print(hitungan.most_common(2))
# [('the', 3), ('fox', 2)]
print(hitungan["the"]) # 3
print(hitungan["zzz"]) # 0 -> bukan KeyError, ini bedanya dengan dict biasa
most_common(n) langsung memberimu elemen teratas tanpa perlu sorted() manual. Akses kunci yang belum pernah tercatat mengembalikan 0, bukan KeyError — nyaman untuk kode yang sering membaca key yang mungkin belum ada.
Kamu bisa menambah data ke Counter yang sudah ada lewat update(), atau langsung menjumlahkan dua Counter. Operator +, -, &, dan | bekerja seperti operasi himpunan, jadi membandingkan dua kumpulan data jadi singkat.
a = Counter("aab")
b = Counter("abc")
print(a + b) # Counter({'a': 3, 'b': 2, 'c': 1}) -> gabungan
print(a - b) # Counter({'a': 1}) -> selisih, hasil negatif diabaikan
print(a & b) # Counter({'a': 1, 'b': 1}) -> irisan (ambil minimum)
Counter sering saya pakai untuk membandingkan dua snapshot data. Misalnya dua ekspor database dari hari yang berbeda — Counter(lama) - Counter(baru) langsung menampilkan entri yang hilang. Satu baris, tanpa set comprehension.
defaultdict: Hilangkan Pengecekan Kunci
Pola kedua yang selalu berulang: mengelompokkan nilai berdasarkan kunci. Tanpa defaultdict, kamu harus memeriksa apakah kunci sudah ada sebelum menambahkan nilai. defaultdict menerima sebuah factory — fungsi yang dipanggil otomatis saat kunci belum ada.
from collections import defaultdict
log_kunjungan = [
("andra", "/home"),
("budi", "/produk"),
("andra", "/keranjang"),
("budi", "/home"),
]
halaman = defaultdict(list)
for nama, url in log_kunjungan:
halaman[nama].append(url)
print(dict(halaman))
# {'andra': ['/home', '/keranjang'], 'budi': ['/produk', '/home']}
Di baris halaman[nama].append(url), saat nama muncul pertama kali, defaultdict memanggil list() lebih dulu lalu menambahkan elemen. Factory-nya bisa apa saja: int untuk mencacah, set untuk menyimpan nilai unik, atau lambda untuk nilai default khusus.
# int: pencacah tanpa inisialisasi manual
jumlah = defaultdict(int)
for huruf in "banana":
jumlah[huruf] += 1
print(dict(jumlah)) # {'b': 1, 'a': 3, 'n': 2}
# defaultdict bersarang: pohon tanpa inisialisasi berlapis
def pohon():
return defaultdict(pohon)
data = pohon()
data["produk"]["laptop"]["merek"] = "ThinkPad"
print(data["produk"]["laptop"]["merek"]) # ThinkPad
deque: Antrean Dua Arah yang Cepat
List Python tidak dirancang untuk dipakai sebagai antrean. list.pop(0) menggeser seluruh elemen sisanya, jadi kompleksitasnya O(n) — makin panjang list, makin lambat. deque (dibaca "deck") mendukung append dan pop dari kedua ujung dalam O(1).
from collections import deque
antrean = deque()
antrean.append("tugas-1")
antrean.append("tugas-2")
antrean.appendleft("tugas-prioritas")
print(antrean.popleft()) # tugas-prioritas
print(antrean.popleft()) # tugas-1
Dua fitur deque yang paling sering saya pakai: maxlen untuk menyimpan hanya N elemen terakhir, dan rotate untuk menggeser posisi elemen.
riwayat = deque(maxlen=5)
for i in range(10):
riwayat.append(i)
print(riwayat) # deque([5, 6, 7, 8, 9], maxlen=5)
riwayat.rotate(1) # geser semua elemen ke kanan
print(riwayat) # deque([9, 5, 6, 7, 8], maxlen=5)
maxlen membuat deque jadi buffer melingkar otomatis — pas untuk menyimpan 100 baris log terakhir atau riwayat pencarian. Elemen tertua terbuang sendiri tanpa kamu menulis del.
namedtuple: Tuple yang Punya Nama
Tuple biasa cepat dan ringan, tapi mengakses p[0] dan p[1] cepat membingungkan — yang mana x, yang mana y? namedtuple memberi nama pada tiap field tanpa kehilangan sifat tuple.
from collections import namedtuple
Titik = namedtuple("Titik", ["x", "y"])
p = Titik(10, 20)
print(p.x) # 10 -> akses lewat nama
print(p[0]) # 10 -> tetap bisa diakses seperti tuple biasa
x, y = p # unpacking tetap jalan
print(x, y) # 10 20
Dua method bawaan yang bikin nyaman: _asdict() untuk mengubah ke dict, dan _replace() untuk membuat salinan dengan sebagian field diganti. Karena immutable, kamu tidak bisa mengubah p.x langsung — _replace mengembalikan objek baru.
print(p._asdict()) # {'x': 10, 'y': 20}
p2 = p._replace(y=99)
print(p2) # Titik(x=10, y=99)
print(p) # Titik(x=10, y=20) -> p tidak berubah
namedtuple bukan pengganti kelas. Kalau kamu butuh nilai default, validasi input, atau method, pakai dataclass — saya bahas lengkap di artikel Python Dataclasses. Gunakan namedtuple saat datanya sederhana, immutable, dan sering di-unpack.
Pilih yang Tepat, Jangan Asal
Empat struktur ini menutupi sebagian besar kebutuhan sehari-hari. Isi modul collections sebenarnya lebih banyak lagi — ada ChainMap untuk menggabungkan beberapa dict jadi satu tampilan, dan OrderedDict yang kini jarang diperlukan karena dict biasa sudah mempertahankan urutan sisipan sejak Python 3.7. Kalau kamu sering mengolah iterator, collections kerap dipasangkan dengan itertools. Dan saat struktur datamu mulai butuh perilaku sendiri, itu waktunya naik kelas ke OOP — baca Belajar OOP di Python.
Latihan cepat: buka satu skrip lama yang berisi loop penghitung atau pengecekan kunci, lalu tulis ulang dengan Counter atau defaultdict. Bandingkan jumlah barisnya — biasanya menyusut hampir setengah. Kalau hasilnya terasa jauh lebih pendek, kamu sudah paham kenapa collections ada.