Hogyan használj reguláris kifejezéseket Pythonban

A digitális világban a szöveges adatok mindenhol jelen vannak. E-mail címek, dátumok, URL-ek, logfájlok, felhasználói beviteli adatok – a lista szinte végtelen. Ezeknek az adatoknak a hatékony feldolgozása, keresése, kivonatolása vagy éppen validálása kulcsfontosságú feladat minden programozó számára. Itt lépnek színre a reguláris kifejezések, vagy röviden regexek. Pythonban a reguláris kifejezések használata rendkívül erőteljes és rugalmas módot biztosít a szöveges adatok manipulálására. Ebben az átfogó cikkben mélyrehatóan bemutatjuk, hogyan használhatjuk ki a Pythonban rejlő regex lehetőségeket, a kezdetektől a haladó technikákig, gyakorlati példákkal illusztrálva.

Miért van szükségünk reguláris kifejezésekre?

Gondoljon arra, hogy keres egy adott mintát egy hosszú szövegben. Például az összes telefonszámot, ami egy adott formátumú, vagy az összes e-mail címet egy weboldalon. A hagyományos string metódusok (pl. `str.find()`, `str.replace()`) korlátozottak lennének, ha a minta nem rögzített, hanem bizonyos szabályok szerint változik (pl. „bármely számjegy”, „bármely kisbetű”). A reguláris kifejezések egy mini programozási nyelvként működnek, amely lehetővé teszi komplex minták leírását, és azok illesztését a szövegre. Ezáltal a szövegkeresés, kivonatolás és szövegmanipuláció sokkal hatékonyabbá válik.

A Python `re` modulja: A Kapu a Regex Világába

Pythonban a reguláris kifejezések funkcionalitását az alapértelmezett re modul biztosítja. Ahhoz, hogy használni tudjuk, egyszerűen importálnunk kell:

import re

A re modul számos függvényt kínál, amelyekkel különböző műveleteket végezhetünk. Nézzük meg a legfontosabbakat:

1. `re.search()`: Az első találat keresése

A re.search(minta, szöveg) függvény átvizsgálja a teljes szöveget, és visszaadja az első olyan helyet, ahol a minta illeszkedik. Ha találatot talál, egy Match objektumot ad vissza; ellenkező esetben None-t. A Match objektum számos hasznos metódust tartalmaz a talált információk eléréséhez:

  • group(0) vagy egyszerűen group(): A teljes illesztett string.
  • start(): Az illesztés kezdő indexe.
  • end(): Az illesztés utáni első index.
  • span(): Az illesztés kezdő és vég indexét tartalmazó tuple.
import re

szöveg = "A macska alszik a padon."
minta = "macska"
találat = re.search(minta, szöveg)

if találat:
    print(f"Találat: {találat.group()}") # Kimenet: Találat: macska
    print(f"Kezdő pozíció: {találat.start()}") # Kimenet: Kezdő pozíció: 2
    print(f"Vég pozíció: {találat.end()}") # Kimenet: Vég pozíció: 8
    print(f"Pozíció intervallum: {találat.span()}") # Kimenet: Pozíció intervallum: (2, 8)
else:
    print("Nincs találat.")

2. `re.match()`: Illesztés a string elején

A re.match(minta, szöveg) hasonló a re.search()-höz, de egy fontos különbséggel: csak akkor talál illeszkedést, ha a minta a szöveg elején található. Ha az illeszkedés nem az első karaktertől kezdődik, None-t ad vissza.

import re

szöveg = "A macska alszik a padon."
minta1 = "A macska"
minta2 = "macska"

találat1 = re.match(minta1, szöveg)
találat2 = re.match(minta2, szöveg)

if találat1:
    print(f"Találat1 a string elején: {találat1.group()}") # Kimenet: Találat1 a string elején: A macska
else:
    print("Nincs találat1 a string elején.")

if találat2:
    print(f"Találat2 a string elején: {találat2.group()}")
else:
    print("Nincs találat2 a string elején.") # Kimenet: Nincs találat2 a string elején.

3. `re.findall()`: Az összes találat listaként

