Ekspresi Reguler Python

Ekspresi reguler adalah bahasa yang canggih untuk mencocokkan pola teks. Halaman ini memberikan pengantar dasar tentang ekspresi reguler yang memadai untuk latihan Python kita dan menunjukkan cara kerja ekspresi reguler di Python. Modul "re" Python menyediakan dukungan ekspresi reguler.

Di Python, penelusuran ekspresi reguler biasanya ditulis sebagai:

match = re.search(pat, str)

Metode re.search() mengambil pola ekspresi reguler dan {i>string<i} dan mencari pola itu dalam {i>string<i}. Jika penelusuran berhasil, search() akan mengembalikan objek yang cocok atau Tidak ada jika sebaliknya. Oleh karena itu, penelusuran biasanya langsung diikuti dengan pernyataan if (jika) untuk menguji apakah penelusuran berhasil, seperti yang ditunjukkan dalam contoh berikut yang menelusuri pola 'word:' diikuti dengan kata yang terdiri dari 3 huruf (detail di bawah):

import re

str = 'an example word:cat!!'
match = re.search(r'word:\w\w\w', str)
# If-statement after search() tests if it succeeded
if match:
  print('found', match.group()) ## 'found word:cat'
else:
  print('did not find')

Kode match = re.search(pat, str) menyimpan hasil penelusuran dalam variabel bernama "match". Kemudian pernyataan if akan menguji kecocokan -- jika true, penelusuran berhasil dan match.group() merupakan teks yang cocok (misalnya, 'word:cat'). Sebaliknya, jika kecocokan salah (Tidak ada atau lebih spesifik), berarti penelusuran tidak berhasil, dan tidak ada teks yang cocok.

'r' di awal string pola menunjukkan string "raw" python yang melewati garis miring terbalik tanpa perubahan yang sangat berguna untuk ekspresi reguler (Java sangat membutuhkan fitur ini!). Saya menyarankan Anda selalu menulis {i>string<i} pola dengan 'r' hanya sebagai kebiasaan.

Pola Dasar

