Upozornění pro čtenáře a uživatele této knihy
Všechna práva vyhrazena. Žádná část této tištěné či elektronické knihy nesmí být reprodukována a šířena v papírové, elektronické či jiné podobě bez předchozího písemného souhlasu nakladatele. Neoprávněné užití této knihy bude trestně stíháno.

Automatizovaná analýza textů nebo dat ve smyslu čl. 4 směrnice 2019/790/EU a použití této knihy k trénování AI jsou bez souhlasu nositele práv zakázány.

Rudolf Pecinovský

Python - knihovny pro práci s daty
Pro verzi 3.11

Vydala Grada Publishing, a.s.
U Průhonu 22, Praha 7
obchod@grada.cz, www.grada.cz
tel.: +420 234 264 401
jako svou 8162. publikaci

Odpovědný redaktor Petr Somogyi
Fotografie na obálce Depositphotos/mario7
Grafická úprava a sazba Rudolf Pecinovský
Počet stran 328
První vydání, Praha 2023
Vytiskla TISKÁRNA V RÁJI, s.r.o., Pardubice

© Grada Publishing, a.s., 2023
Cover Design © Grada Publishing, a. s., 2023
Cover Photo © Depositphotos/mario7

Názvy produktů, firem apod. použité v knize mohou být ochrannými známkami nebo registrovanými ochrannými známkami příslušných vlastníků.

ISBN 978-80-271-7315-0 (ePub)
ISBN 978-80-271-6715-9 (pdf)
ISBN 978-80-271-0659-2 (print)

Mé ženě Jarušce a dětem
Štěpánce, Pavlínce, Ivance a Michalovi

Poděkování

Vím, že se v českých knížkách většinou neděkuje, ale tvorba knih v současném tempu je spojena s takovými oběťmi řady lidí z mého blízkého i vzdálenějšího okolí, že bych měl velkou újmu na duši, kdybych tak neučinil.

Chtěl bych především nesmírně poděkovat své ženě Jarušce, která byla po celou dobu mojí největší oporou a jejíž nekonečná trpělivost a vstřícnost mi pomohla dokončit knihu v termínu, který se příliš nelišil od toho, jejž jsme původně s nakladatelem dohodli, a ne až někdy za rok po něm. Stále marně přemýšlím, kde má schovanou tu svatozář.

Na vylepšování textu nového vydání se podílela řada dalších lidí. Musím poděkovat především těm, kteří si dali práci s odhalováním případných chyb ve vznikajícím rukopisu. Mezi nimi pak především Luďkovi Šťastnému, který po celou přípravu rukopis pročítal a odhaloval v něm pasáže, které by si zasloužily vylepšit. Neméně velkou zásluhu na současné podobě má i Jirka Kofránek, který mne průběžně upozorňoval na některé problémy s výukou podle běžně používaných postupů. Řadou podnětných myšlenek přispěl Michal Palas, jenž pracuje v oblasti analýzy a zpracování dat a přispěl tak řadou poznatků z praxe.

Nezanedbatelnou zásluhu má Adam Záhorský, který v rámci své technologické praxe nastudoval pravidla práce s knihovnou tkinter a vybavil můj testový tester regulárních výrazů grafickým uživatelským rozhraním. Poté ještě připravil převodníky scénářů mých studentů do formátů JSON a CSV a odevzdané práce převedl.

Svoji zásluhu má i Martina Jandová, která mne osvobozovala od nejrůznějších administrativních povinností a umožnila mi soustředit se pouze na výuku a psaní knih.

Velký dík patří i redaktoru Petrovi Somogyimu, který musel opakovaně procházet některé již zredigované pasáže, protože jsem je znovu a znovu upravoval. Nemalý dík patří i šéfredaktoru Radku Matulíkovi, který mne k napsání jednotlivých knih z posledních let vyhecoval, a byl pak ochoten týden či dva počkat, když se mi nepodařilo přesně dodržet původně dohodnutý termín.

Úvod

Python je moderní programovací jazyk, který umožňuje velmi jednoduše navrhovat jednoduché programy, ale na druhou stranu nabízí dostatečně mocné prostředky k tomu, abyste mohli s přiměřeným úsilím navrhovat i programy poměrně rozsáhlé. Je pro něj vyvinuto obrovské množství knihoven a frameworků, které uživatelům umožňují soustředit se na řešený úkol a nerozptylovat se vývojem nejrůznějších pomocných podprogramů.

Jednou z oblastí, v nichž má Python v současné době dominantní postavení, je zpracování dat. Tato kniha vychází vstříc všem, kteří potřebují pracovat s daty nejrůznějšího druhu. Představuje základní moduly standardní knihovny, jež se této oblasti týkají, a seznamuje se třemi nejpoužívanějšími externími knihovnami: knihovnou NumPy nabízející rozsáhlý matematický aparát pro práci s poli, knihovnou Matplotlib definující funkce, které velmi usnadňují tvorbu nejrůznějších grafů, a knihovnou pandas nabízející bohatou sadu nástrojů pro práci s daty uspořádanými do tabulek.

Komu je kniha určena

Tato kniha je určena všem, kteří znají jazyk Python přibližně na úrovni prvních tří částí učebnice [19] nebo prvních čtyř částí příručky [17], a chtěli by se seznámit se základními možnostmi práce s daty poskytovanými jeho knihovnami.

Kniha není učebnicí programování (tou je např. zmíněná učebnice [19]), ani učebnicí jazyka Python (tou je např. zmíněná příručka [17]). Proto se nesnaží probrat jeho konstrukce, ale omezuje se při výkladu pouze na základní možnosti využití jeho knihoven. Narazí-li však při výkladu na konstrukci, která nebyla vysvětlena v učebnici [19], tak ji dovysvětlí.

Bohužel se musí omezit opravdu pouze na základní a široce používanou podmnožinu dostupných funkcí a metod, protože při podrobnějším výkladu by kniha dalece přesáhla únosnou velikost.

Koncepce výkladu a jeho uspořádání

Knihu jsem se snažil koncipovat tak, aby mohla sloužit jako učebnice i jako referenční příručka. Pokusil jsem se do ní zahrnout nejčastěji zpracovávané oblasti.

Kniha je rozdělena do čtyř částí.

První část: Práce se stringy

První část rozšiřuje znalosti o práci se stringy a poté se věnuje především regulárním výrazům, které jsou mocným nástrojem usnadňujícím mnohé aspekty práce s texty. Na závěr představuje bajtové objekty používané při práci s binárními daty.

Druhá část: Vstup a výstup dat

Druhá část se zabývá nástroji pro práci s daty v nejrůznějších typech souborů. Vysvětlí principy práce se soubory, ukáže jak pracovat s datovými proudy a jak s jejich pomocí číst a zapisovat data.

Třetí část: Základy práce s daty

Třetí část seznámí s datovými třídami a pak probere možnosti ukládání dat do souborů typu CSV a JSON a čtení dat uložených v těchto souborech. Na závěr se pak bude věnovat několika užitečným modulům. Nejprve představí možnosti pro generování pseudonáhodných čísel, poté probere typ Decimal umožňující práci s velkými čísly, u nichž je důležité zachování přesnosti, a na závěr se věnuje modulům definujícím datové typy pro práci s datem a časem.

Čtvrtá část: Důležité externí knihovny

Čtvrtá část se nejprve věnuje balíčkovacímu systému Pythonu a programu pip sloužícímu k instalaci externích knihoven. Seznámí s koncepcí virtuálního prostředí umožňujícího paralelně pracovat na projektech s různými potřebnými konfiguracemi používaných nástrojů. V závěrečných třech kapitolách postupně probere výše zmíněné tři knihovny: NumPy, Matplotlib a pandas.

Jazyk identifikátorů

Doprovodné programy bychom mohli rozdělit do dvou skupin:

•První skupinu tvoří tzv. AHA-příklady, tj. příklady, jejichž jediným cílem je, aby si čtenář řekl: „Aha, takto to funguje.“ V těchto příkladech jsou pro maximální názornost používané české identifikátory.

•Druhou skupinu tvoří programy, které se snaží řešit nějaký praktický problém. V těchto příkladech jsem podle všeobecně dodržovaných konvencí dal přednost identifikátorům anglickým. Komentáře a tištěné texty však zůstávají nadále české.