Ha az összes illeszkedést szeretnénk megkapni egy szövegből, a re.findall(minta, szöveg) a megfelelő függvény. Ez egy listát ad vissza az összes illesztéssel. Ha nincsenek capture group-ok a mintában, a lista stringeket tartalmaz, amelyek a teljes illeszkedéseket képviselik. Ha vannak capture group-ok, a lista tuple-öket tartalmaz, ahol minden tuple a csoportok illesztéseit tartalmazza.

import re

szöveg = "Alma, Körte, Banán, Szőlő. Még egy Alma."
minta = r"([A-Z][a-záéíóöőúű]+)" # Nagybetűvel kezdődő magyar szavak
gyümölcsök = re.findall(minta, szöveg)
print(f"Gyümölcsök: {gyümölcsök}") # Kimenet: Gyümölcsök: ['Alma', 'Körte', 'Banán', 'Szőlő', 'Alma']

4. `re.finditer()`: Az összes Match objektum iterátorként

A re.finditer(minta, szöveg) hasonló a re.findall()-hoz, de nem egy listát, hanem egy iterátort ad vissza, amely Match objektumokat szolgáltat. Ez akkor hasznos, ha nem csak az illesztett stringekre van szükségünk, hanem az illesztés pontos pozíciójára vagy a csoportok tartalmára is.

import re

szöveg = "Az email címem: [email protected] és még egy: [email protected]"
minta = r"b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Z|a-z]{2,}b"

for találat in re.finditer(minta, szöveg):
    print(f"Email: {találat.group()}, Pozíció: {találat.span()}")
# Kimenet:
# Email: [email protected], Pozíció: (17, 34)
# Email: [email protected], Pozíció: (47, 62)

5. `re.sub()`: Szöveg cseréje reguláris kifejezésekkel

A re.sub(minta, csere_string, szöveg, count=0, flags=0) függvény a leggyakoribb szövegmanipulációs eszköz. Keresi a minta illesztéseit a szövegben, és lecseréli azokat a csere_stringgel. A count paraméterrel korlátozhatjuk a cserék számát. A csere_string hivatkozhat capture group-okra is (pl. `1`, `g`).

import re

szöveg = "Ez egy régi szöveg, tele régi szavakkal."
új_szöveg = re.sub(r"régi", "új", szöveg)
print(f"Új szöveg: {új_szöveg}") # Kimenet: Új szöveg: Ez egy új szöveg, tele új szavakkal.

dátum_szöveg = "Ma van 2023-10-26. Holnap lesz 2023-10-27."
# Dátum formátum megváltoztatása YYYY-MM-DD -> DD.MM.YYYY
formázott_dátum = re.sub(r"(d{4})-(d{2})-(d{2})", r"3.2.1", dátum_szöveg)
print(f"Formázott dátum: {formázott_dátum}") # Kimenet: Formázott dátum: Ma van 26.10.2023. Holnap lesz 27.10.2023.

6. `re.split()`: Darabolás minták alapján

A re.split(minta, szöveg, maxsplit=0, flags=0) a str.split() reguláris kifejezéses változata. Egy listát ad vissza, amely a szöveg darabjait tartalmazza, a minta illesztései mentén darabolva. A maxsplit paraméterrel korlátozhatjuk a darabolások számát.

import re

szöveg = "alma,körte;banán.szőlő"
gyümölcsök = re.split(r"[,;.]", szöveg) # Darabolás vessző, pontosvessző vagy pont mentén
print(f"Darabolt gyümölcsök: {gyümölcsök}") # Kimenet: Darabolt gyümölcsök: ['alma', 'körte', 'banán', 'szőlő']

A Reguláris Kifejezések Nyelvének Alapjai: Metakarakterek és Speciális Szimbólumok

A reguláris kifejezések ereje a speciális karakterekben rejlik, amelyekkel komplex mintákat írhatunk le. Fontos megjegyezni, hogy Pythonban gyakran használunk nyers stringeket (pl. `r”minta”`) a regex mintákhoz. Ez megakadályozza, hogy a backslash karaktereket () a Python értelmezze escape karakterként, mielőtt a regex motorhoz kerülnének. Ez elengedhetetlen a helyes működéshez.

1. Pont (`.`)

A pont karakter illeszkedik bármely karakterre, kivéve az újsor karaktert (n). (Ez a viselkedés a re.DOTALL flaggel módosítható).

