Hlavní navigace

Létající cirkus (12)

31. 5. 2002
Doba čtení: 9 minut

Sdílet

Dnešní díl našeho povídání o jazyce Python věnujeme zpracování textů. Povíme si více o řetězcích, o podpoře Unicode a nakonec i o regulárních výrazech.

Řetězce

Jak již bylo řečeno v jednom z prvních dílů tohoto seriálu, řetězce jsou typem neměnným, což znamená, že jejich obsah nelze změnit. Nový řetězec můžeme vytvořit složením z jiných řetězců. Řetězce jsou sekvence, takže pro jejich indexování můžeme použít klasické postupy včetně slice konstrukcí. Řetězce zapisujeme mezi jednoduché, popřípadě dvojité uvozovky. Uvnitř řetězců můžeme používat escape sekvence jako v jazyce C.

Každý řetězec má i několik metod, které umožňují s ním jednoduše pracovat. Pokud metoda nějakým způsobem mění řetězec, vrací vždy novou hodnotu. Mezi tyto metody patří hlavně tyto:

  • Metody pro změnu velikosti písmen – capitalize(), lower(), upper(), title()
  • Metody pro zarovnání řetězců – ljust(), rjust(), center() – přejímají jeden argument – šířku pole, do kterého se má řetězec zarovnat
  • Metody pro dotazování řetězce – mezi tyto metody patří především vyhledávací metody count(), find(), index() a rfind() a metody, které vrací 1, odpovídá-li řetězec nějaké vlastnosti: isalnum(), isalpha() apod.
  • Metody pro náhradu znaků – replace() a translate()

Řetězce mají ještě dvě užitečné a často používané metody: split() a join(). Metoda split rozdělí řetězec na sekvenci, přičemž jako oddělovač prvků bere první argument. Nepředáme-li jí žádný argument, použije se jako oddělovač libovolný „bílý“ znak. Metoda join() je metodou inverzní, to znamená, že provádí pravý opak. Jako argument přejímá sekvenci a její prvky spojí v řetězec za použití řetězce jako oddělovače:

>>> s = 'Vitejte na serveru root.cz'
>>> s.split()
['Vitejte', 'na', 'serveru', 'root.cz']
>>> s.split('e')
['Vit', 'jt', ' na s', 'rv', 'ru root.cz']
>>> l = ['Python', 'only', 'Python']
>>> ' '.join(l)
'Python only Python'
>>> ' '.join('Python 2.2')
'P y t h o n   2 . 2'

Na posledním řádku můžete vidět efektní způsob, jak „prostrkat“ řetězec. Takto se třeba mohou generovat elegantní nadpisy pro WWW stránky apod.

Unicode řetězce

Řetězce v kódování Unicode můžete vytvořit zapsáním písmena u před řetězec. Unicode řetězec lze také získat voláním interní funkce unicode(). Této funkci můžeme předat libovolný řetězec a kódování, ve kterém je, a Python z něj vytvoří Unicode řetězec. Pokud kódování nezadáme, použije se implicitní kódování tak, jak bylo iniciováno funkcí sys.setdefaulten­coding().

>>> unicode('ř', 'iso8859-2')
u'\u0159'
>>> unicode('ř', 'iso8859-1')
u'\xf8'
>>> unicode('ř', 'ascii')

V prvním případě vytvoříme ze znaku ‚ř‘ (hodnota 0×F8) Unicode řetězec za použití „našeho“ kódování ISO8859–2. Podíváme-li se do tabulky kódů Unicode, zjistíme, že hodnota 0×0159 opravdu odpovídá malému ř. Použijeme-li jiné kódování, dojde k chybné interpretaci znaků (viz druhý příklad, kdy našemu písmenu ‚ř‘ v kódování ISO8859–1 odpovídá písmeno ‚ó‘ s hodnotou 0×00F8, ve třetím případě interpret dokonce odmítl znak ‚ř‘ zkonvertovat, protože kódování ASCII používá pouze hodnoty 0×00 až 0×7F).

Namísto funkce unicode() můžeme použít i metodu decode() libovolného řetězce. Ta „dekóduje“ data v určitém kódování a vrátí výsledný Unicode řetězec, který odpovídá původnímu řetězci. Metodu decode() podporují pouze obyčejné řetězce, u Unicode ztrácí význam, protože data jsou již v univerzálním formátu:

>>> 'ř'.decode('iso8859-2')
u'\u0159'
>>> 'příšera'.decode('iso8859-2')
u'p\u0159\xed\u0161era'

Pro převedení Unicode, ale i obyčejného řetězce, do nějakého kódování použijeme metodu encode(), která je pravým opakem metody decode(), vezme data v univerzálním formátu a vrátí je v daném kódování:

