<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="/cjs/screen.xsl" media="screen"?>
<lecture>

<meta>
  <maintitle>Python</maintitle>
  <author>Jiří Znamenáček</author>
  <title>Regulární výrazy</title>
  <date>2011-10-27</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
</meta>
<!--
  „“–
  ←→ ↑↓ ↔↕
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->


<slide title="Úvod">

  <p>
    <strong>Regexpy</strong> uvnitř Python'u můžeme použít v zásadě ke dvěma rozdílným operacím:
  </p>
  <ul>
    <li>
        zjišťování informací o řetězci
        <note>Typicky například zda vstupní řetězec vyhovuje předepsané struktuře a podobně.</note>
    </li>
    <li>
        úprava (obsahu) řetězců
        <note>Používáme hlavně všude tam, kde vlastní metody typu <em>řetězec</em> již neposkytují potřebnou funkcionalitu.</note>
    </li>
  </ul>
  
  <hr/>
  
  <p>
    Poznámka: Následující slajdy předpokládají alespoň základní znalost regexpů. Pro rychlé zopakování či seznámení se se základními principy viz <a href="/materialy/regexp/examples.xml">tuto přednášku</a>.
  </p>

</slide>
<slide title="Modul „re“">

  <p>
    Regexpové nástroje jsou v Python'u uschovány v modulu <code>re</code>. Ten definuje především:
  </p>
  <ul>
    <li>
        <strong>konstanty</strong> – definice kompilačních příznaků, tedy např. <em>re.I</em> aka <em>re.IGNORECASE</em> apod.
    </li>
    <li>
        <strong>metody</strong> – vlastní metody pro aplikaci regexpů na vstupní řetězce, jako např. <em>re.finditer()</em>
    </li>
  </ul>
  <p>
    Kromě toho je v něm definována regexpová výjimka <em>re.error</em> a další podobné záležitosti.
  </p>

</slide>
<slide title="Kompilace regexpů – compile()">

  <p>
    Ač můžeme regepxy používat tak říkajíc „přímo“, mnohem častější (zvláště u opakovaně používaných regexpů) je jejich zavedení v podobě předkompilovaného objektu (tzv. <em>pattern object</em>), operace na němž jsou rychlejší (právě proto, že řetězcový popis regexpu byl již převeden do interní podoby uzpůsobené vyhledávání vzorů).
  </p>
  <p>
    Ke kompilaci slouží metoda <em>re.compile()</em>. Její použití je následující:
  </p>
  <example lang="python">
    # bez kompilačních příznaků
    pattern = re.compile(r'&lt;([a-zA-Z]\w*).*?>')
    
    # s kompilačními příznaky
    pattern = re.compile(r"&lt;([a-z]\w*).*?>", re.IGNORECASE | re.M)
  </example>
  <notes>
    <note>
        Označením řetězců jako <em>raw</em> (tj. použitím <code>r''</code> nebo <code>r""</code>) se vyhneme problémům s vyhodnocováním některých sekvencí znaků (typicky všechny uvozené zpětným lomítkem <code>\</code>) jiným způsobem, než bychom mohli zrovna potřebovat.
    </note>
    <note>
        Kompilační příznaky se skládají dohromady pomocí operátoru <code>|</code> binárního <em>or</em>. Každá z možných konstant totiž nastavuje svůj příslušný jeden bit na celkovém kompilačním příznaku.
    </note>
  </notes>
  <p>
    Na takto zavedeném objektu <em>pattern</em> máme k dispozici celou armádu regexpových metod. S vybranými se seznámíme na následujících slajdech.
  </p>

</slide>
<slide title="Vyhledávání – finditer()">

  <p>
    Nejpřirozenějším python-trojkovským způsobem, jak v zadaném řetězci vyhledat všechny (nepřekrývající se) výskyty podřetězce zadaného daným regexpem, je použít metodu <strong>finditer()</strong>. Najde-li tato vyhovující zásahy (v podobě <em>match</em>-objektů), vrátí nad nimi <a href="/materialy/python/iterators/iterators.xml">iterátor</a>, takže je můžeme snadno prozkoumat například pomocí smyčky <em>for-in</em>:
  </p>
  <example layout="vertical">
    <program src="_files/re_finditer.py" lang="python"/>
    <out src="_files/re_finditer.out" lang="text"/>
  </example>
  <notes>
      <note>
        Uvedený regexp hledá názvy HTML-elementů v zadaném řetězci. Takové jsou v něm právě dva, <em>p</em> a <em>em</em>, proto jsou na výstupu ukázány dva <em>SRE_Match</em>-objekty.
      </note>
      <note>
        Funkci můžeme v úplnosti volat <code>finditer(string, pos, endpos)</code>, kde nepovinný druhý a třetí parametr určují, odkud až kam bude řetězec prohledáván.
      </note>
  </notes>