re.search(r"a.b", "axb") # Illeszkedik
re.search(r"a.b", "anb") # Nem illeszkedik alapértelmezés szerint

2. Horgonyok (`^`, `$`)

  • `^`: Illeszkedik a string elejére.
  • `$`: Illeszkedik a string végére.
re.search(r"^hello", "hello world") # Illeszkedik
re.search(r"world$", "hello world") # Illeszkedik
re.search(r"^world", "hello world") # Nem illeszkedik

3. Ismétlődések (Quantifiers)

  • `*`: Nulla vagy több előző elem. (Pl. `a*` illeszkedik „”, „a”, „aa”, „aaa”…)
  • `+`: Egy vagy több előző elem. (Pl. `a+` illeszkedik „a”, „aa”, „aaa”…)
  • `?`: Nulla vagy egy előző elem (opcionális). (Pl. `colou?r` illeszkedik „color” és „colour”)
  • `{n}`: Pontosan `n` alkalommal. (Pl. `d{3}` illeszkedik három számjegyre)
  • `{n,}`: Legalább `n` alkalommal. (Pl. `d{3,}` illeszkedik legalább három számjegyre)
  • `{n,m}`: Legalább `n`, legfeljebb `m` alkalommal. (Pl. `d{3,5}` illeszkedik három, négy vagy öt számjegyre)

4. Karakterosztályok (`[]`)

A szögletes zárójelekkel megadhatunk egy halmazt, amelyből egy karakternek illeszkednie kell. Csak egyetlen karakterre illeszkednek.

  • `[abc]`: Illeszkedik ‘a’, ‘b’ vagy ‘c’ karakterre.
  • `[a-z]`: Illeszkedik bármely kisbetűre.
  • `[A-Z]`: Illeszkedik bármely nagybetűre.
  • `[0-9]`: Illeszkedik bármely számjegyre.
  • `[a-zA-Z0-9]`: Illeszkedik bármely alfanumerikus karakterre.
  • `[^abc]`: A `^` a zárójel belsejében negációt jelent, azaz illeszkedik bármilyen karakterre, KIVÉVE ‘a’, ‘b’ vagy ‘c’.
re.search(r"[aeiou]", "apple") # Illeszkedik 'a'-ra
re.search(r"[^0-9]", "123abc456") # Illeszkedik 'a'-ra

5. Előre definiált karakterosztályok (Shorthand Character Classes)

Gyakran használt karakterosztályokra léteznek rövidítések:

  • `d`: Bármely számjegy (digit) `[0-9]`.
  • `D`: Bármely nem-számjegy `[^0-9]`.
  • `w`: Bármely szó karakter (word character) `[a-zA-Z0-9_]`.
  • `W`: Bármely nem-szó karakter `[^a-zA-Z0-9_]`.
  • `s`: Bármely whitespace karakter (space, tab, newline, etc.) `[ tnrfv]`.
  • `S`: Bármely nem-whitespace karakter `[^ tnrfv]`.
  • `b`: Szóhatár (word boundary). Illeszkedik a szó elejére vagy végére.
  • `B`: Nem szóhatár.
re.search(r"d+", "A szám: 12345") # Illeszkedik '12345'-re
re.search(r"w+", "Hello_World") # Illeszkedik 'Hello_World'-re
re.search(r"bcatb", "A cat sits.") # Illeszkedik 'cat'-re

6. Csoportosítás és Visszahivatkozások (`()`)

A zárójelekkel (()) csoportosíthatunk mintákat. Ez több célra is szolgál:

  • Logikai csoportosítás: Pl. `(abra|kadabra)` a „abra” vagy „kadabra” illesztéséhez.
  • Capture Groupok: A zárójelekben lévő minta illesztése külön tárolódik, és később elérhető a Match objektumon keresztül (group(1), group(2) stb.).
  • Visszahivatkozások: Egy korábbi csoport tartalmára hivatkozhatunk a mintában (pl. `1`, `2`).
import re

szöveg = "Név: John Doe, Telefon: 123-456-7890"
minta = r"Név: (w+ w+), Telefon: (d{3}-d{3}-d{4})"
találat = re.search(minta, szöveg)