Potřebné vybavení

Pro úspěšné studium celé knihy je vhodné mít instalovanou platformu Python ve verzi nejméně 3.11. Tu lze stáhnout na adrese https://www.python.org/downloads/. Instrukce pro stažení všech dalších potřebných programů budete dostávat v okamžiku, kdy tyto programy začnou být potřeba.

Doprovodné programy

Text knihy je prostoupen řadou doprovodných programů. Budete-li si je chtít spustit a ověřit jejich funkci, potřebujete je nejprve stáhnout. Najdete je na stránce knihy na adrese 1 http://knihy.pecinovsky.cz/66_py311data.

Zdrojové soubory s doprovodnými programy (moduly) se nacházejí ve složce 66_INP. Jejich názvy začínají vždy písmenem m následovaným dvojmístným číslem kapitoly. Následují-li dvě podtržítka a text (např. m01__prolog), jedná se o soubor příkazů použitých ve výpisech v průběhu kapitoly. Je-li za číslem malé písmeno následované jedním podtržítkem (např. m01a_script), jedná se o samostatný modul.

Soubory příkazů použitých ve výpisech mají ještě partnera. Ten se nachází ve složce 88_IWD a má stejný název, ale odlišnou příponou. Soubory s příponou py jsou zdrojové soubory Pythonu, soubory s příponou pydoc obsahují záznam konverzace se systémem zaznamenaný ve výpisech v knize i s uvedenými čísly řádků.

Tyto soubory vám mají usnadnit sledování rozboru některých programů, abyste při něm nemuseli neustále přecházet mezi rozborem a rozebíraným výpisem. Soubory pydoc si můžete vytisknout nebo otevřít v jiném okně, v textu knihy pak číst rozbor s odkazy na čísla řádků a vedle sledovat příslušný výpis na papíře či v paralelně otevřeném okně textového editoru.

Použité typografické konvence

K tomu, abyste se v textu lépe vyznali a také abyste si vykládanou látku lépe zapamatovali, používám několik prostředků pro odlišení a zvýraznění textu.

Termíny První výskyt nějakého termínu a další texty, které chci zvýraznit, vysazuji tučně.

Název Názvy firem a jejích produktů vysazuji kurzivou. Kurzivou vysazuji také názvy kapitol, podkapitol a oddílů, na něž v textu odkazuji.

Citace Texty, které si můžete přečíst na displeji, např. názvy polí v dialogových oknech či názvy příkazů v nabídkách, vysazuji tučným bezpatkovým písmem.

Odkaz Celá kniha je prošpikovaná křížovými odkazy na související pasáže. Není-li odkazovaný objekt (kapitola, obrázek, výpis programu, …) na stejné stránce nebo na některé ze sousedních stránek, je pro čtenáře tištěné verze doplněn o číslo stránky, na níž se nachází. Čtenářům elektronické verze stačí, když na něj klepnou. Použitý prohlížeč by je měl na odkazovaný objekt ihned přenést.

Adresa Názvy souborů a internetové adresy vysazuji obyčejným bezpatkovým písmem.

Program Identifikátory a další části programů zmíněné v běžném textu vysazuji neproporcionálním písmem, které je v elektronických verzích pro zvýraznění tmavě červené.

zadání V záznamech komunikace se systémem budou texty, které zadává uživatel, vysazeny tučně (v elektronických verzích pro zvýraznění tmavě modře).

Komentář Nejrůznější komentáře v ukázkových příkladech jsou pak vysazeny kurzivou a podbarveny světle zeleně .

klíč Klíčová slova jsou v programech pro zvýraznění vysazena tučně a tmavě červeně a pro zvýraznění v tištěných knihách jsou navíc podtržena .

Kromě výše zmíněných částí textu, které považuji za důležité zvýraznit nebo alespoň odlišit od okolního textu, najdete v knize ještě řadu doplňujících poznámek a vysvětlivek. Všechny budou v jednotném rámečku, jenž bude označen ikonou charakterizující druh informace, kterou vám chce poznámka či vysvětlivka předat.

Symbol jin-jang bude uvozovat poznámky, s nimiž se setkáte na počátku každé kapitoly. Zde vám vždy prozradím, co se v dané kapitole naučíte.

Píšící ruka označuje obyčejnou poznámku, která pouze doplňuje informace z hlavního proudu výkladu o nějakou zajímavost.

Ruka s hrozícím prstem upozorňuje na věci, které byste měli určitě vědět a na které byste si měli dát pozor, protože jejich zanedbání vás většinou dostane do problémů.

Usměváček vás bude upozorňovat na různé tipy, jimiž můžete vylepšit svůj program nebo zefektivnit svoji práci.

Mračoun vás naopak bude upozorňovat na různá úskalí programovacího jazyka nebo programů, s nimiž budeme pracovat, a bude vám radit, jak se těmto nástrahám vyhnout či jak to zařídit, aby vám alespoň pokud možno nevadily.

Obrázek knihy označuje poznámku týkající se používané terminologie. Tato poznámka většinou upozorňuje na další používané termíny označující stejnou skutečnost nebo na konvence, které se k probírané problematice vztahují. Seznam všech terminologických poznámek najdete v rejstříku pod heslem „terminologie“.

Brýle označují tzv. „poznámky pro šťouraly“, ve kterých se vás snažím seznámit s některými zajímavými vlastnostmi probírané konstrukce nebo upozorňuji na některé souvislosti, které však nejsou k pochopení látky nezbytné.

Odbočka – podšeděný blok

Občas je potřeba vysvětlit něco, co nezapadá přímo do okolního textu. V takových případech používám podšeděný blok se silnou čarou po straně. Tento podšeděný blok je takovou drobnou odbočkou od ostatního výkladu. Nadpis podšeděného bloku pak najdete i v podrobném obsahu mezi nečíslovanými nadpisy.

Zpětná vazba

Tato kniha je sice mou šedesátou šestou učebnicí, ale současně je to moje první kniha, v níž se zabývám daným tématem, tj. knihovnami pro práci s daty. Nelze proto vyloučit, že se v ní mohou objevit přehlédnutí, která nemá redaktor šanci zachytit a opravit.

Pokud vám proto bude někde připadat text nepříliš srozumitelný nebo budete mít nějaký dotaz (ať už k vykládané látce či použitému vývojovému prostředí), anebo v knize objevíte nějakou chybu či budete mít návrh na nějaké její vylepšení v případné příští verzi, neostýchejte se poslat na adresu rudolf@pecinovsky.cz e-mail s předmětem mailto:rudolf@pecinovsky.cz?subject=66_PYTHON_DATA_DOTAZ.

Bude-li se dotaz týkat něčeho obecnějšího nebo to bude upozornění na chybu, pokusím se co nejdříve zveřejnit na stránce knihy http://knihy.pecinovsky.cz/66_py311data odpověď i pro ostatní čtenáře, kteří by mohli o danou chybu zakopnout, nebo by je mohl obdobný dotaz napadnout za pár dní, anebo jsou natolik ostýchaví, že si netroufnou se sami zeptat.

Předem se však omlouvám, že budu určitě odpovídat se značným zpožděním, protože při počtu knížek, které si Grada objednala, a paralelní výuce už nemám čas na včasné odpovídání na e-maily.

I když možná bude brzy všechno jinak, protože už nehodlám nadále sponzorovat naše školství (za situace, kdy u nás vysokoškolského učitele platí méně než prodavačku v supermarketu, nemohu svoji výuku považovat za něco jiného než sponzoring). Velká zátěž spojená s výukou na univerzitách tak možná brzy odpadne a budu se moci více věnovat svým knihám a jejich čtenářům.

Část A
Práce se stringy

První část knihy prohloubí vaše znalosti práce se stringy, které jsou jedním z nejpoužívanějších datových typů (a pro mnohé dokonce typem nejpoužívanějším). Seznámí vás nejprve s metodami, které třída str a její instance poskytují, a poté se plně soustředí na výklad regulárních výrazů, což je nástroj, jenž umožňuje výrazné zefektivnění práce se stringy a uplatní se nejenom při tvorbě programů, ale velice často jej využijeme i při ostatních počítačových aktivitách. Na závěr pak představí bajtové stringy jako způsob práce s binárními daty.

Kapitola 1
Metody třídy str

