Perilaku bawaan regex kadang terlalu kaku untuk kebutuhan nyata: pola python menolak Python, anchor ^ hanya mengenal satu awal string, dan pola panjang tidak bisa diberi komentar. Flags mengubah aturan main tersebut, sedangkan lookaround menambahkan pemeriksaan konteks — “cocok hanya bila sebelum posisi ini ada …” — tanpa mengonsumsi karakter. Chapter ini menutup kosakata regex dengan kedua alat tersebut.
Flags yang Mengubah Perilaku Pola #
| Flag | Singkatan | Efek |
|---|---|---|
re.IGNORECASE | re.I | pola tidak peduli huruf besar atau kecil |
re.MULTILINE | re.M | ^ dan $ mengenali awal dan akhir setiap baris |
re.DOTALL | re.S | . juga mencocokkan karakter baris baru |
re.VERBOSE | re.X | spasi dan komentar # di dalam pola diabaikan |
Flag dilewatkan sebagai argument tambahan pada re.search() dan kawan-kawannya, atau sebagai argument kedua re.compile(). Flag re.IGNORECASE paling sederhana:
import re
teks = "Python python PYTHON"
print(re.findall(r"python", teks))
print(re.findall(r"python", teks, re.IGNORECASE))Output yang Dihasilkan:
['python']
['Python', 'python', 'PYTHON']Flag re.MULTILINE mengubah makna anchor ^ dan $ dari Chapter *Pola Dasar Regex`: keduanya kini mengenali tepi setiap baris, bukan hanya tepi string utuh:
import re
catatan = """judul laporan
isi laporan
penutup laporan"""
print(re.findall(r"^isi", catatan))
print(re.findall(r"^isi", catatan, re.MULTILINE))
print(re.findall(r"laporan$", catatan))
print(re.findall(r"laporan$", catatan, re.MULTILINE))Output yang Dihasilkan:
[]
['isi']
['laporan']
['laporan', 'laporan', 'laporan']Tanpa flag, pola ^isi gagal karena baris isi laporan tidak berada di awal string; kata laporan hanya dihitung sekali karena hanya baris terakhir yang menyentuh akhir string. Flag re.DOTALL melonggarkan satu larangan yang sudah kita saksikan di Chapter Pola Dasar Regex — . yang menolak baris baru:
import re
blok = "baris pertama\nbaris kedua"
print(re.findall(r"pertama.baris", blok))
print(re.findall(r"pertama.baris", blok, re.DOTALL))Output yang Dihasilkan:
[]
['pertama\nbaris']Pola yang Bisa Dibaca dengan re.VERBOSE #
Pola yang tumbuh panjang cepat menjadi tak terbaca. Flag re.VERBOSE menyuruh mesin mengabaikan spasi dan baris baru di dalam pola serta memperlakukan # sebagai awal komentar — pola bisa ditulis seperti kode yang terformat:
import re
pola_seri = re.compile(r"""
^S # diawali huruf S
\d{4} # tepat empat digit
$ # pola berakhir di sini
""", re.VERBOSE)
for kode in ["S1234", "S12345", "X1234"]:
print(kode, "->", bool(pola_seri.fullmatch(kode)))Output yang Dihasilkan:
S1234 -> True
S12345 -> False
X1234 -> FalsePada baris 3-7, setiap bagian pola menempati satu baris dengan komentarnya sendiri, dan flag re.VERBOSE diberikan sebagai argument kedua re.compile(). Tanpa flag tersebut, seluruh spasi di dalam pola akan dicocokkan secara literal dan karakter # akan dianggap karakter biasa. Loop pada baris 9-10 memperlihatkan hasilnya: S1234 lolos, S12345 ditolak karena \d{4} menuntut tepat empat digit, dan X1234 ditolak huruf pembukanya.
Lookaround: Memeriksa Tanpa Mengonsumsi #
Lookaround adalah pemeriksaan posisi yang tidak memakan karakter — sifat ini disebut zero-width. Bandingkan dua pola yang sama-sama mengambil angka setelah Rp :
import re
struk = "Total Rp 7500, diskon Rp 500"
m_biasa = re.search(r"Rp (\d+)", struk)
m_lookbehind = re.search(r"(?<=Rp )\d+", struk)
print("match utuh:", m_biasa.group(0), "| group 1:", m_biasa.group(1))
print("dengan lookbehind:", m_lookbehind.group(0))Output yang Dihasilkan:
match utuh: Rp 7500 | group 1: 7500
dengan lookbehind: 7500Pola pertama mengonsumsi Rp sebagai bagian dari kecocokan, sehingga angka harus diambil lewat capture group. Pola kedua memakai positive lookbehind (?<=Rp ): mesin hanya memeriksa bahwa tiga karakter Rp (huruf R, huruf p, satu spasi) berada tepat sebelum posisi saat ini, lalu mulai mencocokkan \d+ dari posisi itu — kecocokan berisi angka saja. Empat bentuk lookaround yang tersedia:
| Bentuk | Nama | Arti |
|---|---|---|
(?=...) | positive lookahead | cocok bila teks setelah posisi ini cocok dengan ... |
(?!...) | negative lookahead | cocok bila teks setelah posisi ini tidak cocok |
(?<=...) | positive lookbehind | cocok bila teks sebelum posisi ini cocok |
(?<!...) | negative lookbehind | cocok bila teks sebelum posisi ini tidak cocok |
Karena tidak mengonsumsi karakter, lookahead bisa memfilter kecocokan tanpa ikut memakan konteksnya:
import re
belanja = "beras 5 kg, gula 2 kg, susu 1 liter"
print(re.findall(r"\d+(?= kg)", belanja))
print(re.findall(r"\d+(?! kg)", belanja))Output yang Dihasilkan:
['5', '2']
['1']Positive lookahead (?= kg) hanya menerima digit yang diikuti satuan kg — dan karena kg tidak ikut dikonsumsi, hasil kecocokannya murni angka. Negative lookahead (?! kg) membalik syarat tersebut dan justru menangkap digit 1 pada 1 liter. Pola serupa dengan arah sebaliknya memakai lookbehind:
import re
teks = "meja #3 dan kursi 7"
print(re.findall(r"(?<!#)\d+", teks))Output yang Dihasilkan:
['7']Digit 3 ditolak karena posisinya didahului karakter #; hanya 7 yang lolos.
Merangkai Lookahead: Validasi Password #
Kekuatan sesungguhnya dari lookaround: beberapa lookahead bisa ditempatkan berurutan di satu titik yang sama, sehingga satu pola memeriksa beberapa syarat sekaligus tanpa saling mengganggu. Contoh klasiknya adalah validasi password — wajib ada huruf kecil, huruf besar, digit, dan panjang minimal delapan karakter:
import re
pola_password = re.compile(r"""
(?=.*[a-z]) # minimal satu huruf kecil
(?=.*[A-Z]) # minimal satu huruf besar
(?=.*\d) # minimal satu digit
.{8,} # badan password: minimal 8 karakter
""", re.VERBOSE)
for kandidat in ["rahasia1A", "pe1A", "TANPA-ANGKA1", "tanpa-besar123"]:
print(kandidat, "->", bool(pola_password.fullmatch(kandidat)))Output yang Dihasilkan:
rahasia1A -> True
pe1A -> False
TANPA-ANGKA1 -> False
tanpa-besar123 -> FalseMari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 3-6 (tiga lookahead): ketiganya dievaluasi di titik yang sama — awal string; masing-masing memindai ke depan lewat
.*untuk mencari satu kategori karakter tanpa mengonsumsi satu karakter pun. - Baris 7 (
.{8,}): baru setelah ketiga syarat lulus, bagian pola ini mengonsumsi badan password dengan panjang minimal delapan karakter. - Baris 10-11 (
for kandidat in):rahasia1Amemenuhi semua syarat;pe1Aterlalu pendek;TANPA-ANGKA1tanpa huruf kecil;tanpa-besar123tanpa huruf besar.
Tiga jenis kegagalan berbeda tertangkap oleh satu pola. Tanpa lookahead, kita membutuhkan tiga pemanggilan re.search() terpisah — tiga pola yang harus dirawat satu per satu.