if találat:
    print(f"Teljes találat: {találat.group(0)}") # Kimenet: Teljes találat: Név: John Doe, Telefon: 123-456-7890
    print(f"Név: {találat.group(1)}") # Kimenet: Név: John Doe
    print(f"Telefon: {találat.group(2)}") # Kimenet: Telefon: 123-456-7890

# Névvel ellátott csoportok (Named Groups)
minta_named = r"Név: (?Pw+ w+), Telefon: (?Pd{3}-d{3}-d{4})"
találat_named = re.search(minta_named, szöveg)
if találat_named:
    print(f"Név (névvel): {találat_named.group('nev')}") # Kimenet: Név (névvel): John Doe
    print(f"Telefon (névvel): {találat_named.group('telefon')}") # Kimenet: Telefon (névvel): 123-456-7890

7. Mohó és Nem Mohó Illesztés (Greedy vs. Non-Greedy)

Alapértelmezés szerint az ismétlődő minták (*, +, ?, {m,n}) mohóak (greedy) – annyi karaktert illesztenek, amennyit csak tudnak, miközben mégis sikeres az illeszkedés. Ha nem mohó (nem greedy vagy lusta) illesztésre van szükségünk, tegyünk egy ?-t az ismétlődés után (pl. `*?`, `+?`, `??`, `{m,n}?`).

import re

szöveg = "<b>Ez egy félkövér szöveg.</b> <i>Ez dőlt.</i>"

# Mohó illesztés
mohó_minta = r"<.*>"
print(f"Mohó: {re.findall(mohó_minta, szöveg)}") # Kimenet: Mohó: ['<b>Ez egy félkövér szöveg.</b> <i>Ez dőlt.</i>']

# Nem mohó illesztés
nem_mohó_minta = r"<.*?>"
print(f"Nem mohó: {re.findall(nem_mohó_minta, szöveg)}") # Kimenet: Nem mohó: ['<b>', '</b>', '<i>', '</i>']

Fordítási Zászlók (Flags)

A re modul függvényei opcionálisan elfogadnak egy flags argumentumot, amely megváltoztatja a minta illesztésének viselkedését.

  • `re.IGNORECASE` (vagy `re.I`): Kis- és nagybetű figyelmen kívül hagyása az illesztés során.
  • `re.MULTILINE` (vagy `re.M`): A ^ és $ horgonyok illeszkednek minden sor elejére és végére (nem csak a string elejére/végére).
  • `re.DOTALL` (vagy `re.S`): A pont karakter (.) illeszkedik az újsor karakterre (n) is.
  • `re.VERBOSE` (vagy `re.X`): Lehetővé teszi a whitespace és kommentek használatát a regex mintában a jobb olvashatóság érdekében. (A backslash-elt whitespace-ek vagy a karakterosztályokban lévő whitespace-ek nem ignorálódnak.)
import re

szöveg = "HellonWorld"
print(re.search(r"^World", szöveg)) # Kimenet: None
print(re.search(r"^World", szöveg, re.M)) # Kimenet: <re.Match object; span=(6, 11), match='World'>

szöveg2 = "helló világ"
print(re.search(r"VILÁG", szöveg2)) # Kimenet: None
print(re.search(r"VILÁG", szöveg2, re.I)) # Kimenet: <re.Match object; span=(7, 12), match='világ'>

A `re.compile()` Függvény: Teljesítményoptimalizálás

Ha ugyanazt a reguláris kifejezést többször is használni fogjuk egy programon belül (pl. egy ciklusban, vagy több függvényben), érdemes előre lefordítani azt a re.compile() függvénnyel. Ez egy regex objektumot ad vissza, amelyen aztán meghívhatjuk a search(), match(), findall() stb. metódusokat. A fordítás egyszer történik meg, így a későbbi illesztések gyorsabbak lesznek.

import re
import time

szövegek = ["alma", "körte", "banán", "szőlő", "almafa"] * 10000

# Fordítás nélküli használat
start_time = time.time()
for szöveg in szövegek:
    re.search(r"alma", szöveg)
end_time = time.time()
print(f"Fordítás nélkül: {end_time - start_time:.4f} másodperc")

# Fordítással
compiled_minta = re.compile(r"alma")
start_time = time.time()
for szöveg in szövegek:
    compiled_minta.search(szöveg)
end_time = time.time()
print(f"Fordítással: {end_time - start_time:.4f} másodperc")