</slide>
<slide title="Objekty zásahu">

  <p>
    Návratové <em>match</em>-objekty z metody <em>finditer()</em> mají svoji poměrně složitou vnitřní strukturu. Podívejme se jim trochu na zoubek:
  </p>
  <example layout="vertical">
    <program src="_files/match-object.py" lang="python"/>
    <out src="_files/match-object.out" lang="text"/>
  </example>
  <p>
    Tři použité metody mají zjevně následující význam:
  </p>
  <ul>
    <li>
        <strong>group()</strong> – zachycený podřetězec vyhovující danému regexpu
    </li>
    <li>
        <strong>span()</strong> – pozice umístění zásahu v celém textu
        <note>Oba údaje se dají získat i samostatně pomocí metod <em>start()</em> a <em>end()</em>.</note>
    </li>
    <li>
        <strong>groups()</strong> – n-tice podskupin regexpu
        <note>Náš regexp obsahuje jednu podskupinu – název elementu.</note>
    </li>
  </ul>
  <p>
    Poznámka: Objekt zásahu má vlastností daleko více, než jsme si zde ukázali.
  </p>

</slide>
<slide title="Vyhledávání – findall()">

  <p>
    Na první pohled se funkce <em>findall()</em> tváří jako klasická neiterátorová varianta k funkci <em>finditer()</em>, která prostě mímo iterátoru vrací starý klasický seznam všech zásahů. Jenže to není tak úplně pravda, protože její výstup se liší podle toho, zda regexp obsahuje nebo neobsahuje podskupiny.
  </p>
  <note>
    Funkci můžeme v úplnosti volat <code>findall(string, pos, endpos)</code>, kde nepovinný druhý a třetí parametr určují, odkud až kam bude řetězec prohledáván.
  </note>
  
  <p class="enumerate">
    Bez podskupin se nám vrátí seznam všech podřetězců vyhovujících danému regexpu:
  </p>
  <example layout="vertical">
    <program src="_files/re_findall.1.py" lang="python"/>
    <out src="_files/re_findall.1.out" lang="text"/>
  </example>
  <p class="enumerate">
    S podskupinami se nám vrátí seznam n-tic všech nalezených podskupin z podřetězců vyhovujících danému regexpu:
  </p>
  <example layout="vertical">
    <program src="_files/re_findall.2.py" lang="python"/>
    <out src="_files/re_findall.2.out" lang="text"/>
  </example>

</slide>
<slide title="Vyhledávání – search()">

  <p>
    Pokud nás zajímá pouze první zásah (nebo ekvivalentně zda se daný regexp v řetězci prostě vůbec někde vyskytuje), použijeme nejspíše funkci <em>search()</em>:
  </p>
  <example layout="vertical">
    <program src="_files/re_search.py" lang="python"/>
    <out src="_files/re_search.out" lang="text"/>
  </example>
  <notes>
      <note>
        Není-li nalezen žádný zásah, bude vrácena hodnota <code>None</code>.
      </note>
      <note>
        Funkci můžeme v úplnosti volat <code>search(string, pos, endpos)</code>, kde nepovinný druhý a třetí parametr určují, odkud až kam bude řetězec prohledáván.
      </note>
  </notes>

</slide>
<slide title="Vyhledávání – match()">

  <p>
    Velmi podobně jako <em>search()</em> se chová metoda <em>match()</em>. Jediným rozdílem oproti <em>search()</em> je, že hledá zásahy pouze od začátku prohledávaného textu, takže pro stejný příklad jako na předchozím slajdu nenajde nic:
  </p>
  <example layout="vertical">
    <program src="_files/re_match.py" lang="python"/>
    <out src="_files/re_match.out" lang="text"/>
  </example>
  <notes>
      <note>
        Typicky se tedy <em>match()</em> používá, pokud má zkoumaný řetězec vyhovovat nějaké předepsané struktuře, kterou právě daný regexp kontroluje.
      </note>
      <note>
        Funkci můžeme v úplnosti volat <code>match(string, pos, endpos)</code>, kde nepovinný druhý a třetí parametr určují, odkud až kam bude řetězec prohledáván.
      </note>
  </notes>