Kelebihan ekspresi reguler adalah dapat menentukan pola, bukan hanya karakter tetap. Berikut adalah pola paling dasar yang cocok dengan karakter tunggal:

  • a, X, 9, < -- karakter biasa sama persis. Karakter meta yang tidak sama persis karena memiliki arti khusus adalah: . ^ $ * + ? { [ ] \ | ( ) (detail di bawah)
  • . (titik) -- cocok dengan karakter tunggal apa pun kecuali baris baru '\n'
  • \w -- (huruf kecil w) cocok dengan karakter "word": huruf atau angka atau garis bawah [a-zA-Z0-9_]. Perhatikan bahwa meskipun "word" adalah mnemonik, kata ini hanya cocok dengan satu kata char, bukan seluruh kata. \W (huruf besar W) cocok dengan karakter non-kata apa pun.
  • \b -- batas antara kata dan bukan kata
  • \s -- (huruf kecil s) cocok dengan satu karakter spasi kosong -- spasi, baris baru, return, tab, formulir [ \n\r\t\f]. \S (huruf besar S) cocok dengan karakter non-spasi kosong apa pun.
  • \t, \n, \r -- tab, baris baru, kembali
  • \d -- digit desimal [0-9] (beberapa utilitas ekspresi reguler yang lebih lama tidak mendukung \d, tetapi semuanya mendukung \w dan \s)
  • ^ = start, $ = end -- cocok dengan awal atau akhir string
  • \ -- menghambat "keistimewaan" karakter. Jadi, misalnya, gunakan \. untuk mencocokkan titik atau \\ untuk mencocokkan garis miring. Jika Anda tidak yakin apakah suatu karakter memiliki arti khusus, seperti '@', Anda dapat mencoba menempatkan garis miring di depannya, \@. Jika ini bukan urutan escape yang valid, seperti \c, program Python Anda akan berhenti dengan error.

Contoh Dasar

Lelucon: apa sebutannya untuk babi bermata tiga? Pai!

Aturan dasar penelusuran ekspresi reguler untuk pola dalam string adalah:

  • Penelusuran berlanjut pada string dari awal hingga akhir, berhenti pada kecocokan pertama yang ditemukan
  • Semua pola harus cocok, tetapi tidak semua string
  • Jika match = re.search(pat, str) berhasil, pencocokan bukan merupakan Tidak Ada dan khususnya match.group() adalah teks yang cocok
  ## Search for pattern 'iii' in string 'piiig'.
  ## All of the pattern must match, but it may appear anywhere.
  ## On success, match.group() is matched text.
  match = re.search(r'iii', 'piiig') # found, match.group() == "iii"
  match = re.search(r'igs', 'piiig') # not found, match == None

  ## . = any char but \n
  match = re.search(r'..g', 'piiig') # found, match.group() == "iig"

  ## \d = digit char, \w = word char
  match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123"
  match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"

Pengulangan

Suasana menjadi lebih menarik ketika Anda menggunakan + dan * untuk menentukan pengulangan dalam pola

  • + -- 1 atau lebih kemunculan pola di sebelah kirinya, misalnya 'i+' = satu atau lebih pola i
  • * -- 0 atau lebih kemunculan pola di sebelah kirinya
  • ? -- cocok dengan 0 atau 1 kemunculan pola di sebelah kirinya

Paling kiri & Terbesar

Pertama, penelusuran akan menemukan kecocokan paling kiri untuk pola, dan kedua mencoba menggunakan sebanyak mungkin string -- yaitu + dan * sejauh mungkin (tanda + dan * dianggap "serakah").

Contoh Pengulangan

  ## i+ = one or more i's, as many as possible.
  match = re.search(r'pi+', 'piiig') # found, match.group() == "piii"

  ## Finds the first/leftmost solution, and within it drives the +
  ## as far as possible (aka 'leftmost and largest').
  ## In this example, note that it does not get to the second set of i's.
  match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii"

  ## \s* = zero or more whitespace chars
  ## Here look for 3 digits, possibly separated by whitespace.
  match = re.search(r'\d\s*\d\s*\d', 'xx1 2   3xx') # found, match.group() == "1 2   3"
  match = re.search(r'\d\s*\d\s*\d', 'xx12  3xx') # found, match.group() == "12  3"
  match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123"

  ## ^ = matches the start of string, so this fails:
  match = re.search(r'^b\w+', 'foobar') # not found, match == None
  ## but without the ^ it succeeds:
  match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"

Contoh Email

Misalnya Anda ingin mencari alamat email di dalam string 'xyz alice-b@google.com monyet ungu'. Kita akan menggunakannya sebagai contoh untuk mendemonstrasikan lebih banyak fitur ekspresi reguler. Berikut ini adalah upaya menggunakan pola r'\w+@\w+':

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'\w+@\w+', str)
  if match:
    print(match.group())  ## 'b@google'

Penelusuran tidak mendapatkan seluruh alamat email dalam kasus ini karena \w tidak cocok dengan '-' atau '.' pada alamat. Kita akan memperbaikinya menggunakan fitur ekspresi reguler di bawah ini.

Kurung Siku

Tanda kurung siku dapat digunakan untuk menunjukkan kumpulan karakter, sehingga [abc] cocok dengan 'a', 'b', atau 'c'. Kode \w, \s dll. berfungsi di dalam tanda kurung siku dengan satu pengecualian bahwa tanda titik (.) berarti titik literal. Untuk masalah email, tanda kurung siku adalah cara mudah untuk menambahkan '.' dan '-' ke kumpulan karakter yang bisa muncul di sekitar @ dengan pola r'[\w.-]+@[\w.-]+' untuk mendapatkan seluruh alamat email:

  match = re.search(r'[\w.-]+@[\w.-]+', str)
  if match:
    print(match.group())  ## 'alice-b@google.com'
(Lebih banyak fitur tanda kurung persegi) Anda juga dapat menggunakan tanda hubung untuk menunjukkan rentang, sehingga [a-z] cocok dengan semua huruf kecil. Untuk menggunakan tanda hubung tanpa menunjukkan rentang, letakkan tanda hubung di akhir, misalnya [abc-]. Topi ke atas (^) di awal rangkaian tanda kurung persegi akan membalikkannya, jadi [^ab] berarti karakter apa pun kecuali 'a' atau 'b'.

Ekstraksi Kelompok

