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.

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

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

python
# 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).

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

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

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

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