Co se v kapitole naučíte

Bez ohledu na to, jaký typ programu budete vytvářet, dříve nebo později budete potřebovat nějakým způsobem pracovat s textem uloženým ve strinzích. Tato kapitola prohloubí vaše znalosti metod, které stringy nabízejí. Nebude ale probírat otázku jejich formátování – ta je řešena v příručce [17].

1.1  Efektivnější spojování stringů

Pamatujte si, že stringy v Pythonu jsou neměnné (dokonce hešovatelné), což znamená, že když chcete sloučit dva stringy dohromady, vytvoří se nový string, do kterého se nakopírují ty slučované. To má ovšem značný vliv na výkon celé operace.

Spojujete-li jen dva stringy, není to problém, jestliže byste však měli spojovat pomocí operátoru + více stringů, anebo spojovat stringy v cyklu, může se kvůli neustálému vytváření nových stringů a kopírování těch předešlých celý výpočet výrazně zpomalit. V takovém případě je vhodnější použít metodu join().

join(iterable)

Metoda join() očekává v argumentu zdroj stringů, které sloučí, přičemž mezi ně vloží vždy obsah vlastního objektu. Takto vytvořený string pak vrátí jako svoji návratovou hodnotu. Ukázku použití najdete ve výpisu 1.1.

Výpis 1.1: Slučování stringů metodou join()

1 >>> jhs = ["Jablka", "Hrušky", "Švestky"]

2 >>> ', '.join(jhs);   ''.join(jhs)

3 'Jablka, Hrušky, Švestky'

4 'JablkaHruškyŠvestky'

5 >>> "; ".join("Jablka")

6 'J; a; b; l; k; a'

7 >>> 

Na řádku 1 je definován zdroj slučovaných stringů. Na řádku 2 jsou pak dva příkazy k jejich sloučení, přičemž v prvním je oddělovač definován jako string tvořený čárkou a mezerou, v druhém je oddělovačem prázdný string. Druhý příkaz je ukázkou postupu, kdy chcete stringy pouze sloučit a nechcete je ničím oddělovat.

Příkaz na řádku 5 upozorňuje na to, že parametr metody join() je zdroj stringů. Pokud byste do parametru použili jen jeden string, Python by ho považoval za generátor jednotlivých znaků a ty by pospojoval zadaným oddělovačem.

1.2  Rozdělování stringů na části

Stejně jako je třeba někdy stringy spojovat, občas se hodí string rozložit na jednotlivé části. Pojďme se postupně seznámit s některými metodami, které nám takovouto funkcionalitu nabízejí.

str.split (sep=None, maxsplit=-1) -> list[str...]
str.rsplit(sep=None, maxsplit=-1) -> list[str]

Metoda split() má dva parametry, přičemž oba mají definovánu implicitní hodnotu. Prvním parametrem je podstring oddělující jednotlivé části. Výsledkem volání je seznam stringů, který obsahuje části původního stringu oddělené zadaným oddělovačem. Ukázku použití najdete ve výpisu 1.2 na řádku 1.

Výpis 1.2: Ukázky rozdělení stringu použitím metod split() a rsplit()

1 >>> 'Jablka a Hrušky a Švestky'.split(' a ')        # Standardní použití

2 ['Jablka', 'Hrušky', 'Švestky']

3 >>> '1,,2'.split(',')   # Oddělovače se neslučují

4 ['1', '', '2']

5 >>> '1 2  3   4    5     6      7'.split()  # Bezparametrický odděluje sadou mezer

6 ['1', '2', '3', '4', '5', '6', '7']

7 >>> 'Jablka a Hrušky a Švestky'.split(' a ', 1)     # Maximální počet částí

8 ['Jablka', 'Hrušky a Švestky']

9 >>> 'Jablka a Hrušky a Švestky'.rsplit(' a ', 1)    # Začínáme zprava

10 ['Jablka a Hrušky', 'Švestky']

11 >>> 

Narazí-li metoda na dva sousední oddělovače, nesloučí je v jeden, ale umístí na příslušné místo do seznamu prázdný string – viz reakce na příkaz na řádku 3.

Nezadáte-li první argument nebo bude-li mít hodnotu None, rozdělovací algoritmus se mění a za oddělovač se považuje jeden či více sousedících bílých znaků. Jinými slovy: v takovém případě se oddělovače slučují. Dokládají to i reakce na příkaz na řádku 5.

Hodnota parametru maxsplit určuje, maximálně kolikrát se daný oddělovač při rozdělování použije. Je-li v stringu nalezen vícekrát, další výskyty se ignorují – viz reakce na příkaz na řádku 7.

Pokud byste potřebovali začít s vyhledáváním oddělovačů od konce stringu, stačí nahradit volání metody split() voláním metody rsplit(). Jak ukazuje reakce na příkaz na řádku 9, drobnou úpravou předchozího příkladu snadno smícháte hrušky a jablka.

partition (sep:str) -> tuple[str,str,str]
rpartition(sep:str) -> tuple[str,str,str]

Pro rozdělování stringů Python nabízí ještě metodu partition(). Na rozdíl od metody split() však nevrací seznam oddělených částí, ale n-tici (začátek, oddělovač, konec). Ukázku použití najdete ve výpisu 1.3.

Výpis 1.3: Ukázky rozdělení stringu použitím metod partition() a rpartition()

1 >>> 'Mléko, Máslo, Chleba'.partition(', ')      # Standardní použití

2 ('Mléko', ', ', 'Máslo, Chleba')

3 >>> 'Název_souboru.přípona'.partition('.')      # Práce se soubory

4 ('Název_souboru', '.', 'přípona')

5 >>> 'Mléko, Máslo, Chleba'.partition('; ')      # Nenalezený oddělovač

6 ('Mléko, Máslo, Chleba', '', '')

7 >>> 'Mléko, Máslo, Chleba'.rpartition(', ')     # Prohledávání zprava

8 ('Mléko, Máslo', ', ', 'Chleba')

9 >>> 

Jak ukazuje reakce na příkaz na řádku 1, metoda vezme string a vyhledá v něm první výskyt oddělovače. Pak jako nultou položku vracené n-tice uloží vše před ním, další položkou bude vlastní oddělovač a poslední položkou vše ostatní, až do konce stringu.

Příklad na řádku 3 naznačuje, že se tato metoda může hodit např. při práci se soubory.

Příklad na řádku 5 demonstruje situace, kde zadaný oddělovač ve stringu není. Metoda pak vrátí jako počáteční prvek n-tice celý string a na dalších pozicích dva prázdné stringy.

Chcete-li, aby se string prohledával od konce, použijte metodu rpartition(). Demonstrační příkaz je na řádku 7.

Pro rafinovanější rozdělování stringů je třeba použít regulární výrazy, které budeme probírat v několika následujících kapitolách.

str.splitlines(keepends=False) -> list[str]

V řadě případů potřebujeme nějaký víceřádkový text rozdělit na jednotlivé řádky, které pak budeme zpracovávat. Stringy k tomu nabízejí použití metody splitlines(), která vrátí seznam stringů jednotlivých řádků.

Stringy ve vraceném seznamu již neobsahují znaky ukončení řádku, ledaže byste o to explicitně požádali zadáním argumentu s hodnotou True.

Tato metoda rozeznává jako konec řádku nejenom standardní konec řádku, ale i několik dalších znaků, jako např. konec stránky nebo vertikální tabulátor. Nebudu je tu vyjmenovávat, jejich seznam najdete v dokumentaci metody splitlines().

Od metody split() se kromě toho, že akceptuje libovolný z celé sady oddělovačů (konců řádku), liší také tím, že končí-li zadaný string koncem řádku, neobsahuje vrácený seznam jako poslední prvek prázdný string reprezentující prostor za tímto oddělovačem.

Vše ilustruje výpis 1.4, v němž najdete sadu příkladů převzatých z dokumentace či dokumentací inspirovaných.

Výpis 1.4: Demonstrace funkce metody splitlines()

1 >>> s = 'ab c\n\nde fg\rkl\r\n'

2 >>> print(f'{s.splitlines()     = }\n{s.splitlines(True) = }')

3 s.splitlines()     = ['ab c', '', 'de fg', 'kl']

4 s.splitlines(True) = ['ab c\n', '\n', 'de fg\r', 'kl\r\n']

