Regulární výrazy jsou výkonným jazykem pro porovnávání textových vzorů. Tato stránka poskytuje základní úvod do samotných regulárních výrazů, který postačí pro naše cvičení v Pythonu, a ukazuje, jak regulární výrazy v Pythonu fungují. Podporu regulárních výrazů zajišťuje modul „re“ jazyka Python.

V jazyce Python se hledání regulárních výrazů obvykle zapisuje takto:

 match = re.search(pat, str)

Metoda re.search() přijímá vzor regulárního výrazu a řetězec a hledá tento vzor v řetězci. Pokud je hledání úspěšné, funkce search() vrátí objekt shody nebo None v opačném případě. Proto za hledáním obvykle ihned následuje příkaz if, který testuje, zda hledání proběhlo úspěšně, jak ukazuje následující příklad, který hledá vzor „word:“ následovaný třípísmenným slovem (podrobnosti níže):

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

Kód match = re.search(pat, str) ukládá výsledek hledání do proměnné s názvem „match“. Poté příkaz if testuje shodu — pokud je true, hledání uspělo a match.group() je odpovídající text (např. ‚slovo:cat‘). V opačném případě, pokud je shoda false (přesněji řečeno None), pak hledání neuspělo a neexistuje žádný odpovídající text.

Výraz ‚r‘ na začátku vzorového řetězce označuje pythonovský „surový“ řetězec, který beze změny prochází zpětnými lomítky, což je pro regulární výrazy velmi užitečné (Java tuto funkci nutně potřebuje!). Doporučuji psát vzorové řetězce vždy s ‚r‘ jen ze zvyku.

Základní vzory