>>> 'příšera'.encode('utf-7')
'p+AVkA7QFh-era'

>>> 'příšera'.encode('base64')
'cPjtuWVyYQ==\n'

Metodu encode() používá i funkce str() nebo příkaz print při tisku Unicode řetězců. V případě, že máte nastaveno implicitní kódování, nemusíte uvádět jméno kódování, rutiny Pythonu automaticky použijí toto kódování. Všechna jména kódování je doporučeno uvádět malými písmeny. Python podporuje mnoho kodeků pro konvertování řetězců, kromě jmenovaných i utf-7, utf-8, utf-16, base64, hex nebo zlib, přičemž můžete dopsat kodeky vlastní.

Regulární výrazy

Python pro práci s regulárními výrazy nezavádí žádnou novou syntaxi, vše řeší „svojí“ cestou – za použití modulů. Tento modul se jmenuje re a obsahuje několik funkcí a konstant, které jsou užitečné pro práci s regulárními výrazy. Modul re používá regulární výrazy ve stylu jazyka Perl. Existuje i modul regex, který používá výrazy programu Emacs, nicméně tento modul již byl dávno zavržen a jeho používání v nových programech není doporučováno. Kvůli omezenému prostoru si ukážeme pouze jednoduché aplikace regulárních výrazů. Pro více informací si přečtěte Regular Expresion HOWTO, které spolu s dalšími HOWTO jazyka Python najdete na http://www.pyt­hon.org/doc/how­to.

První funkcí, se kterou se setkáte, je funkce re.match(), která má dva argumenty – regulární výraz a řetězec. Odpovídají-li znaky na začátku řetězce regulárnímu výrazu, vrátí objekt, který umožňuje získat další informace o výsledku, jestliže si neodpovídají, vrátí None. Podobnou funkcí, která ovšem prohledává celý řetězec, je re.search().

>>> import re

>>> print re.match('a[0-9]+', 'abcd')
None
>>> print re.match('a[0-9]+', 'a01234')
<_sre.SRE_Match object at 0x83626a0>
>>> print re.match('a[0-9]+', 'a0123b')
<_sre.SRE_Match object at 0x83633b8>

Výsledný objekt nám umožní získat další informace o výsledku hledání. Přiřaďme tedy výsledek posledního volání funkce re.match() nějaké proměnné:

>>> m = re.match('a[0-9]+', 'a0123b')

Výsledek prohledávání obsahuje některé užitečné informace, především můžeme zjistit, kde hledaný podřetězec začíná a kde končí:

>>> m.start()
0
>>> m.end()
5

Obrovským přínosem ale je používání skupin. Skupiny v regulárním výrazu vyznačíme párem závorek. Třeba pro vyextrahování uživatelského jména a domény z e-mailové adresy můžeme napsat tento regulární výraz:

>>> r = re.match('([a-zA-Z0-9_.]+)@([a-zA-Z0-9_.]+)', 'honza@py.cz')
>>> r.group(1)
'honza'

>>> r.group(2)
'py.cz'

Skupina s číslem 0 je celý řetězec, který odpovídá regulárnímu výrazu. Je-li index skupiny mimo platný rozsah, dojde k výjimce IndexError. Python umožňuje jednotlivé skupiny pojmenovat pomocí syntaxe (?P<name>…):

>>> r = re.match('(?P<user>[a-zA-Z0-9_.]+)@(?P<host>[a-zA-Z0-9_.]+)',
...              'honza@py.cz')
>>> r.group('user')
'honza'

>>> r.group('host')
'py.cz'

Spolu s indexováním skupin pomocí řetězců můžeme stále používat i indexování celým číslem, čili r.group(1) je totéž co r.group(‚user‘).

Funkce re.search() se neomezuje pouze na začátek řetězce, ale prohledává řetězec celý. Pro nalezení e-mailové adresy můžeme psát:

>>> r = re.search(r'([\w.]+@[\w.]+)', 'Adresa honza@py.cz ...')
>>> r.group(1)
'honza@py.cz'

Jak vidíme, regulární výraz pracoval správně. Všimněte si především regulárního výrazu, který je zapsán jako raw řetězec. Vyhneme se tak zdvojení zpětných lomítek (první lomítko patří k množině znaků \w a druhým bychom museli uvést první, aby interpret escape sekvenci \w nenahradil odpovídajícím znakem). Ve výše uvedeném příkladě jsme použili množinu znaků \w, v Pythonu však můžete používat i další:

  • \d odpovídá jakékoli číslici 0–9, čili je to totéž jako [0–9]
  • \D odpovídá jakémukoli znaku kromě 0–9, totéž co [^0–9], obecně jakákoli množina psaná velkým písmenem je doplňkovou množinou k té psané malým písmenem
  • \s odpovídá bílému znaku, totéž co [ \t\n\r\f\v], existuje i \S
  • \w odpovídá libovolnému alfanumerickému znaku [a-zA-Z0–9 ], doplňkem je \W