5 >>> LF='\n';   print(f"{''.splitlines()=}  -  {''.split(LF)}")

6 ''.splitlines()=[]  -  ['']

7 >>> line="Jeden řádek\n";  print(f'{line.splitlines()=}  -  {line.split(LF)=}')

8 line.splitlines()=['Jeden řádek']  -  line.split(LF)=['Jeden řádek', '']

9 >>> 

1.3  Jednoduché úpravy

Stringy je potřeba velmi často nějak drobně upravit. Nejčastější operací je oříznutí přebytečných znaků na kraji stringu, o něco málo složitější je pak nahrazení některých jejich částí jinými stringy.

Vzhledem k neměnnosti stringů snad nemusím připomínat, že všechny uvedené modifikační metody vracejí nově vytvořený string se zapracovanými modifikacemi a že původních oslovených stringů se požadované modifikace nijak nedotknou.

Ořezávání stringů

Častou úpravou stringů je odstranění některých znaků z jejich začátku nebo konce. Nejčastěji se takto ořezávají bílé znaky, ale nemusí to tak být vždy. Ukázky použití různých metod najdete ve výpisu 1.5.

Příkaz na řádku 1 demonstruje nejčastější použití, kdy se ořezávají počáteční a koncové bílé znaky – většinou mezery (v kódu je sice zapsáno mezery, ale platí to pro všechny bílé znaky). Používáme k tomu volání metody strip()2 bez argumentů.

Výpis 1.5: Demonstrace funkce metod pro ořezávání stringů

1 >>> '    mezery před i za textem musí pryč   '.strip()

2 'mezery před i za textem musí pryč'

3 >>> '    mezery před textem musí pryč        '.lstrip()

4 'mezery před textem musí pryč        '

5 >>> '    mezery za textem musí pryč          '.rstrip()

6 '    mezery za textem musí pryč'

7 >>> '***hvězdičky před i za textem musí pryč*'.strip('*')

8 'hvězdičky před i za textem musí pryč'

9 >>> '**--hvězdičky (*) i pomlčky (-) kolem textu musí pryč*-**--***'.strip('*-')

10 'hvězdičky (*) i pomlčky (-) kolem textu musí pryč'

11 >>> lst = '_PrvníTest', '_Druhý', 'Třetí', 'ČtvrtýTest'

12 >>> [s.removeprefix('_') for s in lst]

13 ['PrvníTest', 'Druhý', 'Třetí', 'ČtvrtýTest']

14 >>> [s.removesuffix('Test') for s in lst]

15 ['_První', '_Druhý', 'Třetí', 'Čtvrtý']

16 >>> [s.removeprefix('_').removesuffix('Test') for s in lst]

17 ['První', 'Druhý', 'Třetí', 'Čtvrtý']

18 >>> 

K odstranění bílých znaků pouze ze začátku, resp. konce stringu slouží její kolegyně lstrip(), resp. rstrip() – viz příkazy na řádcích 3 a 5.

Všechny tři uvedené metody jsou ale univerzálnější. Zavoláme-li je se zadaným argumentem, očekávají v něm string s množinou znaků, které mají na počátku a/nebo na konci stringu odebrat (viz reakce na příkazy na řádcích 7 a 9). Případné výskyty těchto znaků uvnitř textu samozřejmě zůstanou.

Ve verzi 3.9 byly přidány metody removeprefix() a removesuffix(), které umějí odstranit zadanou předponu, resp. příponu daného stringu. Od této verze proto již nemusíte testovat, jestli dané slovo zadanou předponu či příponu opravdu má, protože to tyto metody udělají za vás. Použití těchto metod demonstruji na řádcích 11–17.

Nahrazování

Velmi často potřebujete ve stringu nahradit nějaký text jiným. Stačí-li opravdu jen nahradit předem známý text jiným textem, použijete metodu replace().

str.replace(old:str, new:str, count:int=0) -> str

Metoda má tři parametry, přičemž v prvním očekává hledaný nahrazovaný text, v druhém text, který jej má nahradit, a ve třetím nepovinném případně maximálně povolený počet nahrazení. Nezadáte-li jej, nahradí se všechny výskyty. Ukázku použití najdete ve výpisu 1.6.

Příklad

Při zadávání nahrazovaného stringu si musíte zkontrolovat, zda se zadaný nahrazovaný string nevyskytuje v upravovaném stringu i na jiných místech. Jinak byste mohli dopadnout jako příkaz na řádku 2, v jehož výsledku na řádku 3 jsou dvě chyby. Přitom stačí jenom vyhledávaný a nahrazovaný text vhodně rozšířit, jak je předvedeno v příkazu na řádku 4.

Příkaz na řádku 7 požaduje, aby se v upravené verzi textu z písničky Holka modrooká nahradila dosavadní písmena a písmenem e, ale nejvýše 20krát. Můžete si spočítat, že opravdu proběhlo 20 nahrazení.

Výpis 1.6: Demonstrace použití metody replace()

1 >>> text = 'Ve slově standart se na konci nepíše t, ale d.'

2 >>> text.replace('t', 'd')

3 'Ve slově sdandard se na konci nepíše d, ale d.'

4 >>> text.replace('art', 'ard')

5 'Ve slově standard se na konci nepíše t, ale d.'

6 >>> halka = 'Halka madraaka nasadavaj a pataka, halka madraaka nasadavaj tam.'

7 >>> halka.replace('a', 'e', 20)

8 'Helke medreeke nesedevej e peteke, helke medreeke nasadavaj tam.'

9 >>> 

Naše dosavadní znalosti neumožňují realizovat nahrazení v původním textu. Prozatím musí být takový text upraven, aby všechna slova obsahovala stejnou samohlásku. Jak zadat nahrazení aplikovatelné na originální text si ukážeme ve výpisu 6.4, až probereme práci s regulárními výrazy.

str.expandtabs(tabsize=8)

Poslední nahrazovací metoda, o níž se zde zmíním, je metoda expandtabs(), která vrací kopii osloveného stringu, v níž jsou tabulátory nahrazeny sadou mezer tak, aby to odpovídalo zadané velikosti kroku tabulátoru. Ta je implicitně 8 znaků.

Objeví-li se ve stringu přechod na nový řádek, je do výsledného stringu zkopírován a počitadlo aktuálního sloupce se vynuluje. Ukázky použití najdete ve výpisu 1.7.

Výpis 1.7: Demonstrace použití metody expandtabs()

1 >>> print('01\t012\t0123\t01234'.expandtabs(),

2 ...       '01\t012\t0123\t01234\n0\t1\t2\t3\t4'.expandtabs(4), sep='\n')

3 01      012     0123    01234

4 01  012 0123    01234

5 0   1   2   3   4

6 >>> 

Vyhledávání

Někdy nepotřebujeme daný text hned nahradit, ale stačí nám vědět, jestli se v daném stringu vyskytuje zadaný podstring. K vyhledání pozice zadaného podstringu nebo zjištění jeho přítomnosti nabízí stringy celou řadu metod. Ukázku najdete ve výpisu 1.8.

Všechny dále uvedené metody mají implicitní parametr start a end definující podstring zadaného stringu, v němž se vyhledává. Parametry se interpretují jako u vyřezávání, takže se prohledává podřetězec [start:end].

Počítejte však s tím, že všechny argumenty jsou poziční, takže potřebujete-li zadat koncovou pozici prohledávaného stringu, musíte zadat i tu počáteční. Pojmenované argumenty překladač neakceptuje.

str.count(sub:str, start:int=0, end:int=None, /)

Vrátí počet nepřekrývajících se výskytů zadaného podstringu.

str.find(sub:str, start:int=0, end:int=None, /)
str.rfind(sub:str, start:int=0, end:int=None, /)

Vrátí výskyt prvního výskytu zadaného podstringu. Není-li podstring nalezen, vrátí -1. Metoda rfind() prohledává zprava.

str.index(sub:str, start:int=0, end:int=None, /)
str.rindex(sub:str, start:int=0, end:int=None, /)

Pracují stejně jako metody find(), resp. rfind(). Liší se pouze tím, že nenajdou-li hledaný řetězec, vyvolají výjimku ValueError.

str.startswith(prefix:(str|tuple[str]), start:int=0, end:int=None, /)
str.endswith (suffix:(str|tuple[str]), start:int=0, end:int=None, /)