Láthatjuk, hogy nagyobb adathalmazok esetén a `re.compile()` jelentős teljesítményelőnyt jelenthet.

Gyakorlati Példák és Tippek

1. E-mail cím validálása vagy kivonatolása

Egy tipikus regex minta e-mail címekhez:

email_minta = r"b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Z|a-z]{2,}b"
teszt_email = "[email protected]"
if re.match(email_minta, teszt_email):
    print(f"Az '{teszt_email}' érvényes email cím.")
else:
    print(f"Az '{teszt_email}' érvénytelen email cím.")

Megjegyzés: Az e-mail címek szabályai rendkívül komplexek, egy „tökéletes” regex minta írása szinte lehetetlen. Ez a példa a leggyakoribb formátumokat fedi le.

2. Dátum formátumok keresése

Egy dátum (YYYY-MM-DD) keresése:

szöveg_dátum = "A megbeszélés dátuma: 2023-11-05."
dátum_minta = r"d{4}-d{2}-d{2}"
talált_dátum = re.search(dátum_minta, szöveg_dátum)
if talált_dátum:
    print(f"Talált dátum: {talált_dátum.group()}")

3. HTML tag-ek eltávolítása

Gyakori feladat a HTML tag-ek eltávolítása egy stringből. Mohó vs. nem mohó illesztés itt kulcsfontosságú!

html_szöveg = "<p>Ez egy <b>félkövér</b> szöveg.</p>"
tiszta_szöveg = re.sub(r"<.*?>", "", html_szöveg)
print(f"Tiszta szöveg: {tiszta_szöveg}") # Kimenet: Tiszta szöveg: Ez egy félkövér szöveg.

4. URL-ek kivonatolása

szöveg_url = "Látogasson el a https://www.example.com weboldalra, vagy a http://blog.site.org címre."
url_minta = r"https?://(?:www.)?[a-zA-Z0-9.-]+.[a-zA-Z]{2,}(?:/[^s]*)?"
talált_url = re.findall(url_minta, szöveg_url)
print(f"Talált URL-ek: {talált_url}")

Gyakori Hibák és Tippek a Hibakereséshez

  • Escape Karakterek („): A backslash karakterek különleges jelentéssel bírnak mind a Python stringekben, mind a reguláris kifejezésekben. Használjon nyers stringeket (`r”…”`) a regex mintákhoz, hogy elkerülje a dupla escape-elést (pl. `\` helyett `d`).
  • Mohó vs. Nem Mohó: Ha a regex túl sok vagy túl kevés karaktert illeszt, gondoljon arra, hogy a `*`, `+`, `?` alapértelmezésben mohóak. Használja a `?` utótagot (pl. `*?`) a nem mohó illesztéshez.
  • `re.match()` vs. `re.search()`: Ne feledje, hogy a `match()` csak a string elejéről keres illeszkedést, míg a `search()` a teljes stringet átvizsgálja.
  • Komplexitás: A túlságosan komplex regex minták nehezen olvashatók és karbantarthatók. Ha egy minta túl bonyolulttá válik, fontolja meg a re.VERBOSE flag használatát, vagy bontsa fel a feladatot több lépésre.
  • Online Regex Tesztelők: Használjon online eszközöket (pl. regex101.com, regexr.com) a minták tesztelésére és hibakeresésére. Nagyon hasznosak, mivel valós idejű visszajelzést adnak, és magyarázzák a minta egyes részeit.

Összefoglalás

A reguláris kifejezések Pythonban a re modulon keresztül rendkívül hatékony eszköztárat biztosítanak a szövegfeldolgozáshoz. Legyen szó adatok kivonatolásáról, validálásáról, kereséséről vagy cseréjéről, a regexek jelentősen leegyszerűsíthetik és felgyorsíthatják a munkát. Bár elsőre kissé ijesztőnek tűnhet a szintaxis, a metakarakterek és függvények megértése révén hamarosan folyékonyan használhatja ezt az erős nyelvet. Gyakorlással és a fent bemutatott eszközök és tippek segítségével a Python és a reguláris kifejezések kombinációja az egyik legértékesebb készsége lesz a programozói eszköztárában.

Ne habozzon, merüljön el a regexek világában – a Python várja, hogy kihasználja a benne rejlő lehetőségeket!

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük