Chapter-chapter sebelumnya terus memperlengkapi kita dengan pola yang makin tajam; chapter ini justru mengajarkan menyarungkannya. Keterampilan yang membedakan pemrogram berpengalaman bukan kemampuan menulis regex paling rumit, melainkan kepekaan mengenali kapan regex adalah alat yang salah: saat sudah ada parser khusus untuk formatnya, saat method string lebih sederhana, atau saat pola berisiko membuat program berhenti merespons.

Mengurai HTML dengan Regex adalah Jebakan #

HTML memiliki struktur bersarang yang tidak bisa digambarkan oleh regex — secara formal, regex tidak mampu menghitung pasangan tag. Pola seperti <p class="harga">(.*)</p> tampak bekerja pada contoh pertama yang kita uji, lalu diam-diam gagal pada markup yang bermakna sama:

python
import re

html1 = '<p class="harga">Rp 150000</p>'
html2 = '<p id="utama" class="harga">Rp 150000</p>'

pola = r'<p class="harga">(.*)</p>'
print(re.search(pola, html1).group(1))
print(re.search(pola, html2))

Output yang Dihasilkan:

text
Rp 150000
None

Penambahan satu attribute id — perubahan yang sah dan lazim — meruntuhkan pola. Melonggarkan pola menjadi <p.*>(.*)</p> justru menimbulkan masalah baru: .* yang greedy dari Chapter Character Classes dan Quantifiers akan melahap tag pembuka lain di tengahnya. Untuk HTML, gunakan parser. Standard library Python sudah menyertakan html.parser:

python
from html.parser import HTMLParser

class AmbilHarga(HTMLParser):
    def __init__(self):
        super().__init__()
        self.dalam_harga = False
        self.hasil = []

    def handle_starttag(self, tag, attrs):
        if tag == "p" and dict(attrs).get("class") == "harga":
            self.dalam_harga = True

    def handle_endtag(self, tag):
        if tag == "p":
            self.dalam_harga = False

    def handle_data(self, data):
        if self.dalam_harga:
            self.hasil.append(data.strip())

parser = AmbilHarga()
parser.feed('<p id="utama" class="harga">Rp 150000</p>')
parser.feed('<p class="penjelasan">Diskon lebaran</p>')
print(parser.hasil)

Output yang Dihasilkan:

text
['Rp 150000']

Mari kita telaah bagian-bagian pentingnya:

  1. Baris 3 (class AmbilHarga(HTMLParser)): class mewarisi HTMLParser; setiap kejadian markup memicu method yang sesuai secara otomatis.
  2. Baris 9-11 (handle_starttag): method dipanggil untuk setiap tag pembuka; parameter attrs berupa list pasangan nama-nilai attribute, dan dict(attrs) mengubahnya menjadi dictionary agar mudah diperiksa.
  3. Baris 17-19 (handle_data): teks di antara tag dilaporkan lewat method ini; teks hanya dikumpulkan bila parser sedang berada di dalam <p> yang dimaksud.
  4. Baris 21-24: object parser dibuat, dua dokumen kecil di-feed, dan hasilnya dicetak — attribute yang urutannya berbeda tidak lagi menjadi masalah.

Untuk proyek nyata dengan HTML dunia liar, pustaka pihak ketiga seperti BeautifulSoup dan lxml adalah pilihan standar industri — keduanya dibangun di atas parser sungguhan, bukan regex.

String Method Sering Lebih Cukup #

Banyak pekerjaan teks sehari-hari tidak butuh pola sama sekali. Method string dari Chapter String Methods lebih pendek, lebih cepat, dan langsung terbaca:

KebutuhanSolusi tanpa regexSolusi regex
Memeriksa awalanteks.startswith("INV")re.search(r"^INV", teks)
Memeriksa kehadiran teks tetap"budi" in teksre.search(r"budi", teks)
Memotong pemisahteks.split(",") dan strip()re.split(r"\s*,\s*", teks)
Mengganti teks tetapteks.replace("lama", "baru")re.sub(r"lama", "baru", teks)

Perbandingan konkret pada satu baris data berpemisah koma:

python
import re

baris = "budi, 85, jakarta"

print([kolom.strip() for kolom in baris.split(",")])
print(re.split(r"\s*,\s*", baris))
print(baris.startswith("budi"))

Output yang Dihasilkan:

text
['budi', '85', 'jakarta']
['budi', '85', 'jakarta']
True

Kedua pendekatan menghasilkan keluaran yang sama. Versi split() plus list comprehension dari Chapter Comprehensions dan Iterators tidak lebih panjang, tidak memuat pola yang harus didekodekan pembaca, dan tidak menanggung biaya mesin regex.

Format Terstruktur Butuh Parser #

Nomor HP dan baris log adalah teks semi-terstruktur — wilayah kerja regex. Begitu data memiliki format baku dengan aturan sintaksnya sendiri, gunakan parser resminya. Module json mengubah string JSON menjadi dictionary dan list sungguhan:

python
import json

profil = '{"nama": "Budi", "nilai": [85, 92, 78]}'
data = json.loads(profil)
print(data["nama"], "nilai terbaik:", max(data["nilai"]))

Output yang Dihasilkan:

text
Budi nilai terbaik: 92

Menulis pola regex untuk JSON yang benar nyaris mustahil: string JSON boleh berisi tanda kutip ter-escape, koma, dan kurung kurawal di dalam nilainya. Hal yang sama berlaku untuk format lain:

python
import tomllib

config_toml = """
[database]
host = "localhost"
port = 5432
"""
config = tomllib.loads(config_toml)
print(config["database"]["host"], "port", config["database"]["port"])

Output yang Dihasilkan:

text
localhost port 5432

Catastrophic Backtracking #

Ancaman paling serius dari regex yang salah rancang bukan hasil yang salah, melainkan waktu yang tidak pernah selesai. Pola (a+)+$ mengandung quantifier bersarang atas kelompok karakter yang sama: saat kecocokan gagal, mesin regex mencoba semua cara membagi deretan a menjadi grup-grup sebelum menyerah. Banyaknya cara pembagian berlipat dua setiap tambahan satu karakter:

python
def jumlah_cara_membagi(n):
    return 2 ** (n - 1)

for n in (10, 20, 26):
    print(f"n={n}: {jumlah_cara_membagi(n):,} kemungkinan")

Output yang Dihasilkan:

text
n=10: 512 kemungkinan
n=20: 524,288 kemungkinan
n=26: 33,554,432 kemungkinan

Perkiraan tersebut bukan teori. Mari kita ukur sungguhan — perhatikan karakter x di akhir input yang memaksa kegagalan total:

python
import re
import time

pola_berbahaya = re.compile(r"(a+)+$")

for n in (16, 20, 24):
    teks = "a" * n + "x"
    mulai = time.perf_counter()
    pola_berbahaya.search(teks)
    durasi = time.perf_counter() - mulai
    print(f"n={n}: {durasi:.3f} detik")

Output yang Dihasilkan (durasi berubah mengikuti kecepatan mesin Anda):

text
n=16: 0.008 detik
n=20: 0.131 detik
n=24: 2.093 detik

Dua puluh empat huruf membuat pencarian tertahan dua detik; beberapa huruf tambahan lagi cukup untuk menggantung server selama berjam-jam. Fenomena ini disebut catastrophic backtracking. Penawarnya: hindari quantifier bersarang atas kelompok karakter yang sama, dan bila memungkinkan sederhanakan (a+)+$ menjadi a+$ — keduanya mencocokkan string a yang sama persis tanpa percobaan pembagian yang meledak.

Tabel Keputusan #

Cara tercepat menahan diri: sebelum menulis pola, periksa dulu kecocokan situasi Anda pada tabel berikut.

SituasiAlat yang tepat
Awalan, akhiran, atau kehadiran teks tetapmethod string: startswith, endswith, in, replace
Memotong pemisah sederhanasplit dan strip
Pola berubah-ubah: digit berulang, pemisah opsionalregex
Tanggal dan angka berformat bakuregex untuk menangkap, int atau datetime untuk mengonversi
JSON, TOML, CSV, INI, URLparser standard library
HTML dan XMLhtml.parser atau pustaka parser HTML

Ada pepatah lama di kalangan pemrogram: orang yang menyelesaikan setiap masalah dengan regex akhirnya memiliki dua masalah. Regex tetap alat yang hebat di wilayahnya — teks semi-terstruktur — dan section ini telah menunjukkan betapa tajamnya. Keahlian terakhir adalah tahu kapan mengembalikannya ke sarung.