Bab 7: Regular Expression¶
Pencarian "Ctrl+F" itu untuk teks pasti. Regex untuk pencarian pola. Bedanya seperti mencari "Budi" vs mencari "semua nomor HP".
Bayangkan kamu punya dokumen 500 halaman yang berisi banyak nomor HP, dan kamu ingin ekstrak semuanya. Nomor HP punya pola: 10-13 digit, kadang ada -, kadang diawali +62 atau 0.
Tanpa regex, kamu harus tulis puluhan baris kode dengan if/else yang rumit. Dengan regex, satu baris:
Regex (regular expression) adalah bahasa kecil khusus untuk mendeskripsikan pola dalam teks. Bab ini tidak akan jadikan kamu master regex (itu butuh bertahun-tahun), tapi setelah ini kamu akan bisa:
- Cari pola sederhana di teks
- Validasi format (email, nomor HP, kode pos)
- Ekstrak data dari teks tidak terstruktur
- Replace dengan pola
7.1. Tanpa Regex — Demonstrasi Masalah¶
Sebelum belajar regex, mari lihat kenapa regex ada. Tulis fungsi yang cari format nomor HP 0812-3456-7890:
def adalah_nomor_hp(teks):
if len(teks) != 14:
return False
if teks[4] != "-" or teks[9] != "-":
return False
for i, c in enumerate(teks):
if i in (4, 9):
continue
if not c.isdigit():
return False
return True
print(adalah_nomor_hp("0812-3456-7890")) # True
print(adalah_nomor_hp("0812-345-6789")) # False
12 baris kode untuk pola sederhana. Sekarang dengan regex:
3 baris. Itulah daya tarik regex.
7.2. Pattern Dasar¶
Membuat Pattern¶
Pattern regex selalu dibuat dengan re.compile():
import re
pola = re.compile(r"\d{4}-\d{4}-\d{4}")
hasil = pola.search("Hubungi saya di 0812-3456-7890.")
print(hasil) # <re.Match object; span=(15, 29), match='0812-3456-7890'>
print(hasil.group()) # 0812-3456-7890
Tiga hal yang perlu kamu paham:
r"..."= raw string. Selalu pakai untuk pattern regex — supaya\d,\w,\stidak diinterpretasi sebagai escape Python.re.compile(pola)= build pattern reusable.pola.search(teks)= cari pertama yang cocok. ReturnMatchobject atauNone.
Karakter Khusus Paling Penting¶
| Pattern | Cocok dengan |
|---|---|
\d |
Satu digit (0-9) |
\D |
Satu non-digit |
\w |
Satu karakter "kata" (huruf, digit, underscore) |
\W |
Satu non-kata |
\s |
Satu whitespace (spasi, tab, newline) |
\S |
Satu non-whitespace |
. |
Satu karakter apa saja (kecuali newline) |
flowchart LR
subgraph Pattern["Pattern: r'\\d{4}-\\d{4}-\\d{4}'"]
direction LR
P1["\\d{4}<br/>4 digit"]
P2["-<br/>literal"]
P3["\\d{4}<br/>4 digit"]
P4["-<br/>literal"]
P5["\\d{4}<br/>4 digit"]
end
subgraph Match["Match: '0812-3456-7890'"]
direction LR
M1["0812"]
M2["-"]
M3["3456"]
M4["-"]
M5["7890"]
end
P1 -.-> M1
P2 -.-> M2
P3 -.-> M3
P4 -.-> M4
P5 -.-> M5
style Pattern fill:#141414,stroke:#f59e0b,color:#a1a1a1
style Match fill:#141414,stroke:#10b981,color:#a1a1a1
style P1 fill:#1a1a1a,stroke:#f59e0b,color:#fafafa
style P2 fill:#1a1a1a,stroke:#6e6e6e,color:#fafafa
style P3 fill:#1a1a1a,stroke:#f59e0b,color:#fafafa
style P4 fill:#1a1a1a,stroke:#6e6e6e,color:#fafafa
style P5 fill:#1a1a1a,stroke:#f59e0b,color:#fafafa
style M1 fill:#1a1a1a,stroke:#10b981,color:#fafafa
style M2 fill:#1a1a1a,stroke:#10b981,color:#fafafa
style M3 fill:#1a1a1a,stroke:#10b981,color:#fafafa
style M4 fill:#1a1a1a,stroke:#10b981,color:#fafafa
style M5 fill:#1a1a1a,stroke:#10b981,color:#fafafa
Cara baca diagram
Diagram ini menunjukkan bagaimana pattern dibaca dari kiri ke kanan, bagian per bagian, dan dipasangkan ke bagian teks yang cocok.
Pattern \d{4}-\d{4}-\d{4} dipecah jadi 5 token:
\d{4}(amber) — placeholder: "tepat 4 digit". Dipasangkan ke0812.-(abu-abu) — literal: harus ada karakter strip tepat di posisi ini. Dipasangkan ke-.\d{4}— 4 digit lagi. Dipasangkan ke3456.-— strip lagi.\d{4}— 4 digit terakhir. Dipasangkan ke7890.
Cara berpikir regex:
- Token amber/dinamis = "saya menerima apa saja yang cocok jenis ini"
- Token abu-abu/literal = "saya cuma terima karakter yang persis ini"
Karakter literal yang perlu di-escape (kasih backslash di depan): . ^ $ * + ? { } [ ] \ | ( ). Karena karakter-karakter itu punya makna khusus di regex.
Pemula sering: lupa raw string r"...". Tanpa raw string, backslash-nya jadi escape Python, bukan regex. "\d" di Python = "huruf d", bukan "digit".
Kuantifier — Berapa Kali¶
| Pattern | Arti |
|---|---|
? |
0 atau 1 kali |
* |
0 atau lebih |
+ |
1 atau lebih |
{n} |
Tepat n kali |
{n,} |
Minimum n kali |
{n,m} |
Antara n sampai m kali |
>>> re.search(r"\d{3}", "123") # tepat 3 digit
<re.Match object; span=(0, 3), match='123'>
>>> re.search(r"\d{3,5}", "12345678") # 3-5 digit, ambil sebanyak mungkin
<re.Match object; span=(0, 5), match='12345'>
>>> re.search(r"a+b", "aaab")
<re.Match object; span=(0, 4), match='aaab'>
flowchart LR
Teks["'Hubungi 0812-3456-7890 hari ini'"]
Pola["r'\\d{4}-\\d{4}-\\d{4}'"]
Match["Match: '0812-3456-7890'"]
Teks --> Search["re.search()"]
Pola --> Search
Search --> Match
style Teks fill:#1a1a1a,stroke:#6366f1,color:#fafafa
style Pola fill:#1a1a1a,stroke:#f59e0b,color:#fafafa
style Search fill:#1a1a1a,stroke:#ec4899,color:#fafafa
style Match fill:#1a1a1a,stroke:#10b981,color:#fafafa
Cara baca diagram
Diagram ini menunjukkan 3 input + 1 output dari operasi regex.
- Kotak indigo (atas) = teks yang akan dicari.
- Kotak amber (kiri) = pattern regex — "deskripsi" pola yang kita cari.
- Kotak pink (tengah) = fungsi
re.search()yang menggabungkan pattern + teks. - Kotak hijau (kanan) = hasil match.
Cara baca pattern \d{4}-\d{4}-\d{4}:
\d{4}= persis 4 digit-= literal tanda strip\d{4}= lagi, 4 digit-= strip lagi\d{4}= 4 digit terakhir
Total: 4 digit, strip, 4 digit, strip, 4 digit — pola nomor HP berformat.
Kunci: regex itu seperti "menerangkan apa yang dicari ke komputer". Setelah pattern dibangun, re.search() tinggal jalan menelusuri teks dan kembalikan posisi pertama yang cocok.
7.3. Method Penting¶
.search() — Cari Pertama¶
Return Match atau None. Selalu cek is not None sebelum panggil .group().
.findall() — Cari Semua¶
Return list semua match.
.fullmatch() — Apakah Seluruh String Cocok¶
>>> re.fullmatch(r"\d{4}-\d{4}-\d{4}", "0812-3456-7890")
<re.Match object; ...>
>>> re.fullmatch(r"\d{4}-\d{4}-\d{4}", "0812-3456-7890 lain")
None
Berbeda dengan .search() — .fullmatch() butuh seluruh string cocok pattern.
.sub() — Replace dengan Pattern¶
7.4. Grouping dengan Tanda Kurung¶
Tanda kurung (...) di regex bikin group — bagian yang bisa diambil terpisah:
>>> hasil = re.search(r"(\d{4})-(\d{4})-(\d{4})", "0812-3456-7890")
>>> hasil.group() # seluruh match
'0812-3456-7890'
>>> hasil.group(1) # group pertama
'0812'
>>> hasil.group(2) # group kedua
'3456'
>>> hasil.groups() # semua group sebagai tuple
('0812', '3456', '7890')
Named Group — Lebih Mudah Dibaca¶
>>> pola = re.compile(r"(?P<area>\d{4})-(?P<nomor>\d{4})-(?P<lain>\d{4})")
>>> hasil = pola.search("0812-3456-7890")
>>> hasil.group("area")
'0812'
>>> hasil.group("nomor")
'3456'
7.5. Character Class — [...]¶
Tanda kurung siku [...] artinya "salah satu dari karakter ini":
>>> re.findall(r"[aiueo]", "Halo dunia")
['a', 'o', 'u', 'i', 'a']
>>> re.findall(r"[A-Z]", "Halo Dunia Python")
['H', 'D', 'P']
>>> re.findall(r"[A-Za-z]", "Halo123")
['H', 'a', 'l', 'o']
>>> re.findall(r"[^aiueo]", "Halo") # ^ = NOT
['H', 'l']
- di dalam [...] artinya range. [A-Z] = huruf besar, [0-9] = digit (sama dengan \d).
^ di awal [...] artinya kebalikan — semua KECUALI yang disebutkan.
7.6. Anchor — Awal & Akhir String¶
| Anchor | Arti |
|---|---|
^ |
Awal string (kalau bukan di dalam [...]) |
$ |
Akhir string |
\b |
Batas kata (word boundary) |
>>> re.search(r"^Halo", "Halo dunia") # cocok karena di awal
<Match>
>>> re.search(r"^Halo", "dunia Halo") # tidak cocok
None
>>> re.search(r"dunia$", "Halo dunia") # cocok karena di akhir
<Match>
\b berguna untuk match kata utuh:
>>> re.findall(r"\bcat\b", "cat catastrophe")
['cat'] # 'catastrophe' tidak ikut karena kata berbeda
7.7. Pattern Praktis yang Sering Dipakai¶
Email¶
Bukan validator email sempurna (regex untuk email betul-betul valid sangat panjang), tapi cukup untuk 99% kasus.
Nomor HP Indonesia¶
Cocok dengan +628123456789, 628123456789, 08123456789.
URL¶
https? = http atau https (s opsional). [^\s]+ = bukan whitespace, satu atau lebih.
Kode Pos Indonesia¶
5 digit dengan batas kata di depan-belakang (supaya tidak match angka 7 digit).
7.8. Project: Ekstraksi Kontak dari Teks¶
import re
def ekstrak_kontak(teks):
"""Ekstrak email dan nomor HP dari teks bebas."""
pola_email = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
pola_hp = re.compile(r"(?:\+62|62|0)8[1-9][0-9]{6,10}")
return {
"email": pola_email.findall(teks),
"telepon": pola_hp.findall(teks),
}
teks = """
Profil Tim:
- Andi (andi@kantor.com, +6281234567890)
- Sari (sari.lestari@example.com, 081298765432)
- Budi (budi-x@perusahaan.co.id, 6285511112222)
"""
hasil = ekstrak_kontak(teks)
print("Email ditemukan:", hasil["email"])
print("Telepon:", hasil["telepon"])
Output:
Email ditemukan: ['andi@kantor.com', 'sari.lestari@example.com', 'budi-x@perusahaan.co.id']
Telepon: ['+6281234567890', '081298765432', '6285511112222']
5 baris regex, hasil bersih. Bandingkan kalau nulis manual.
7.9. Tips Belajar Regex¶
Regex itu seperti bahasa pemrograman kecil dalam string. Cara cepat menguasainya:
- Pakai regex tester online — regex101.com atau pythex.org. Mereka kasih penjelasan real-time tiap bagian pattern.
- Mulai dari yang sederhana, tambahkan bertahap. Jangan langsung tulis pattern panjang.
- Komentar pattern panjang dengan flag
re.VERBOSE:
pola = re.compile(r"""
\(?\d{3}\)? # area code, kurung opsional
[-.\s]? # pemisah opsional
\d{3,4} # 3-4 digit
[-.\s]?
\d{4} # 4 digit terakhir
""", re.VERBOSE)
- Jangan over-engineer. Kalau pakai
.startswith()cukup, jangan pakai regex.
7.10. Ringkasan¶
- Regex = bahasa untuk mendeskripsikan pola dalam teks
- Selalu pakai raw string
r"..."untuk pattern \d,\w,\s= digit, word char, whitespace- Kuantifier:
?,*,+,{n},{n,m} - Group dengan
(...)— ambil bagian dengan.group(n) - Character class
[...], range[a-z], negasi[^...] - Anchor:
^awal,$akhir,\bbatas kata - Method utama:
.search(),.findall(),.fullmatch(),.sub()
Konsep paling penting: mulai sederhana, baru tambah kompleksitas. Pattern regex panjang yang dibikin sekaligus hampir selalu salah.
7.11. Latihan¶
7.1 — Validator Email¶
Tulis fungsi valid_email(email) pakai regex.
7.2 — Hitung Kata¶
Tulis fungsi hitung_kata(teks) yang return jumlah kata. Pakai \w+.
7.3 — Sensor Kata Kasar¶
Tulis fungsi sensor(teks, daftar_kasar) yang ganti tiap kata kasar dengan asterisk dengan jumlah sama. Pakai re.sub dengan pattern \b(kata1|kata2)\b.
7.4 — Format Tanggal¶
Tulis fungsi format_tanggal(teks) yang konversi 15-05-2026 jadi 2026-05-15.
7.5 — Ekstrak Hashtag¶
Tulis fungsi yang return semua hashtag dari teks tweet.
7.6 — Tantangan: URL Validator¶
Validasi URL dengan: protocol (http/https), domain, optional path, optional query string.
Wajib: Raw String¶
Karakter Khusus¶
| Pattern | Cocok |
|---|---|
\d \D |
digit, non-digit |
\w \W |
word char, non-word |
\s \S |
whitespace, non-ws |
. |
apa saja kecuali newline |
Kuantifier¶
| Q | Arti |
|---|---|
? |
0 atau 1 |
* |
0+ |
+ |
1+ |
{n} |
tepat n |
{n,m} |
n sampai m |
Method¶
re.search(p, s) # cari pertama → Match atau None
re.findall(p, s) # list semua match
re.fullmatch(p, s) # cek seluruh string cocok
re.sub(p, "ganti", s) # replace dengan pattern
re.compile(p) # build pattern reusable
Group¶
m = re.search(r"(\d{4})-(\d{4})", "0812-3456")
m.group() # '0812-3456'
m.group(1) # '0812'
m.group(2) # '3456'
m.groups() # ('0812', '3456')
Character Class & Anchor¶
[abc] # a, b, atau c
[a-z] # a sampai z
[^abc] # NOT abc
^ # awal string
$ # akhir string
\b # batas kata