Vrátí Tru v případě, začíná-li (resp. končí-li) prohledávaný řetězec zadaným stringem, resp. některým ze zadané n-tice stringů.

Výpis 1.8: Demonstrace použití vyhledávacích metod

1 >>> t = 'madraaka'

2 >>> halka.count('a')    # Prohledává string "Halka madraaka..."

3 25

4 >>> print(f'{(i1:=halka.find(t)) = },    {(i2:=halka.rfind(t))  = }')

5 (i1:=halka.find(t)) = 6,    (i2:=halka.rfind(t))  = 41

6 >>> print(f'{halka.find(t, i1+1) = },    {halka.rfind(t, 0, i2) = }')

7 halka.find(t, i1+1) = 41,    halka.rfind(t, 0, i2) = 6

8 >>> halka.startswith('Halka'),    halka.endswith('tam'), \

9 ... halka.startswith('halka'),    halka.endswith('tam.'),

10 (True, False, False, True)

11 >>> halka.rfind(t, end=i2)

12 Traceback (most recent call last):

13   File "<stdin>", line 1, in <module>

14 TypeError: str.rfind() takes no keyword arguments

15 >>> 

1.4  Jednoduché formátování

Pro jistotu opět připomínám, že všechny uvedené modifikační metody vracejí nově vytvořený string se zapracovanými modifikacemi a že oslovených stringů se požadované modifikace nijak nedotknou.

Změna velikosti písmen

Pro změny velikosti písmen ve stringu nabízejí jeho instance několik bezparametrických metod:

lower()Vrátí nový string vzniklý převodem svého stringu na malá písmena.

upper()Vrátí nový string vzniklý převodem svého stringu na velká písmena.

capitalize()Vrátí nový string vzniklý převodem svého stringu na malá písmena s výjimkou prvního znaku, který bude velký.

title()Vrátí nový string vzniklý převodem svého stringu na malá písmena s prvními znaky všech slov velkými písmeny.

Ukázky použití uvedených metod jsou ve výpisu 1.9.

Výpis 1.9: Demonstrace nastavení velikosti písmen

1 >>> text     = 'pokus: Text z malých písmen, VELKÝCH PÍSMEN a čísla 42.'

2 >>> (malá   := text.lower())

3 'pokus: text z malých písmen, velkých písmen a čísla 42.'

4 >>> (velká  := text.upper())

5 'POKUS: TEXT Z MALÝCH PÍSMEN, VELKÝCH PÍSMEN A ČÍSLA 42.'

6 >>> (věta   := text.capitalize())

7 'Pokus: text z malých písmen, velkých písmen a čísla 42.'

8 >>> (nadpis := text.title())

9 'Pokus: Text Z Malých Písmen, Velkých Písmen A Čísla 42.'

10 >>> 

Zarovnávání

Při vytváření textových výstupů se často hodí zarovnat výstupní text doleva, doprava, nebo na střed. Lze k tomu použít metody ljust(), rjust() a center().

Parametrem všech těchto metod je požadovaná délka výstupního stringu. Zadaný text je podle typu zarovnání doplněn mezerami na konci a/nebo na začátku na požadovanou délku.

Kromě délky stringu je možné ještě v druhém (nepovinném) parametru určit, jaký znak se má použít k doplnění stringu namísto standardní mezery.

Oba argumenty jsou poziční, takže se je nesnažte pojmenovávat. Ukázky použití najdete ve výpisu 1.10.

center(width, fillchar=' ', /)

Vrátí string zadané délky, v němž bude text vycentrován.

ljust(width, fillchar=' ', /)

Vrátí string zadané délky, v němž bude text zarovnán vlevo.

rljust(width, fillchar=' ', /)

Vrátí string zadané délky, v němž bude text zarovnán vpravo.

Výpis 1.10: Demonstrace použití zarovnávání

1 >>> print("|", "vlevo" .ljust(20),  "|", "uprostřed".center(20),

2 ...       "|", "vpravo".rjust(20),  "|")

3 | vlevo     |     uprostřed     |     vpravo |

4 >>> print ("Vítejte".center(40, '-'))

5 ----------------Vítejte-----------------

6 >>> 

Šablony

Potřebujete-li provádět nějaké záměny opakovaně a navíc s různými nahrazujícími texty, můžete s výhodou použít šablony, tj. instance třídy Template z modulu string. Ty zadaný string obalí a na požádání jej pak vracejí se zadanými nahrazujícími texty. To je výhodné zejména při lokalizaci programu do různých jazyků.

String zadávaný konstruktoru šablony musíte připravit podle následujících pravidel:

•Kdykoliv v něm potřebujete použít znak $ (dolar), zapíšete na dané místo dvojici znaků $$.

•Místo nahrazovaného textu zadáte text ve tvaru $identifikátor, kde identifikátor smí implicitně obsahovat pouze písmena sady ASCII, číslice a znak podtržení (dá se nastavit jiné pravidlo). Za identifikátorem musí být znak, který nesmí být legální součástí identifikátoru – např. nějaké interpunkční znaménko.

•Potřebujete-li, aby za nahrazovaným textem následoval obyčejný znak, musíte použít tvar ${identifikátor}. Tento tvar ale můžete používat i tehdy, když to není nezbytně potřeba.

Šablony nabízejí jeden atribut a dvě metody:

template

Datový atribut odkazující na zabalený string. Jeho hodnotu nelze změnit.

substitute(mapping={}, /, **kwds)

Metoda očekává mapovací objekt (např. slovník), jehož prvky budou mít jako klíče identifikátory pozic, na něž budou vkládány nahrazující texty zadané jako hodnoty. Místo něj ale můžete zadat příslušné texty jako pojmenované argumenty.

safe_substitute(mapping={}, /, **kwds)

Metoda se liší od té předchozí tím, že nebude-li zadán některý z identifikátorů, nevyvolá KeyError, a objeví-li se někde osamocený dolar, tak jej zkopíruje do výsledného stringu a nevyvolá ValueError. Jinými slovy metoda se snaží vrátit pokaždé použitelný string.

Metoda je sice podle názvu bezpečná (anglicky safe), ale zkušenější programátoři tvrdí, že je všechno jiné, jen ne bezpečná, protože ignoruje chyby a oddaluje tak jejich odhalení.

Ukázky použití metody najdete ve výpisu 1.11.

Výpis 1.11: Ukázka použití šablony

1 >>> from string import Template

2 >>> tmp  = Template('$Kdo miluje $Co')

3 >>> pair = {'Kdo':'Petr',  'Co':'Lucii'}

4 >>> tmp.substitute(pair)

5 'Petr miluje Lucii'

6 >>> tmp.substitute(Kdo='Pepa', Co='pivo')

7 'Pepa miluje pivo'

8 >>> tmp.safe_substitute(Co='guláš')

9 '$Kdo miluje guláš'

10 >>> 

Dokonalejší formátování

Python samozřejmě nabízí i mnohem dokonalejší způsoby formátování prostřednictvím metody format(). Toto formátování je však od zavedení f-stringů ve verzi 3.6 už tak hluboko zapuštěné do systému, že se jedná spíše o vlastnost jazyka a ne o vlastnost jeho knihovny. Proto se jim a metodě format(), která je s nimi silně propojena, poměrně podrobně věnuje samostatná kapitola v publikaci [17]. V této příručce je přeskočím.

1.5  Zjišťovací metody

Kromě metod, které vracejí modifikovanou podobu svého stringu, nebo o něm prozrazují některé interní informace, definuje třída str sadu bezparametrických metod vracejících logickou hodnotu informující o některých celkových vlastnostech jejich stringu. Ukázku aplikace některých z nich najdete ve výpisu 1.12.

Řada těchto metod staví na informacích definovaných ve znakové sadě Unicode.3 Jejich zprostředkování má v Pythonu na starosti modul unicodedata.

str.islower()Vrací True, je-li ve stringu alespoň jedno písmeno a všechna písmena jsou malá. Znaky, které nejsou písmeny, výsledek neovlivní.

str.isupper()Vrací True, je-li ve stringu alespoň jedno písmeno a všechna písmena jsou velká. Znaky, které nejsou písmeny, výsledek neovlivní.