Síla regulárních výrazů spočívá v tom, že mohou určovat vzory, nejen pevné znaky. Zde jsou nejzákladnější vzory, které odpovídají jednotlivým znakům:

  • a, X, 9, < — běžné znaky se prostě shodují přesně. Metaznaky, které se samy neshodují, protože mají speciální význam, jsou: . ^ $ * + ? { \ | ( ) (podrobnosti níže)
  • . (tečka) — odpovídá jakémukoli jednotlivému znaku kromě nového řádku ‚\n‘
  • \w — (malé w) odpovídá „slovnímu“ znaku: písmenu nebo číslici nebo podtržítku . Všimněte si, že ačkoli je „word“ mnemotechnickým označením, odpovídá pouze jednomu slovnímu znaku, nikoli celému slovu. \W (velké písmeno W) odpovídá jakémukoli neslovnímu znaku.
  • \b — hranice mezi slovem a neslovem
  • \s — (malé písmeno s) odpovídá jednomu bílému znaku — mezeře, novému řádku, návratu, tabulátoru, formuláři . \S (velké písmeno S) odpovídá jakémukoli znaku, který není bílým znakem.
  • \t, \n, \r — tabulátor, nový řádek, návrat
  • \d — desetinná číslice (některé starší regexové nástroje nepodporují ale \d, ale všechny podporují \w a \s)
  • ^ = začátek, $ = konec — odpovídá začátku nebo konci řetězce
  • \ — potlačuje „speciálnost“ znaku. Takže například použijte \. pro shodu s tečkou nebo \\ pro shodu s lomítkem. Pokud si nejste jisti, zda má znak zvláštní význam, například ‚@‘, můžete před něj vložit lomítko, \@, abyste se ujistili, že se s ním zachází jen jako se znakem.

Základní příklady

Vtip: Jak se říká praseti se třemi očima? piiig!

Základní pravidla hledání vzoru v řetězci pomocí regulárního výrazu jsou:

  • Hledání postupuje řetězcem od začátku do konce a zastaví se na první nalezené shodě
  • Musí se shodovat celý vzor, ale ne celý řetězec
  • Pokud je match = re.search(pat, str) úspěšný, shoda není None a zejména match.group() je odpovídající text
 ## 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"

Pakování

Zajímavější věci se stanou, když použijete + a * pro určení opakování ve vzoru

  • + — 1 nebo více výskytů vzoru nalevo od něj, např. ‚i+‘ = jedno nebo více i
  • * — 0 nebo více výskytů vzoru nalevo od něj
  • ? — shoda s 0 nebo 1 výskytem vzoru vlevo od něj

Největší vlevo & Největší

Nejprve vyhledá nejlevější shodu vzoru a poté se snaží využít co největší část řetězce — tj. + a * jdou co nejdále (říká se, že + a * jsou „chamtivé“).

Příklady opakování

 ## 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"

Emaily Příklad

Předpokládejme, že chcete najít e-mailovou adresu uvnitř řetězce ‚xyz [email protected] purple monkey‘. Tento příklad použijeme jako běžný příklad pro demonstraci dalších funkcí regulárních výrazů. Zde je pokus s použitím vzoru r’\w+@\w+‘:

 str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'

Vyhledávání v tomto případě nezíská celou e-mailovou adresu, protože \w neodpovídá znakům ‚-‚ nebo ‚.‘ v adrese. To napravíme pomocí níže uvedených funkcí regulárních výrazů.

Čtvercové závorky

Čtvercové závorky lze použít k označení množiny znaků, takže odpovídají ‚a‘ nebo ‚b‘ nebo ‚c‘. Kódy \w, \s atd. fungují i uvnitř hranatých závorek s jedinou výjimkou, že tečka (.) znamená pouze doslovnou tečku. V případě problému s e-maily jsou hranaté závorky snadným způsobem, jak přidat znaky ‚.‘ a ‚-‚ do množiny znaků, které se mohou objevit kolem znaku @ se vzorem r’+@+‘, a získat tak celou e-mailovou adresu:

 match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'

(Další funkce hranatých závorek) Můžete také použít pomlčku pro označení rozsahu, takže odpovídá všem malým písmenům. Chcete-li použít pomlčku bez označení rozsahu, vložte pomlčku jako poslední, např. Háček nahoru (^) na začátku sady hranatých závorek ji invertuje, takže znamená jakýkoli znak kromě ‚a‘ nebo ‚b‘.

Výběr skupiny

Funkce „skupiny“ regulárního výrazu umožňuje vybrat části odpovídajícího textu. Předpokládejme u problému s e-maily, že chceme extrahovat zvlášť uživatelské jméno a zvlášť hostitele. To provedeme tak, že kolem uživatelského jména a hostitele ve vzoru přidáme závorky ( ), například takto: r'(+)@(+)‘. V tomto případě závorky nemění to, co bude vzor porovnávat, místo toho vytvářejí logické „skupiny“ uvnitř textu porovnávání. Při úspěšném vyhledávání je match.group(1) text odpovídající 1. levé závorce a match.group(2) text odpovídající 2. levé závorce. Obyčejný match.group() je stále celý text shody jako obvykle.

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

Běžný pracovní postup s regulárními výrazy spočívá v tom, že napíšete vzor pro hledanou věc a přidáte skupiny závorek, abyste získali požadované části.

findall

findall() je pravděpodobně jediná nejmocnější funkce v modulu re. Výše jsme použili funkci re.search() k nalezení první shody vzoru. findall() najde *všechny* shody a vrátí je jako seznam řetězců, přičemž každý řetězec představuje jednu shodu.

 ## Suppose we have a text with many email addresses str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'+@+', str) ## for email in emails: # do something with each found email string print email

findall With Files

Pro soubory můžete mít ve zvyku psát smyčku pro iteraci řádků souboru a pak byste mohli volat funkci findall() na každém řádku. Místo toho nechte funkci findall() provést iteraci za vás — je to mnohem lepší! Stačí do funkce findall() vložit celý text souboru a nechat ji vrátit seznam všech shod v jediném kroku (připomeňme, že f.read() vrací celý text souboru v jediném řetězci):

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

findall a skupiny

Mechanismus skupinových závorek ( ) lze kombinovat s funkcí findall(). Pokud vzor obsahuje 2 nebo více skupin závorek, pak funkce findall() místo seznamu řetězců vrátí seznam *párů*. Každý tuple představuje jednu shodu vzoru a uvnitř tuplu je údaj group(1), group(2) …. Pokud jsou tedy k e-mailovému vzoru přidány 2 skupiny v závorce, pak funkce findall() vrátí seznam tuplů, z nichž každý o délce 2 obsahuje uživatelské jméno a hostitele, například (‚alice‘, ‚google.com‘).

 str = 'purple [email protected], blah monkey [email protected] blah dishwasher' tuples = re.findall(r'(+)@(+)', str) print tuples ## for tuple in tuples: print tuple ## username print tuple ## host

Jakmile máte seznam tuplů, můžete nad ním projít smyčkou a provést nějaký výpočet pro každý tupl. Pokud vzor neobsahuje závorky, funkce findall() vrátí seznam nalezených řetězců jako v předchozích příkladech. Pokud vzor obsahuje jedinou skupinu závorek, funkce findall() vrátí seznam řetězců odpovídajících této jediné skupině. (Nepříliš jasná volitelná funkce: Někdy se ve vzoru vyskytují skupiny se závorkami ( ), které však nechcete extrahovat. V takovém případě zapište na začátek závorky znak ?:, např. (?: ), a tato levá závorka se nebude počítat jako výsledek skupiny.“

RE Workflow and Debug

Vzorce pravidelných výrazů vměstnají spoustu významu do pouhých několika znaků , ale jsou tak husté, že můžete strávit spoustu času laděním vzorců. Nastavte běhové prostředí tak, abyste mohli vzor snadno spustit a vypsat, čemu odpovídá, například tak, že jej spustíte na malém testovacím textu a vypíšete výsledek funkce findall(). Pokud vzor neodpovídá ničemu, zkuste vzor oslabit, odstranit jeho části, abyste získali příliš mnoho shod. Když se nic neshoduje, nemůžete nijak pokročit, protože nemáte nic konkrétního, na co byste se mohli podívat. Jakmile se shoduje příliš mnoho, můžete pracovat na jeho postupném zpřísňování, abyste se trefili právě do toho, co chcete.

Možnosti

Funkce re přijímají možnosti, které upravují chování shody vzoru. Příznak volby se přidává jako další argument do funkce search() nebo findall() atd., např. re.search(pat, str, re.IGNORECASE).

  • IGNORECASE — ignoruje rozdíly mezi velkými a malými písmeny při porovnávání, takže ‚a‘ odpovídá jak ‚a‘, tak ‚A‘.
  • DOTALL — povolí, aby tečka (.) odpovídala novému řádku — normálně odpovídá všemu kromě nového řádku. To vás může zmást — myslíte si, že .* odpovídá všemu, ale ve výchozím nastavení nepřekračuje konec řádku. Všimněte si, že \s (bílé znaky) zahrnuje nové řádky, takže pokud chcete porovnat běh bílých znaků, který může obsahovat nový řádek, můžete prostě použít \s*
  • MULTILINE — V řetězci složeném z mnoha řádků povolte ^ a $ pro porovnání začátku a konce každého řádku. Normálně by ^/$ odpovídalo jen začátku a konci celého řetězce.

Šetrné vs. nešetrné (nepovinné)

Tato nepovinná část ukazuje pokročilejší techniku regulárních výrazů, která není pro cvičení potřeba.

Předpokládejme, že máte text se značkami: <b>foo</b> a <i>také</i>

Předpokládejme, že se snažíte porovnat každou značku se vzorem „(<.*>)“ — čemu se to porovná jako prvnímu?

Výsledek je trochu překvapivý, ale chamtivý aspekt .* způsobí, že se porovná celý ‚<b>foo</b> a <i>také</i>‘ jako jedna velká shoda. Problém je v tom, že .* jde tak daleko, jak jen může, místo aby se zastavil na prvním > (čili je „chamtivý“).

Existuje rozšíření regulárních výrazů, kdy se na konec přidá znak ?, například .*? nebo .+?, čímž se změní na ne-chamtivé. Nyní se zastaví, jakmile to bude možné. Takže vzor ‚(<.*?>)‘ dostane jako první shodu právě ‚<b>‘ a jako druhou shodu ‚</b>‘ a tak dále, přičemž dostane postupně každou dvojici <..>. Styl je obvykle takový, že použijete příponu .*? a pak hned vpravo od ní hledáte nějakou konkrétní značku (v tomto případě >), která si vynutí konec běhu .*?

Přípona *? vznikla v Perlu a regulární výrazy, které obsahují rozšíření Perlu, se nazývají Perl Compatible Regular Expressions — pcre. Python obsahuje podporu pcre. Mnoho nástrojů příkazového řádku atd. má příznak, že akceptují vzory pcre.

Starší, ale hojně používaná technika kódování této myšlenky „všechny tyto znaky kromě zastavení na X“ používá styl hranatých závorek. Pro výše uvedené můžete zapsat vzor, ale místo .* pro získání všech znaků použijte *, které přeskočí všechny znaky, které nejsou > (úvodní ^ „převrátí“ sadu hranatých závorek, takže se shoduje s libovolným znakem, který není v závorce).

Zastoupení (nepovinné)

Funkce re.sub(pat, replacement, str) vyhledá všechny výskyty vzoru v daném řetězci a nahradí je. Nahrazovací řetězec může obsahovat ‚\1‘, ‚\2‘, které odkazují na text ze skupiny(1), skupiny(2) atd. z původního odpovídajícího textu.

Tady je příklad, který vyhledá všechny e-mailové adresy a změní je tak, aby byl zachován uživatel (\1), ale jako hostitel byl uveden yo-yo-dyne.com.

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

Cvičení

Pro procvičení regulárních výrazů viz Cvičení na dětská jména.

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

Cvičení na dětská jména.

admin

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.

lg