</slide>
<slide title="Více o skupinách zásahů">

  <p>
    Podívejme se podrobněji na práci s návratovým <em>objektem zásahu</em>. V následujícím příkladu se pokusíme rozložit vstupní adresu na jednotlivé významové prvky:
  </p>
  <example layout="vertical">
    <program src="_files/re_groups.1.py" lang="python"/>
    <out src="_files/re_groups.1.out" lang="text"/>
  </example>
  <p>
    Vidíme, že máme k dispozici standardní přehledy zásahů <em>group()</em> a <em>groups()</em> a navíc, jelikož jsme v regexpu zavedli <a href="/materialy/regexp/extensions.xml?slajd=7">pojmenované skupiny</a>, máme k dispozici i jejich velmi šikovný slovník:
  </p>
  <ul>
    <li>
        <code>groupdict()</code> – slovník jmen pojmenovaných skupin jako klíčů a jim odpovídajících podřetězců jako hodnot
    </li>
  </ul>
  <p>
    Poznámka: Metody <em>groups()</em> a <em>groupdict()</em> se dají zavolat s jedním nepovinným parametrem <em>default</em>. Ten se uplatní v případě, že některá z hledaných skupin se v řetězci nevyskytla – bez jeho uvedení bude taková skupina ve výpise nahrazena hodnotou <code>None</code>, s jeho uvedením právě jeho hodnotou.
  </p>

</slide>
<slide title="Použití metody „group()“">

  <p>
    Zůstaňme ještě chvíli u předchozího příkladu a podívejme se více na metodu <em>group()</em>:
  </p>
  <example layout="vertical">
    <program src="_files/re_groups.2.py" lang="python"/>
    <out src="_files/re_groups.2.out" lang="text"/>
  </example>
  <p>
    Zjevně platí:
  </p>
  <ul>
    <li>
        <code>group()</code> zavolaná bez parametru je totéž, jako volání <code>group(0)</code>. Přitom vrácenou hodnotou je řetězec zachycený celým regexpem.
    </li>
    <li>
        Předáním pořadového čísla podskupiny (např. <code>group(1)</code>), resp. odpovídajícího jména (např. <code>group('host')</code>), zavedli-li jsme skupiny jako pojmenované, nám metoda vrátí pouze odpovídající řetězec zásahu.
    </li>
    <li>
        Pořadových čísel, respektive jmen, podskupin můžeme metodě <em>group()</em> poslat více – vrátí se nám pak n-tice odpovídajících zásahů.
    </li>
  </ul>
  <p>
    Poznámka: Nebyla-li příslušná skupina zachycena, vrátí se místo ní <code>None</code>. Pokus o získání neexistující skupiny vyvolá výjimku <code>IndexError</code>.
  </p>

</slide>
<slide title="Modifikace – split()">

  <p>
    Podobně jako řetězcová metoda <em>split()</em> i regexpová varianta slouží k rozdělení řetězce na místech určených svým argumentem. Jen pochopitelně pro regexpovou metodu je argumentem regexp, takže je mnohem flexibilnější a mocnější.
  </p>
  <note>
    V úplnosti se funkce volá jako <code>split(string, maxsplit=0)</code>, kde druhý parametr určuje, ke kolika nejvýše rozdělením má dojít.
  </note>
  <p>
    Podobně jako funkce <em>findall()</em> se její návratové hodnoty poněkud liší podle toho, obsahuje-li regexp podskupiny nebo ne.
  </p>
  
  <p class="enumerate">
    Bez podskupin se nám vrátí seznam podřetězců oddělených podle částí vyhovujících danému regexpu:
  </p>
  <example layout="vertical">
    <program src="_files/re_split.1.py" lang="python"/>
    <out src="_files/re_split.1.out" lang="text"/>
  </example>
  <p class="enumerate">
    S podskupinami se nám vrátí stejný seznam, ale navíc včetně těchto oddělovačů:
  </p>
  <example layout="vertical">
    <program src="_files/re_split.2.py" lang="python"/>
    <out src="_files/re_split.2.out" lang="text"/>
  </example>