str.istitle()Vrací True, je-li ve stringu alespoň jedno písmeno a mají-li ve stringu všechna slova první písmeno velké a ostatní malá. Znaky, které nejsou písmeny, výsledek neovlivní.

str.isidentifier()Vrací True, je-li string platným identifikátorem.

Výpis 1.12: Testy některých vlastností stringů

1 >>> f'{malá.islower()=}, {velká.islower()=}, {nadpis.islower()=}'

2 'malá.islower()=True, velká.islower()=False, nadpis.islower()=False'

3 >>> f'{malá.isupper()=}, {velká.isupper()=}, {nadpis.isupper()=}'

4 'malá.isupper()=False, velká.isupper()=True, nadpis.isupper()=False'

5 >>> f'{malá.istitle()=}, {velká.istitle()=}, {nadpis.istitle()=}'

6 'malá.istitle()=False, velká.istitle()=False, nadpis.istitle()=True'

7 >>> # Pravidla pro akceptování stringu jako titulku jsou maličko složitější

8 >>> [s.istitle() for s in ['A', 'A2', 'A2b', 'A2B']]

9  [True, True, False, True]

10 >>> # S výjimkou mezery jsou bílé znaky považovány za netisknutelné

11 >>> [c.isprintable() for c in ' \a\b\f\n\r\t\v\xA0']

12 [True, False, False, False, False, False, False, False, False]

13 >>> # Mezera + bílé znaky zadatelné jako escape sekvence + pevná mezera

14 >>> ' \f\n\r\t\v\xA0'.isspace()

15 True

16 >>> # Sada různých dalších mezer

17 >>> ', '.join([f'{hex(i)}-{chr(i).isspace()}' for i in range(0x2000, 0x200B)])

18 '0x2000-True, 0x2001-True, 0x2002-True, 0x2003-True, 0x2004-True, 0x2005-True, 0x2006-True, 0x2007-True, 0x2008-True, 0x2009-True, 0x200a-True'

19 >>> # V následujících příkazech nemusí některé editory zobrazit všechny znaky

20 >>> # Součástí definice Unicode je označení, které znaky jsou písmena

21 >>> 'aBᶜßŒψΣшЯ'.isalpha()

22 True

23 >>> # Za číslice jsou považovány i indexy, exponenty a číslice v jiných jazycích

24 >>> '0123456789⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉٠١٢٣٤٥٦٧٨٩'.isdigit()

25 True

26 >>> +# Za desítkové jsou považovány i cizojazyčné desítkové číslice

27 >>> '0123456789٠١٢٣٤٥٦٧٨٩'.isdecimal()

28 True

29 >>> # Indexy a exponenty nejsou považovány za desítkové číslice

30 >>> '⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉'.isdecimal()

31 False

32 >>> # Za číselné znaky jsou považovány i znaky reprezentující zlomky

33 >>> ''.isnumeric()

34 True

35 >>> # Za číselné znaky jsou považovány i znaky reprezentující římská čísla

36 >>> 'ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿ'.isnumeric()

37 True

38 >>> 

str.isprintable()Vrací True, je-li string prázdný, nebo obsahuje pouze tisknutelné znaky. Za netisknutelné znaky jsou (mimo jiné) považovány všechny bílé znaky s výjimkou obyčejné mezery (pevná mezera '\xA0' je proto netisknutelný znak).

str.isspace()Vrací True, je-li string neprázdný a všechny jeho znaky jsou v sadě Unicode klasifikovány jako bílé znaky. Jak se můžete přesvědčit ve výpisu 1.12, patří sem nejen bílé znaky známých escape sekvencí, ale i řada různě širokých mezer s kódem od 0x2000 a další znaky.

str.isascii()Vrací True, je-li string prázdný, nebo mají všechny jeho znaky kód menší nebo roven 255 (0x7F). (Přidáno ve verzi 3.7.)

str.isalpha()Vrací True, je-li string neprázdný a všechny jeho znaky jsou v databázi znaků Unicode považovány za písmena.

str.isdigit()Vrací True, je-li string neprázdný a všechny jeho znaky jsou v databázi znaků Unicode považovány za číslice. Vedle běžných číslic latinky a číslic zapsaných jako indexy či exponenty sem patří i číslice dalších jazyků – např. arabsko-indické.

str.isnumeric()Vrací True, je-li string neprázdný a všechny jeho znaky jsou v databázi znaků Unicode považovány za reprezentanty čísel. Sem patří vedle číslic i řada dalších znaků reprezentujících některé zlomky, římské číslice a další hodnoty.

str.isdecimal()Vrací True, je-li string neprázdný a všechny jeho znaky mohou být použity pro zápis čísel o základu 10.

str.isalnum()Vrací True, je-li string neprázdný a všechny znaky představují písmena, číslice či čísla. Za alfanumerický je znak c považován v případě, vrací-li True alespoň jedna z metod c.isalpha(), c.isdecimal(), c.isdigit(), c.isnumeric().

Protože v kódu nemusely být dané znaky dobře čitelné, tak pro zájemce doplním, že mezi numerické znaky patří (mimo jiné) zlomky „velká“ římská čísla4 Ⅰ-Ⅱ-Ⅲ-Ⅳ-Ⅴ-Ⅵ-Ⅶ-Ⅷ-Ⅸ-Ⅹ-Ⅺ-Ⅻ-Ⅼ-Ⅽ-Ⅾ-Ⅿ, „malá“ římská čísla ⅰ-ⅱ-ⅲ-ⅳ-ⅴ-ⅵ-ⅶ-ⅷ-ⅸ-ⅹ-ⅺ-ⅻ-ⅼ-ⅽ-ⅾ-ⅿ včetně obecně neznámých římských číslic pro 5 000 , 10 000 50 000 a 100 000 .

1.6  Surové stringy

Jak jistě víte, Python umožňuje modifikovat způsob interpretace zadaného stringu pomocí vhodné předpony. Předpona r nebo R označuje daný string jako surový5 (anglicky raw string). Jeho nejdůležitější vlastností je, že se v něm znak \ (zpětné lomítko) neinterpretuje jako metaznak zastupující ve dvojici spolu s následujícím znakem nějaký speciální znak, ale představuje sám sebe.

Surové stringy se používají při zadávání názvů souborů pro Windows (i když osobně upřednostňuji zadávání dopředných lomítek, která fungují všude) anebo při zadávání regulárních výrazů, jemuž je věnováno několik následujících kapitol.

Pravidla zápisu surových stringů

Zpětné lomítko má v surových strinzích jednu zvláštní vlastnost: jeho zapsání bezprostředně před jakýkoliv znak zdůrazní, že nejen zpětné lomítko, ale i následující znak zastupuje sám sebe. Zapíšete-li jej proto před znak ukončující daný string, nebude tento znak vnímán jako ukončení daného stringu, ale jako apostrof, resp. uvozovky.

Výpis 1.13: Interpretace surových stringů

1 >>> print('Standardní string\n s \'apostrofy\'', "nebo \"uvozovkami\"\n",

2 ...      r'Surový string\n s \'apostrofy\'', r"nebo \"uvozovkami\"")

3 Standardní string

4  s 'apostrofy' nebo "uvozovkami"

5  Surový string\n s \'apostrofy\' nebo \"uvozovkami\"

6 >>> r'Surový string nesmí končit samotným zpětným lomítkem\'

7   File "<stdin>", line 1

8     r'Surový string nesmí končit samotným zpětným lomítkem\'

9     ^

10 SyntaxError: unterminated string literal (detected at line 1)

11 >>> print(r'Dvojice zpětných lomítek je však povolena: \\')

12 Dvojice zpětných lomítek je však povolena: \\

13 >>> r'Při převodu na obyčejný string \n se zpětná lomítka zdvojí: \\'

14 'Při převodu na obyčejný string \\n se zpětná lomítka zdvojí: \\\\' 

15 >>> r"""Dvouřádkový \

16 ... surový string"""

17 'Dvouřádkový \\\nsurový string'

18 >>> (r'Surový string rozepsaný'

19 ...  r' do dvou řádků')

20 'Surový string rozepsaný do dvou řádků'

21 >>> r'Potřebujeme-li vložit do surového stringu' '\n' f'odřádkování'

22 'Potřebujeme-li vložit do surového stringu\nodřádkování'

23 >>> print(_)