Fitur "grup" dari ekspresi reguler memungkinkan Anda memilih bagian teks yang cocok. Misalnya, untuk masalah email, kita ingin mengekstrak nama pengguna dan host secara terpisah. Untuk melakukannya, tambahkan tanda kurung ( ) di sekitar nama pengguna dan host dalam pola, seperti ini: r'([\w.-]+)@([\w.-]+)'. Dalam hal ini, tanda kurung tidak mengubah pola yang akan cocok, melainkan membuat "grup" logis di dalam teks yang cocok. Pada pencarian yang berhasil, match.group(1) adalah teks yang sesuai dengan tanda kurung kiri pertama, dan match.group(2) adalah teks sesuai dengan tanda kurung kiri kedua. Match.group() biasa masih merupakan keseluruhan teks yang cocok seperti biasa.

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'([\w.-]+)@([\w.-]+)', str)
  if match:
    print(match.group())   ## 'alice-b@google.com' (the whole match)
    print(match.group(1))  ## 'alice-b' (the username, group 1)
    print(match.group(2))  ## 'google.com' (the host, group 2)

Alur kerja umum dengan ekspresi reguler adalah Anda menulis pola untuk hal yang Anda cari, menambahkan grup tanda kurung untuk mengekstrak bagian yang Anda inginkan.

Findall

findall() mungkin adalah satu-satunya fungsi yang paling kuat dalam modul ulang. Di atas kita menggunakan re.search() untuk menemukan kecocokan pertama untuk sebuah pola. findall() mencari *semua* kecocokan dan mengembalikannya sebagai daftar string, dengan setiap string mewakili satu kecocokan.
  ## Suppose we have a text with many email addresses
  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'

  ## Here re.findall() returns a list of all the found email strings
  emails = re.findall(r'[\w\.-]+@[\w\.-]+', str) ## ['alice@google.com', 'bob@abc.com']
  for email in emails:
    # do something with each found email string
    print(email)

{i>findall<i} Dengan File

Untuk file, Anda mungkin memiliki kebiasaan menulis loop untuk melakukan iterasi pada baris file, dan kemudian Anda dapat memanggil findall() di setiap baris. Sebaliknya, biarkan findall() melakukan iterasi untuk Anda -- jauh lebih baik! Cukup masukkan seluruh teks file ke findall() dan biarkan mengembalikan daftar semua kecocokan dalam satu langkah (ingat bahwa f.read() mengembalikan seluruh teks file dalam satu string):

  # Open file
  f = open('test.txt', encoding='utf-8')
  # Feed the file text into findall(); it returns a list of all the found strings
  strings = re.findall(r'some pattern', f.read())

findall dan Grup

Mekanisme grup tanda kurung ( ) dapat digabungkan dengan findall(). Jika pola mencakup 2 atau lebih grup tanda kurung, maka bukannya mengembalikan daftar string, findall() mengembalikan daftar *tuple*. Setiap tuple mewakili satu kecocokan pola, dan di dalam tuple terdapat data group(1), group(2) .. Jadi jika 2 grup tanda kurung ditambahkan ke pola email, kemudian findall() mengembalikan daftar tupel, masing-masing panjang 2 berisi nama pengguna dan host, misalnya ('alice', 'google.com').

  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str)
  print(tuples)  ## [('alice', 'google.com'), ('bob', 'abc.com')]
  for tuple in tuples:
    print(tuple[0])  ## username
    print(tuple[1])  ## host

Setelah memiliki daftar tuple, Anda dapat melakukan loop untuk melakukan komputasi bagi setiap tuple. Jika pola tidak menyertakan tanda kurung, findall() mengembalikan daftar {i>string<i} yang ditemukan seperti pada contoh sebelumnya. Jika pola menyertakan satu set tanda kurung, findall() mengembalikan daftar string yang sesuai dengan grup tunggal tersebut. (Fitur opsional samar: Terkadang Anda memiliki pengelompokan kurung ( ) dalam pola, namun tidak ingin Anda ekstrak. Dalam hal ini, tulis kurung dengan ?: di awal, misalnya (?: ) dan kurung siku kiri tidak akan dihitung sebagai hasil grup.)

Alur Kerja RE dan Debug

Pola ekspresi reguler mengemas banyak makna hanya ke dalam beberapa karakter , tetapi pola tersebut sangat padat, Anda dapat menghabiskan banyak waktu untuk men-debug pola. Siapkan runtime agar Anda dapat menjalankan pola dan mencetaknya dengan mudah, misalnya dengan menjalankannya pada teks pengujian kecil dan mencetak hasil findall(). Jika polanya tidak cocok, coba pelemahan pola, hapus sebagiannya agar mendapatkan terlalu banyak hasil yang cocok. Jika hasilnya tidak ada yang cocok, Anda tidak dapat membuat progres karena belum ada yang konkret untuk dilihat. Setelah kecocokan terlalu banyak, Anda dapat mengencangkannya secara bertahap untuk mencapai yang Anda inginkan.

Opsi

Fungsi re menggunakan opsi untuk mengubah perilaku pencocokan pola. Tanda opsi ditambahkan sebagai argumen tambahan ke search() atau findall() dll., misalnya re.search(pat, str, re.IGNORECASE).

  • IGNORECASE -- abaikan perbedaan huruf besar/kecil untuk pencocokan, jadi 'a' cocok dengan 'a' dan 'A'.
  • DOTALL -- memungkinkan titik (.) untuk mencocokkan baris baru -- biasanya cocok dengan apa pun selain baris baru. Hal ini dapat membuat Anda kesal. Menurut Anda, .* cocok dengan semuanya, tetapi secara default tidak melewati akhir baris. Perhatikan bahwa \s (spasi kosong) menyertakan baris baru, jadi jika Anda ingin mencocokkan run spasi kosong yang mungkin menyertakan baris baru, Anda dapat menggunakan \s*
  • MULTILINE -- Dalam string yang terdiri dari banyak baris, izinkan ^ dan $ untuk mencocokkan awal dan akhir setiap baris. Biasanya ^/$ akan mencocokkan awal dan akhir seluruh {i>string<i}.

Serakah vs. Tidak Serakah (opsional)

Ini adalah bagian opsional yang menampilkan teknik ekspresi reguler yang lebih canggih yang tidak diperlukan untuk latihan.

Misalkan Anda memiliki teks dengan tag di dalamnya: <b>foo</b> dan <i>so on</i>

Misalnya Anda mencoba mencocokkan setiap tag dengan pola '(<.*>)' -- apa yang pertama kali cocok?

Hasilnya sedikit mengejutkan, tetapi aspek serakah dari .* menyebabkannya cocok dengan keseluruhan '<b>foo</b> dan <i>seterusnya</i>' sebagai satu kecocokan besar. Masalahnya adalah .* berusaha semaksimal mungkin, bukan berhenti di > pertama (alias "serakah").

Ada ekstensi untuk ekspresi reguler di mana Anda menambahkan ? di akhir, seperti .*? atau .+?, mengubahnya menjadi tidak serakah. Sekarang mereka akan berhenti sesegera mungkin. Jadi pola '(<.*?>)' hanya akan mendapatkan '<b>' sebagai kecocokan pertama, dan '</b>' sebagai kecocokan kedua, dan seterusnya mendapatkan setiap <..> secara bergantian. Gayanya biasanya Anda menggunakan .*? yang diikuti oleh beberapa penanda konkret (dalam hal ini >) yang memaksa dijalankannya .*?.

Ekstensi *? berasal dari Perl, dan ekspresi reguler yang menyertakan ekstensi Perl dikenal sebagai Ekspresi Reguler yang Kompatibel dengan Perl -- pcre. Python menyertakan dukungan pcre. Banyak utilitas baris perintah, dll. yang memiliki penanda tempat mereka menerima pola PCre.

Teknik lama yang banyak digunakan untuk mengkodekan ide "semua karakter ini kecuali berhenti di X" menggunakan gaya kurung siku persegi. Untuk hal di atas, Anda dapat menulis polanya, tetapi alih-alih .* untuk mendapatkan semua karakter, gunakan [^>]* yang melewati semua karakter yang bukan > (awalan ^ "membalikkan" kumpulan tanda kurung siku, sehingga cocok dengan karakter apa pun yang tidak ada dalam tanda kurung).

Substitusi (opsional)

Fungsi re.sub(pat, replace, str) mencari semua {i>instance<i} pola dalam {i>string<i} yang diberikan, dan menggantinya. String pengganti dapat mencakup '\1', '\2' yang merujuk ke teks dari grup(1), grup(2), dan seterusnya dari teks asli yang cocok.

Berikut ini contoh yang mencari semua alamat email, dan mengubahnya untuk mempertahankan pengguna (\1) tetapi memiliki yo-yo-dyne.com sebagai host.

  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  ## re.sub(pat, replacement, str) -- returns new string with all replacements,
  ## \1 is group(1), \2 group(2) in the replacement
  print(re.sub(r'([\w\.-]+)@([\w\.-]+)', r'\1@yo-yo-dyne.com', str))
  ## purple alice@yo-yo-dyne.com, blah monkey bob@yo-yo-dyne.com blah dishwasher

Latihan

Untuk mempraktikkan ekspresi reguler, lihat Latihan Nama Bayi.