</slide>
<slide title="Modifikace – sub()">

  <p>
    Podobně jako u předchozí metody <em>split()</em> je metoda <em>sub()</em> regexpovým zobecněním řetězcových metod <em>replace()</em> a <em>translate()</em>. Jinými slovy – kde nebudou pro záměnu stačit (podstatně rychlejší) řetězcové metody se svými pevnými vstupními argumenty, tam použijeme <em>sub()</em> s regexpovým vstupem:
  </p>
  <example layout="vertical">
    <program src="_files/re_sub.py" lang="python"/>
    <out src="_files/re_sub.out" lang="text"/>
  </example>
  <notes>
      <note>
        Regexp jsme zde použili pro vyhledání elementů, které nevyhovují pravidlům pro pojmenovávání, a jejich nahrazení řetězcem <code>XXX</code>.
      </note>
      <note>
        Úplné volání funkce jest <code>sub(repl, string, count=0)</code>, kde nepovinný parametr <em>count</em> určuje nejvyšší počet výměn, ke kterým může dojít.
      </note>
  </notes>

</slide>
<slide title="Modifikace – sub() a skupiny">

    <p class="enumerate">
        V nahrazovaném textu můžete použít i reference na pojmenované skupiny, a to ve tvaru <code>\g&lt;JMÉNO&gt;</code>:
    </p>
    <example layout="vertical">
        <program src="_files/re_sub_group.2.py" lang="python"/>
        <out src="_files/re_sub_group.2.out" lang="text"/>
    </example>

    <p class="enumerate">
        U reference podle čísel skupin je tvar stejný – také dlouze <code>\g&lt;ČÍSLO&gt;</code>:
    </p>
    <example layout="vertical">
        <program src="_files/re_sub_group.1.py" lang="python"/>
        <out src="_files/re_sub_group.1.out" lang="text"/>
    </example>
    <note>
        To proto, aby se mohly správně vyhodnotit záludnosti typu <code>\g&lt;2&gt;0</code>, které by ve tvaru <code>\20</code> hledaly skupinu číslo 20 (a ne požadovanou 2).
    </note>

</slide>
<slide title="Modifikace – subn()">

  <p>
    Úplně totéž jako <em>sub()</em> zařídí i metoda <em>subn()</em>, pouze spolu s pozměněným řetězcem vrátí v podobě dvojce i počet záměn, ke kterým došlo:
  </p>
  <example layout="vertical">
    <program src="_files/re_subn.py" lang="python"/>
    <out src="_files/re_subn.out" lang="text"/>
  </example>
  <note>
    Úplné volání funkce jest <code>subn(repl, string, count=0)</code>, kde nepovinný parametr <em>count</em> určuje nejvyšší počet výměn, ke kterým může dojít.
  </note>

</slide>
<slide title="Možnosti zápisu složitějších regexpů">

  <p>
    Jelikož regexpy se mohou velmi snadno stát značně složitými a zcela nepřehlednými, vyplatí se vědět, jak si v takovém případě poradit. Srovnejme klasický způsob parsování numerické entity z XML..
  </p>
  <example lang="python">
    charref = re.compile("&amp;#(0[0-7]+|[0-9]+|x[0-9a-fA-F]+);")
  </example>
  <p>
    ..se dvěma alternativními možnými zápisy jinak úplně stejného regexpu.
  </p>
  
  <p class="enumerate">
    Předně si můžete rozložit složitější regexp na logické podčásti a využít pythoního triku s automatickým skládáním řetězců:
  </p>
  <example lang="python">
    charref = re.compile("&amp;#(0[0-7]+"
                         "|[0-9]+"
                         "|x[0-9a-fA-F]+);")
  </example>
  
  <p class="enumerate">
    Druhou možností pak je využít přímo regexpí možnost <strong>zápisu pomocí uvolněné syntaxe</strong> (aka <em>verbose mode</em>). V tomto módu jsou totiž ignorovány všechny bílé znaky, pokud nejsou součástí <em>skupiny znaků</em> <code>[]</code> nebo pokud nejsou uvozeny <em>zpětným lomítkem</em> (jehož speciální význam ovšem není zrovna dalším zpětným lomítkem zrušen), plus se v něm dají psát klasické komentáře pomocí znaku <code>#</code>:
  </p>
  <example lang="python">
    charref = re.compile(r"""
      &amp;[#]                # začátek numerické entity
      (
         0[0-7]+         # číslo oktalově
       | [0-9]+          # číslo decimálně
       | x[0-9a-fA-F]+   # číslo hexadecimálně
      )
      ;                   # středník ukončující numerickou entitu
    """, re.VERBOSE)
  </example>
  <note>
    Zdroj: <a href="http://docs.python.org/py3k/howto/regex.html" class="external">Python Regular Expression HOWTO</a>
  </note>