24 Potřebujeme-li vložit do surového stringu

25 odřádkování

26 >>> 

Samotné zpětné lomítko proto nemůže být posledním znakem zadaného stringu, protože pak bude následující znak vnímán jako běžný znak a string nebude ukončen. String ale může končit dvojicí zpětných lomítek, protože první potvrdí, že druhé zastupuje samo sebe, a další znak bude proto možné vnímat jako ukončení.

Vše jsem se snažil ukázat ve výpisu 1.13.

Příkaz na řádcích 1–2 tiskne pod sebe téměř shodné stringy, přičemž dvojice stringů na řádku 1 je zapsána standardně, kdežto stringy na řádku 2 jsou zadány jako surové. Dvojici stringů jsem na každém řádku použil proto, abych ukázal, že je jedno, jestli stringy ohraničíte apostrofy nebo uvozovkami. Stejně se budou chovat i v případě, kdy je ohraničíte trojicemi apostrofů či uvozovek.

Všimněte si rozdílů zobrazení prostřednictvím funkce print() (tj. při použití uživatelského podpisu) na řádku 12 a při přímém zobrazení interpretem (tj. při použití systémového podpisu) na řádku 14.

Příklad na řádcích 15–17 demonstruje, že při použití surového stringu se neuplatní ani zpětné lomítko na konci řádku a do výsledku se zanese jako zpětné lomítko následované znakem konce řádku.

Potřebujete-li dlouhý surový string rozepsat do dvou řádků, použijte dva za sebou následující stringy, jak je to předvedeno na řádcích 18–20. Nezapomeňte přitom na přístup Pythonu k fyzickým a logickým řádkům6 a v případě potřeby uzavřete dvojici do závorek, jak je to předvedeno ve výpisu.

Potřebujete-li naopak, aby bylo uvnitř definovaného surového stringu odřádkování, musíte daný string rozdělit na dvě části a mezi ně vložit normální string s odřádkováním, jak je to v příkazu na řádku 21.

1.7  Zdrojové kódy

Zadávané příkazy i obsah výpisů se záznamem komunikace s interpretem Pythonu probíhající v této kapitole najdete v souborech nazvaných m01__Jednoduché_úpravy.

Kapitola 2
Úvod do regulárních výrazů

Co se v kapitole naučíte

Jak už bylo naznačeno v úvodu celé části, regulární výrazy jsou nástroj, který práci se stringy výrazně zefektivní. Tato kapitola vás seznámí s jejich základní koncepcí a představí několik nástrojů umožňujících navržené výrazy testovat. Na závěr vám vysvětlí, co to jsou metaznaky, a představí metaznaky používané v regulárních výrazech. Jejich používání vám postupně vysvětlí kapitoly následující.

Motto:Programátor musel jednou řešit zajímavý problém.
Rozhodl se jej vyřešit použitím regulárních výrazů.
Nyní musel řešit dva problémy.

2.1Co jsou regulární výrazy

Předchozí kapitola vám představila jednoduché operace, které je možné se stringy provádět. Potřebujete-li realizovat nějaké složitější operace, bývá velmi výhodné použít regulární výrazy.

Regulární výrazy jsou textové vzory, které definují, jakou podobu má mít textový řetězec. Mají vlastní syntaxi a představují takový jazyk v jazyce. Jazyk, který zařazujeme do kategorie označované zkratkou DSL odvozenou z anglického Domain Specific Language (doménově specifický jazyk). Jazyky tohoto druhu jsou vyvinuté (a případně dále optimalizované) pro nějaký specifický účel.

V případě regulárních výrazů je tímto účelem vyhledávání a případné nahrazování textů odpovídajících zadaným vzorům (regulárním výrazům) a v neposlední řadě také k ověření toho, že testovaný string odpovídá požadovaným pravidlům, např. že se jedná o korektně zapsané datum, telefonní číslo, e-mailovou adresu apod.

S pomocí vhodného regulárního výrazu lze na několika řádcích realizovat operaci, na kterou byste jinak potřebovali napsat několikastránkový program. Regulární výrazy se rozšířily proto, že s jejich pomocí lze řadu úloh naprogramovat nepoměrně jednodušeji a rychleji než prostřednictvím standardních příkazů nějakého univerzálního jazyka.

Jsou natolik užitečné a prosadily se natolik výrazně, že se nástroje umožňující jejich využívání staly nedílnou součástí každého lepšího editoru a podporuje je i značná část správců souborů, které je využívají jak pro vyhledávání souborů, tak pro jejich přejmenovávání či vyhledávání a nahrazování textu v jejich obsahu.

Historie

Regulární výrazy se narodily v roce 1968, když Ken Thompson implementoval teoretický koncept navržený v roce 1951 matematikem S.C.Kleenem. O jejich masové rozšíření se zasloužil programovací jazyk Perl, který je začlenil do své syntaxe a vzhledem ke své popularitě pak inspiroval jejich zařazování do knihoven (a občas i do syntaxe) dalších a dalších programovacích jazyků. Python je má ve své standardní knihovně od verze 1.1, tedy prakticky od svého vzniku.

V současné době jsou regulární výrazy prakticky povinnou součástí každé základní knihovny, přičemž v dokumentaci se často uvádí, nakolik se daná verze liší od verze jazyka Perl. Přiznejme si ale, že spolu se současným slábnutím pozice tohoto jazyka (a jeho nahrazováním Pythonem) potřeba takovéhoto porovnání slábne.

Pokud přecházíte na Python z jiného programovacího jazyka, mějte na paměti, že syntaktická pravidla regulárních výrazů se v různých jazycích liší. Liší se dokonce i syntaxe podporovaná různými knihovnami téhož jazyka.

Předchozí upozornění se vztahuje i na Python. My budeme v této knize probírat verzi implementovanou v modulu re, jenž je součástí standardní knihovny. Zájemci si mohou prostřednictvím programu pip stáhnout modul regex, který má kompatibilní API, ale poskytuje rozšířenou verzi regulárních výrazů.

2.2Testovací programy

Nevýhodou regulárních výrazů je, že jejich jazyk patří do kategorie, kterou programátoři označují ironickou zkratkou WOL – Write Only Language neboli jazyky určené pouze pro zápis. Toto označení naznačuje, že jakýkoliv jen trochu složitější program napsaný v daném jazyku lze jen obtížně analyzovat a najít v něm případnou chybu. (Ostatně to naznačuje i motto v úvodu kapitoly.)

Regulární výrazy je proto velice užitečné před jejich zařazením do programu nejprve samostatně otestovat. Proto vznikla celá řada nejrůznějších testerů, přičemž ty nejdokonalejší bývají placené, protože programátorům, kteří používají regulární výrazy častěji, se jejich používání bezesporu vyplatí.

U těch, které jsou k dispozici zdarma, bývá občas problémem, že neprozradí, jestli podporují regulární výrazy Pythonu, a pokud ano, tak pro kterou verzi Pythonu byly odladěny. Pokud se vám navíc zdá, že na nich něco nechodí optimálně, nemíváte možnost to ověřit.

Testery na webu

Zadáte-li do Googlu výraz python regex tester, dostanete řadu nabídek, z nichž si budete moci vybrat tester, jenž bude nejlépe odpovídat vašim požadavkům. Pamatujte ale na varování, že se syntaxe regulárních výrazů v jednotlivých jazycích liší, a vždy si zkontrolujte, že vámi zvolený tester kontroluje pravidla jazyka Python.

Mně se líbil např. tester na adrese https://extendsclass.com/regex-tester.html#python. Část jeho stránky si můžete prohlédnout na obrázku 2.1.

Obrázek 2.1:
Část webové stránky testeru regulárních výrazů

Při testování svých regulárních výrazů zadáte testovaný regulární výraz do pole Regular expression to test mezi znaky '/' a '/'. Znaky za závěrečným lomítkem pak odpovídají příznakům nastavovaným po stisku tlačítka Flags +.

Do pole String to test zadáte prověřovaný text, na nějž zadaný regulární výraz cvičně aplikujete. Program pak v prověřovaném textu podbarví jednotlivé výskyty zadaného výrazu, jak je předvedeno i na obrázku 2.1.

Nevýhodou programu je, že v době psaní této knihy byl odladěn pro Python 3.4.

Doprovodný GUI tester