Funkce search() vrátí pouze PRVNÍ výskyt hledaného výrazu. Pro zjištění všech výskytů daného regulárního výrazu můžete použít funkci re.findall(), která vrátí všechny odpovídající nepřekrývající se podřetězce jako seznam. Obsahuje-li regulární výraz více než jednu skupinu, budou prvky seznamu tuple odpovídající jednotlivým skupinám:

>>> re.findall(r'[\w.]+@[\w.]+', 'Adresy: honza@py.cz, redakce@root.cz')
['honza@py.cz', 'redakce@root.cz']
>>> re.findall(r'([\w.]+)@([\w.]+)',
...            'Adresy: honza@py.cz, redakce@root.cz')
[('honza', 'py.cz'), ('redakce', 'root.cz')]

Chceme-li provést náhradu výskytu řetězce určeného regulárním výrazem jiným řetězcem, můžete použít funkci re.sub(). Ta jako první argument přebírá regulární výraz, druhým argumentem může být buď řetězec, nebo funkce a konečně třetím argumentem je řetězec, na nějž se má náhrada aplikovat.

Je-li druhým argumentem řetězec, pak je pro každý řetězec odpovídající výrazu provedena náhrada: všechny escape-sekvence jako ‚\n‘, ‚\r‘ apod. jsou převedeny na odpovídající znaky a dále je provedena náhrada skupin – ‚\1‘ se nahradí řetězcem odpovídajícím první skupině. Namísto ‚\1‘ můžeme použít i \g<1>, rozdíl se projeví u víceciferných čísel: ‚\72‘ neznamená skupinu 7 následovanou dvojkou, ale skupinu 72, proto raději používejte \g<7>2. Pro vynásobení všech čísel desítkou můžete tedy napsat výraz:

>>> re.sub(r'([1-9][0-9]*)', '\g<1>0', '12, 34, 56')
'120, 340, 560'

Druhým argumentem může být i funkce, která je pak volána pro každý odpovídající řetězec. Musí mít jediný argument, za nějž se dosadí objekt, který má stejné rozhraní jako objekt získaný funkcí re.match() nebo re.search(). Náhradou pak je návratová hodnota této funkce.

Kompilované regulární výrazy

Chceme-li používat jeden regulární výraz na více řetězců, je efektivnější ho nejprve „zkompilovat“. Tuto práci za nás odvede funkce re.compile(), které regulární výraz předáme. Funkce provede jeho analýzu a vrátí objekt, jenž provádí efektivní vyhledávání zadaného vzorku. Tento objekt podporuje metody match(), search(), sub(), findall() a další, které jsme si ukázali výše.

Příznaky regulárních výrazů

Funkce re.match(), re.search() a re.compile() mohou přejímat ještě jeden nepovinný argumet flags, který je bitovým součinem příznaků. Příznaky jsou definovány jako jména v modulu re:

CS24_early

  • I, IGNORECASE – hledání nebude rozlišovat velikost písmen
  • L, LOCALE – množiny \w, \W, \b a \B budou odpovídat aktuálnímu locale
  • M, MULTILINE – ovlivňuje výrazy ^ a $, ty normálně odpovídají začátku, resp. konci řetězce, s příznakem MULTILINE odpovídají i začátku a konci řádku v řetězci.
  • S, DOTALL – výraz . normálně odpovídá libovolnému znaku kromě znaku nového řádku, s příznakem DOTALL se bude uvažovat i znak nového řádku
  • U, UNICODE – množiny \w, \W, \b a \B budou záviset na tabulce znaků Unicode
  • X, VERBOSE – umožňuje psaní lépe vypadajících regulárních výrazů

Dnešní povídání nebylo ani zdaleka vyčerpávající. Věřím, že čtenář, kterého dnešní výklad zaujal, si sám nastuduje dokumentaci k modulu re a výše uvedené HOWTO. Rovněž prosím všechny regex-guru, aby tolerovali zápis mých výrazů – ty si nekladou za cíl být dokonalé, jako spíše ukázat možnosti, kterak lze pythonovské regulární výrazy používat.

Příště

Příště nás čeká výklad vláken, oblasti, v níž Python není zrovna nejsilnějším hráčem, přesto si podle mého zaslouží pozornost. Budeme se věnovat dvěma modulům pro obsluhu vláken – thread a threading, přičemž ve výkladu druhého z nich se zmíníme o zámcích, semaforech, událostech, vláknech a časovačích.

Byl pro vás článek přínosný?