</slide>
<slide title="Zkrácený přístup k metodám">

  <p>
    Z praktických důvodů se dají všechny výše uvedené metody zavolat nikoli až na <em>zkompilovaném objektu vzorů</em>, nýbrž už přímo na úrovni modulu <em>re</em>. Pochopitelně jim pak ještě musíme nějak předhodit samotný regexp a případné kompilační příznaky. Děje se tak v podobě prvního parametru pro regexp a posledního pojmenovaného (<em>flags</em>) pro příznaky:
  </p>
  <ul>
    <li>
        <code>re.finditer(pattern, string, flags=0)</code>
    </li>
    <li>
        <code>re.findall(pattern, string, flags=0)</code>
    </li>
    <li>
        <code>re.search(pattern, string, flags=0)</code>
    </li>
    <li>
        <code>re.match(pattern, string, flags=0)</code>
    </li>
    <li>
        <code>re.split(pattern, string, maxsplit=0, flags=0)</code>
    </li>
    <li>
        <code>re.sub(pattern, repl, string, count=0, flags=0)</code>
    </li>
    <li>
        <code>re.subn(pattern, repl, string, count=0, flags=0)</code>
    </li>
  </ul>

</slide>
<slide title="PS: Překrývající se vzory">

    <p>
        Někdy můžete potřebovat najít skutečně všechny výskyty nějakého vzoru, a to i když se ve zdrojovém textu překrývají. Jako například pětipísmenný podřetězec začínající na znaky <code>abc</code> v řetězci <code>'abcabc12345'</code>. Obě odpovědi jsou jasné – <code>'abcab'</code> a <code>'abc12'</code>. Nicméně knihovní modul <em>re</em> si to nemyslí:
    </p>
    <example layout="vertical">
        <program src="_files/overlap.1.py" lang="python"/>
        <out src="_files/overlap.1.out" lang="text"/>
    </example>

    <p class="enumerate">
        Myšlenkově nejsnažší je použít alternativní regexpový modul <a href="https://pypi.org/project/regex/" class="external"><em>regex</em></a>, který na svých metodách <em>findall()</em> a <em>finditer()</em> podporuje přepínač pro vyhledávání překrývajících se vzorů:
    </p>
    <example layout="vertical">
        <program src="_files/overlap.2.py" lang="python"/>
        <out src="_files/overlap.2.out" lang="text"/>
    </example>

    <p class="enumerate">
        Jste-li omezeni na standardní knihovnu, můžete využít <a href="https://stackoverflow.com/questions/5616822/python-regex-find-all-overlapping-matches" class="external">triku</a> s <a href="/materialy/regexp/extensions.xml?slajd=3">vyhlížením</a>:
    </p>
    <example layout="vertical">
        <program src="_files/overlap.3.py" lang="python"/>
        <out src="_files/overlap.3.out" lang="text"/>
    </example>
    <note>
        Tento kód doslova a dopísmene hledá „nic“ před vyhlížecím vzorem (tedy ono nic mezi <code>r'</code> a <code>(?=</code>) a prázdné množiny se technicky vzato nepřekrývají, takže poslušně najde všechny výskyty vyhlížecího vzoru, který je označen jako skupina a probublá tudíž do výsledných zásahů.
    </note>

</slide>
<slide title="Poznámka">

  <p>
    Vidíme, že regexpy jsou velmi mocný nástroj pro práci s řetězci. Ale jak řekl klasik:
  </p>
  <blockquote>
    Programátor vidí problém a řekne si: „Ha, na to použiji regexpy!“<br/>V tu chvíli má problémy dva.
  </blockquote>
  <note>
    Aplaus! Aplaus! :D
  </note>
  <p>
    Jinak řečeno: Rozumíte-li regexpům a umíte-li je, jako když bičem mrská, směle do nich. Není-li tomu tak, připravte se na spoustu frustrujících okamžiků, kdy zdánlivě zcela jasný regexp bude dělat něco úplně jiného, než jste od něj čekali ^_^
  </p>

</slide>


</lecture>