Přiznávám, že dávám přednost testeru, který běží nad týmž interpretem jako program, v němž chci požadovaný regulární výraz použít. Proto jsem pro tento kurz napsal modul rtc, o němž budu hovořit v další pasáži. Tento modul vybavil jeden ze studentů grafickým uživatelským rozhraním, které si můžete prohlédnout na obrázku 2.2 (okno je ve skutečnosti vyšší, ale – jak naznačuje čárková čára – pro úsporu místa jsem dole část odřízl).

Obrázek 2.2:
Grafické uživatelské rozhraní doprovodného testeru regulárních výrazů

Při práci s tímto testerem postupujte následovně:

1.Do vstupního pole Find pattern zadejte testovaný regulární výraz.

2.Do vstupního pole Replace pattern zadejte nahrazovací regulární výraz.

3.Do vstupního pole Parsed text zapište analyzovaný text.

4.Zaškrtněte políčka odpovídající příznakům, které chcete nastavit.

5.Stiskněte tlačítko spouštějící požadovanou akci:

•Analyze pro vyhledávání všech výskytů textů odpovídajících zadanému regulárnímu výrazu. Nalezené výskyty se pak v prohledávaném textu barevně zvýrazní.

•Replace pro nahrazení všech výskytů textů odpovídajících zadanému regulárnímu výrazu texty definovanými výrazem ve vstupním poli Replace pattern.

•Matches vrací informaci, jestli zadaný text odpovídá zadanému regulárnímu výrazu.

6.Výsledek najdete:

•Za prvé v poli Parsed text, kde jsou jednotlivé nalezené výskyty barevně zvýrazněny střídajícími se barvami (abyste snadno odlišili i bezprostředně sousedící texty),

•a za druhé v panelu Comprehensive results, kde program zobrazuje podrobné informace o lokalitě a podobě nalezených textů a/nebo prohledávaný text po aplikaci nahrazení.

Omluvte nedokonalosti, je to program středoškolského studenta, jenž měl postavit GUI okolo mého textového testeru, který představím vzápětí. Pokud jsem chtěl stihnout odevzdat knihu do začátku školního roku, neměl jsem čas vytvářet GUI. Přivítal jsem proto nabídku vytvořit nadstavbové GUI v rámci technologické praxe. V časové tísni jsem pak program akceptoval tak, jak byl odevzdaný. Jestli se objeví nějaké časové okno, pokusím se jej učesat.

Doprovodný textový tester v modulu rtc

S výše popsaným GUI testerem se sice příjemně pracuje, ale špatně se z něj přenášejí data do textu knihy. Navíc vznikal v době, kdy už jsem potřeboval pro doprovodné příklady nějaký program používat.

Proto jsem definoval modul, jenž jsem nazval rtc (počáteční znaky slov regexp-tester-console) a po odladění předal jako základ pro vytvoření grafické nadstavby. Tento program pracuje v textovém režimu, takže jej lze spouštět z příkazového řádku nebo příkazového okna používaného IDE. Výstup tohoto modulu budu používat ve výpisech demonstrujících probíranou látku. Záznam jednoho vyvolání testovací metody najdete ve výpisu 2.1 (řádky 10–26 jsou shodné s řádky výpisu 5.6).

Na řádcích 3–8 je záznam komunikace s programem (ten ve výpisech nebude). Program se zeptá na text, a pokud se nám zadávaný text nevejde na jeden řádek, ukončíme jej definovaným ukončovacím znakem, kterým je implicitně zpětné lomítko. (Nevyhovuje-li vám, můžete je nahradit libovolným znakem, který vložíte do proměnné rtc.CR.) Program pak před zpracováním oba řádky sloučí, takže ze dvou fyzických řádků vyrobí jeden logický.7

Potřebujete-li do zadávaného textu vložit znak konce řádku, ukončíte zadávaný řádek dvojicí ukončovacích znaků – implicitně dvojicí zpětných lomítek.

Nechcete-li v prověřovacím textu spouštět žádné nahrazování textů, zadáte jako nahrazovací výraz pouze znak '§'. Nevyhovuje-li vám, můžete ho nahradit libovolným znakem, který vložíte do proměnné rtc.NO. Chcete-li nalezené texty ve výsledném stringu smazat, zadejte jako nahrazovací výraz dvojici těchto znaků (implicitně '§§').

Výpis 2.1: Použití metody tst() z modulu rtc

1>>>importrtc

2>>>rtc.tst()

3Zadejte:

4Regulárnívýraz:\b(?P<den>(?:3[01])|(?:[12][0-9])|[1-9])(?P<sep>[./-])\

5(?P<měsíc>(?:1[012])|[1-9])\2(?P<rok>\d{4})\b

6Nahrazovacívýraz:\g<den>.\g<měsíc>.\g<4>

7Analyzovanýtext:1.2.34562,3,45673-4-56784.5-678912/3/4567\\

876/5/43211-2-34567.65.4321

9----------------------------------------

10Regulárnívýraz:\b(?P<den>(?:3[01])|(?:[12][0-9])|[1-9])(?P<sep>[./-])

11(?P<měsíc>(?:1[012])|[1-9])\2(?P<rok>\d{4})\b

12Nahrazujícívýraz:\g<den>.\g<měsíc>.\g<4>

13Analyzovanýtext:1.2.34562,3,45673-4-56784.5-678912/3/4567

1476/5/43211-2-34567.65.4321

15Nahrazenýtext:1.2.34562,3,45673.4.56784.5-678912.3.4567

1676/5/43211.2.34567.65.4321

17Oddělenétexty:['','2,3,4567','4.5-6789','\n76/5/4321','7.65.4321']

18Nalezenétexty:['1.2.3456','3-4-5678','12/3/4567','1-2-3456']

19Skupinyv'1.2.3456':{1:'1',2:'.',3:'2',4:'3456'}

20Ztohopojmenované:{'den':'1','sep':'.','měsíc':'2','rok':'3456'}

21Skupinyv'3-4-5678':{1:'3',2:'-',3:'4',4:'5678'}

22Ztohopojmenované:{'den':'3','sep':'-','měsíc':'4','rok':'5678'}

23Skupinyv'12/3/4567':{1:'12',2:'/',3:'3',4:'4567'}

24Ztohopojmenované:{'den':'12','sep':'/','měsíc':'3','rok':'4567'}

25Skupinyv'1-2-3456':{1:'1',2:'-',3:'2',4:'3456'}

26Ztohopojmenované:{'den':'1','sep':'-','měsíc':'2','rok':'3456'}

27>>>

Po zadání prověřovacího textu program oddělí řádkem pomlček komunikační řádky od řádků s výsledky a vypíše zadané texty bez případných znaků ukončení fyzického řádku. V místě, kde se opravdu nachází znak konce řádku, zobrazí znak '' symbolizující konec logického řádku. Pokud se vám nelíbí nebo ve vámi používaném fontu není, zadejte svoji volbu do proměnné rtc.EOL.

Ve výpisech v knize jsou v sekci Analyzovaný text podbarvením a podtržením zvýrazněny části odpovídající vyhledávacímu regulárnímu výrazu. Je-li jich více, tak se barva podbarvení i počet čar podtržení střídá, aby byly jednotlivé texty jasně rozpoznány i v případě, že spolu sousedí. Kvůli čtenářům tištěných verzí jsou navíc pro jistotu střídavě jednoduše a dvojitě podtrženy. To samozřejmě v konzolovém okně neuvidíte, zobrazuje to ale výše zmíněné GUI.

Protože řada čtenářů si pořizuje knihu ve formátu EPUB a některé čtečky nezobrazují barvy (a navíc často neumějí zobrazit dvojité podtržení), přidávám pod řádek s analyzovaným textem ještě řádek se seznamem textů obdržených od funkce, které zadáme regulární výraz jako oddělovač, a nakonec řádek se seznamem nalezených textů odpovídajících zadanému regulárnímu výrazu. Ten je uveden jako poslední, protože jej v příští kapitole budeme ještě doplňovat.

2.3Standardní znaky a metaznaky

\.^+?*$|([{}])

Zadání metaznaku jako standardního znaku

2.4Vyhledání textu odpovídajícího regulárnímu výrazu

Výpis 2.2: Použití jednoduchého regulárního výrazu

6

2.5Zdrojové kódy