diff --git a/lessons/fast-track/filesystem/index.md b/lessons/fast-track/filesystem/index.md new file mode 100644 index 0000000000..08269c3fa5 --- /dev/null +++ b/lessons/fast-track/filesystem/index.md @@ -0,0 +1,198 @@ +# Soubory a cesty + +Informace uložené v Pythonních proměnných – seznamech, slovnících, a tak dále – jsou dočasné. +Jakmile Python ukončíš, zmizí. +Chceš-li něco uložit na delší dobu, nebo třeba sdílet s jinými programy, +můžeš si informace uložit do souboru. + +S počítačovými soubory (angl. *files*) už ses asi setkal{{a}}. +Teď se na ně ale podívejme trochu podrobněji. + +Soubor je místo, kam se dají ukládat informace – *obsah*. Kromě obsahu mají soubory ještě další vlastnosti: + +* *jméno*, podle kterého se soubor dá najít, +* informace o *vlastnictví* a *oprávnění*, které určují kdo může ze souboru + číst a kdo do něj může zapisovat, +* informace o *časech* vytvoření, posledního zápisu, a podobně, +* a další informace, na každém druhu operačního systému jiné. + +Tak jako je kapitola v knížce poskládaná z písmenek, obsah souboru je +poskládaný z *bajtů* (angl. *bytes*) – malých čísel. +Každá informace, kterou počítač umí zpracovat, se dá zakódovat do bajtů +podobně jako se z písmenek skládá text. + +Soubory jsou většinou uloženy na disku (nebo podobném médiu), kde bývá místo +na bilióny bajtů. +Aby počítač poznal, kde na disku je který soubor, používá *souborový systém* +(angl. *filesystem*). +Ten plní podobnou funkci jako v knížce obsah, jména kapitol a čísla stránek. + + +## Operační systémy + +Souborových systémů existuje spousta druhů. +Experti nám snad prominou hrubé zjednodušení, když si je rozdělíme na dva +druhy: ty pro Windows a ty pro Unix. + +Unix je operační systém, vytvořený v sedmdesátých letech, ze kterého vycházejí +dnešní systémy Linux, macOS a další. +Základní principy, o kterých bude řeč tady, se od dob Unixu většinou +příliš nezměnily. +A tak když v těchto materiálech uvidíš jméno „Unix“, jde o něco společné pro +Linux i macOS. +Hlavní rozdíly mezi Linuxem a macOS jsou v konvencích – např. na Linuxu se +místo pro domovské adresáře jmenuje většinou `/home`, kdežto na macOS `/Users`. + +Další rozšířený operační systém, Windows, z Unixu nevychází. +Některé věci se v něm, jak později uvidíme, chovají jinak. + + +## Adresáře + +Na dnešních souborových systémech jsou soubory tříděny do *adresářů* neboli +*složek* (angl. *directory*, *folder*). +Adresář může obsahovat spoustu souborů nebo i jiných adresářů. + +A teď něco, co pro tebe může být nové: pro programátory jsou adresáře taky soubory. +Souborů je dokonce spousta druhů: *normální soubory* s informacemi, adresáře +(které obsahují další soubory), speciální soubory které můžou reprezentovat +celý disk nebo spojení mezi počítači, odkazy na jiné soubory, a tak dále. +Co je a co není soubor závisí na systému. +Dnes se proto omezíme jen na dva druhy souborů, které najdeme jak na Windows +tak na Unixu: normální datové soubory (ty, které si pod jménem „soubor“ +představí běžný uživatel) a adresáře. + + +## Cesty + +Abys mohl{{a}} najít nějaký soubor, potřebuješ znát jeho jméno a adresář, +který ten soubor obsahuje. +Abys pak mohl/a najít ten adresář, musíš opět znát jméno adresáře a adresář, +který ho obsahuje. +A tak dál, až se dostaneš ke *kořenovému adresáři* (angl. *root directory*), +který (zjednodušeně řečeno) obsahuje celý souborový systém. +Když napíšeš jména všech adresářů které takhle projdeš za sebe, dostaneš +*cestu* (angl. *path*) k danému souboru. +Taková cesta by mohla na Unixu být třeba: + +* Linux: `/home/janca/Documents/archiv.tar.gz` +* macOS: `/Users/janca/Documents/archiv.tar.gz` + +To znamená, že začneš v kořenovém adresáři (který se na Linuxu jmenuje `/`), +v něm hledáš adresář `home` nebo `Users` (ten tradičně obsahuje domovské +adresáře uživatelů), v něm pak `janca` (podle uživatelského jména), +v něm `Documents`, a v něm pak `archiv.tar.gz`. +To už není adresář, ale normální soubor do kterého se dají zapsat informace. + +Obdobná cesta na Windows by mohla být třeba: +`C:\Users\Jana\Documents\archiv.tar.gz` + +Tahle cesta začíná na disku `C:`. +Windows mají na rozdíl od Unixu zvláštní souborový systém pro každý disk, +a tak mají víc kořenových adresářů – třeba `C:\` a `D:\`. +Dál je to podobné jako na Unixu, jen oddělovač adresářů je zpětné lomítko +místo obyčejného. + +## Absolutní a relativní cesty + +Cesta, která začíná v konkrétním kořenovém adresáři, se nazývá *absolutní* +cesta (angl. *absolute* path). Je jako úplná poštovní adresa: +správně nadepsaný dopis můžu hodit do schránky kdekoli na světě a (teoreticky) +vždy dojde k adresátovi. + +Když ale dopis do Česka házím do české schránky, můžu vynechat informaci +o kontinentu a zemi. +Je to tak kratší a jednodušší‚ ale z Austrálie by to nefungovalo. + +Na podobném principu jsou založeny *relativní cesty* (angl. *relative paths*). +Když už jsi v domovském adresáři, stačí zadat cestu `Documents/archiv.tar.gz`, +bez lomítek či jména disku na začátku. +To znamená, že cesta nezačíná v kořenovém adresáři, ale v *aktuálním adresáři* +(angl. *current directory*) – tam, kde právě jsi. +Kdyby ses pomocí `cd` přepnul{{a}} jinam, tahle relativní cesta by přestala +fungovat. + +Kde relativní cesta „začíná“, to záleží na kontextu – většinou jde o aktuální +adresář, ale může to být třeba gitový repozitář +(hlavní adresář nějakého projektu), adresář s programem který právě běží, +a podobně. + +## Dvě tečky a jedna tečka + +Každý adresář obsahuje dva speciální záznamy: `..` a `.`. + +Jméno `.` (tečka) vždy označuje samotný adresář. +Tudíž `/home/janca` je stejný adresář jako `/home/janca/.`, +`/home/janca/././././.` a tak dál. +To nezní moc užitečně – ale jen do té doby, než potřebuješ zadat jako +relativní cestu samotný aktuální adresář. + +Jméno `..` (dvě tečky) označuje *nadřazený adresář*. +Když se tohle jméno objeví v cestě, znamená to, že potřebujeme přejít +o úroveň výš. +Jsi-li v adresáři `/home/janca/Pictures/dovolena`, tak: + +* cesta `..` znamená adresář `/home/janca/Pictures` +* cesta `../../programy/venv` znamená `/home/janca/programy/venv` +* absolutní cesta `/home/janca/Documents/../Pictures/dovolena` + znamená `/home/janca/Pictures/dovolena` + +> [note] +> Striktně řečeno, výše uvedené neplatí vždycky: +> speciální soubory zvané *symbolické odkazy* (angl. *symlinks*) můžou počítač +> při procházení cesty přesměrovat tak, že `Documents/../Pictures` bude jiný +> soubor než `Pictures`. +> Detaily jsou nad rámec těchto materiálů, nicméně je to důvod, proč Python +> nebude automaticky nahrazovat `Documents/../Pictures` za `Pictures`. + + +## Jména souborů + +Jméno souboru je řetězec. Nemůže to ale být jakýkoli řetězec. +Různé systémy mají různou maximální délku jména (i když na tenhle limit dnes +většinou nenarazíš). +A navíc je omezen i obsah – jméno souboru nesmí obsahovat: + +* na Unixu oddělovač adresářů `/` ani speciální nulový znak, +* na Windows oddělovač `\`, znaky `<>:"/|?*` ani speciální znaky (např. + tabulátor, znak nového řádku). + +Nedoporučuji s názvy příliš experimentovat, protože některé validní znaky +můžou v určitých kontextech mít zvláštní význam (např. `*` a `?` jako zástupné +znaky), špatně se používají (např. mezery a nové řádky), působí problémy +s kódováním (např. písmena s diakritikou nebo emoji), nebo naráží na problémy +s tím, že Windows nerozlišují velikost písmen ale Unix ano. + +Programátoři by se tak měli omezit na: + +* malá písmena bez diakritiky, +* číslice `0` - `9`, +* pomlčku `-`, +* podtržítko `_`, a +* tečku jako oddělovač přípony. + +Možná sis všiml{{a}}, že normální lomítko, `/`, nesmí na Windows být ve jménu +souboru. +Spousta moderních programů (včetně většiny knihoven v Pythonu) toho využívá a +dopředná lomítka automaticky zaměňuje za zpětná. +Můžeš tak na všech systémech používat stejné relativní cesty: `Documents/archiv.tar.gz` většinou funguje i na Windows. + + +## Přípony + +Hodně jmen souborů obsahuje tečku a za ní krátkou *příponu* (angl. *extension*), +která tradičně indikuje formát souboru – způsob, +kterým jsou v souboru zakódovány informace. +Například soubor `hrad.jpeg` má příponu `.jpeg`. +Ten kdo ví, že [JPEG](https://cs.wikipedia.org/wiki/JPEG) je způsob zakódování +obrázku (zvlášť vhodný pro fotografie), si tak může domyslet že v souboru je +nejspíš fotka hradu. + +Pythonisti zase poznají příponu `.py`. +Python samotný ji vyžaduje: příkaz `import module` hledá soubor `module.py`. + +Přípony jsou zvlášť důležité na Windows, kde se podle nich vybírá program, +kterým se soubor otevře. +(Unix se oproti tomu dívá v prvé řadě na samotný obsah souboru.) + +Přípon se může objevit i víc: `archiv.tar.gz` nejspíš obsahuje několik souborů spojených dohromady ve formátu [tar](https://cs.wikipedia.org/wiki/Tar_%28informatika%29) a pak zkomprimovaných ve formátu [gzip](https://cs.wikipedia.org/wiki/Gzip). diff --git a/lessons/fast-track/filesystem/info.yml b/lessons/fast-track/filesystem/info.yml new file mode 100644 index 0000000000..949fd52838 --- /dev/null +++ b/lessons/fast-track/filesystem/info.yml @@ -0,0 +1,4 @@ +title: Souborové systémy +style: md +attribution: Petr Viktorin, 2018-2019 +license: cc-by-sa-40 diff --git a/lessons/intro/pathlib/index.md b/lessons/intro/pathlib/index.md new file mode 100644 index 0000000000..7d88fb2a34 --- /dev/null +++ b/lessons/intro/pathlib/index.md @@ -0,0 +1,621 @@ + +# Cesty a soubory s Pathlib + +Základní práci se soubory – čtení z nich a psaní do nich – rozebírá +[lekce o souborech](naucse:page?lesson=beginners/files). Pro zopakování: + +``` python +# Otevření textového souboru "basnicka.txt" pro čtení +with open('basnicka.txt', encoding='utf-8') as soubor: + # Přečtení obsahu + contents = soubor.read() + +# Velikost souboru +print(len(soubor)) +``` + +Jméno souboru, případně cesta k němu, se tradičně zadává jako řetězec. +Jednotlivé adresáře jsou odděleny lomítkem (případně na Windows zpětným lomítkem); +fungují tu absolutní i relativní cesty. + +Pro prosté otvírání známých souborů to stačí. +Když ale potřebuješ s cestami k souborům pracovat víc, +řetězce jsou docela nepohodlné. +A navíc je problém pamatovat na všechny různé případy, které můžou nastat. + +Zkus pro příklad napsat funkce, které dostanou cestu k souboru a: + +* `vrat_casti` rozdělí cestu na jednotlivé adresáře (a vrátí je jako seznam), +* `vrat_priponu` vrátí příponu souboru. + +Na mém Linuxovém počítači cesty vypadají jako +`/home/janca/Documents/archiv.tar.gz`, takže bych mohl napsat něco jako: + +```python +def vrat_casti(path): + """Vrátí seznam komponentů cesty (jednotlivých adresářů/souborů)""" + return path.split('/') + +def vrat_priponu(path): + """Vrátí příponu souboru""" + parts = path.split('.') + return parts[-1] +``` + +Pro mou cestu to funguje: + +```pycon +>>> retezcova_cesta = '/home/janca/Documents/archiv.tar.gz' + +>>> vrat_casti(retezcova_cesta) +['', 'home', 'janca', 'Documents', 'archiv.tar.gz'] +>>> vrat_pripona(retezcova_cesta) +'gz' +``` + +Ale pro jinou cestu na jiném počítači už ne: + +```pycon +>>> retezcova_cesta = 'C:\\Users\\Jana\\Programy\\superprojekt\\README' + +>>> vrat_casti(retezcova_cesta) +['C:\\Users\\Jana\\Programy\\superprojekt\\README'] +>>> vrat_priponu(retezcova_cesta) +'C:\Users\Jana\Programy\superprojekt\README' +``` + +> [note] +> To, že programátoři používali na cesty řetězce a nepromýšleli všechny možné +> podivnosti souborových systémů, je hlavní důvod proč si ještě dnes spousta +> programů neporadí s diakritikou nebo mezerami v názvech souborů. + +Jde to líp? Samozřejmě! + + +## Knihovna pathlib + +Od verze 3.4 obsahuje Python knihovnu `pathlib`, jejíž třída `Path` reprezentuje +cestu k souboru a umožňuje s takovými cestami jednoduše a bezpečně manipulovat. + +```pycon +>>> from pathlib import Path + +>>> # Cesta, která na Windows i Unixu funguje podobně: +>>> cesta = Path('/home/janca/Documents/archiv.tar.gz') +>>> cesta.parts +('/', 'home', 'janca', 'Documents', 'archiv.tar.gz') +>>> cesta.suffix +'.gz' +``` + +Ukázka s cestou pro Windows (která by na Unixu nefungovala): + +> [note] +> Pouštíš-li ukázku na Windows, můžeš místo `PureWindowsPath` použít rovnou +> `Path`. + +```pycon +>>> from pathlib import PureWindowsPath + +>>> win_cesta = PureWindowsPath('C:\\Users\\Jana\\Programy\\superprojekt\\README') +>>> win_cesta.parts +('C:\\', 'Users', 'Jana', 'Programy', 'superprojekt', 'README') +>>> win_cesta.suffix +'' +``` + +Ukažme si teď něco z toho, co `pathlib` umožňuje. +Nebude to všechno – další možnosti najdeš [na taháku] nebo v angličtině +v [dokumentaci]. + +[dokumentaci]: https://docs.python.org/3/library/pathlib.html +[na taháku]: https://pyvec.github.io/cheatsheets/pathlib/pathlib-cs.pdf + + +## Tvoření cest + +Cesty v `pathlib` se tvoří zavoláním třídy `Path`. +Na Windows se tím vytvoří `WindowsPath`, na Unixu `PosixPath`. + +Obě považují dopředná lomítka za oddělovač adresářů, +takže následující bude fungovat na všech systémech: + +```pycon +>>> docs_cesta = Path('/home/janca/Documents') +>>> docs_cesta +PosixPath('/home/janca/Documents') +``` + +Už při vytvoření cesty se takto *normalizuje*, zjednoduší bez změny významu. +Víc lomítek za sebou se spojí do jednoho, zbytečné adresáře nebo lomítka na +konci se vynechají. + +```pycon +>>> Path('/tmp//foo/./bar/') +PosixPath('/tmp/foo/bar') +``` + +Když chci k takové cestě něco připojit, použiju operátor `/` (který by se měl +používat na dělení, ale psst!): + +```pycon +>>> docs_cesta / 'archiv.tar.gz' +PosixPath('/home/janca/Documents/archiv.tar.gz') +``` + +Přidávat se takhle dají řetězcové cesty, nebo i další `Path`: + +```pycon +>>> Path('/') / 'home/janca' / Path('archiv.tar.gz') +PosixPath('/home/janca/archiv.tar.gz') +``` + +Pozor ale na to, že absolutní cesta (s lomítkem nebo jménem disku na začátku) +znamená, že procházení začíná znovu od kořenového adresáře. +Když k něčemu připojím absolutní cestu, předchozí cesta se zahodí. + +```pycon +>>> Path('/home/janca') / '/tmp/foo' +PosixPath('/tmp/foo') +``` + +Občas lomítko není pohodlné. +V takových případech jde použít metoda `joinpath`, která má stejný efekt: + +```pycon +>>> Path('/').joinpath('home', 'janca/archiv.tar.gz') +PosixPath('/home/janca/archiv.tar.gz') +``` + + +## Atributy + +Cesty v pathlib mají spoustu užitečných atributů – vlastností, ke kterým se +dostaneš pomocí tečky: + +```pycon +>>> # Příklady ukážeme opět na téhle cestě: +>>> cesta = Path('/home/janca/Documents/archiv.tar.gz') +>>> cesta +PosixPath('/home/janca/Documents/archiv.tar.gz') + +>>> # jméno +>>> cesta.name +'archiv.tar.gz' + +>>> # Přípona (poslední) +>>> cesta.suffix +'.gz' + +>>> # Věchny přípony +>>> cesta.suffixes +['.tar', '.gz'] + +>>> # "kořen" jména (bez poslední přípony) +>>> cesta.stem +'archiv.tar' + +>>> # "rodič" – adresář, který tuto cestu obsahuje +>>> cesta.parent +PosixPath('/home/janca/Documents') + +>>> cesta.parent.parent +PosixPath('/home/janca') +>>> cesta.parent.parent.parent.parent +PosixPath('/') +``` + +Všechny "předky" -- rodiče, prarodiče, atd. -- nabízí atribut "parents". +Výsledek je ale *iterátor*; aby se ukázaly jednotlivé hodnoty, +je potřeba ho projít cyklem `for`, převést na seznam, atp. + +```pycon +>>> cesta.parents + + +>>> list(cesta.parents) +[PosixPath('/home/janca/Documents'), + PosixPath('/home/janca'), + PosixPath('/home'), + PosixPath('/')] + +>>> # Je cesta absolutní? +>>> cesta.is_absolute() +True +>>> Path('foo/archiv.zip').is_absolute() +False + +>>> # Jaká by byla relativní vzhledem k jiné, nadřazené cestě? +>>> relativni_cesta = cesta.relative_to('/home/janca') +>>> relativni_cesta +PosixPath('Documents/archiv.tar.gz') + +>>> # Spojením té nadřazené cesty a této relativní dostanu zpátky původní cestu +>>> Path('/home/janca') / relativni_cesta +PosixPath('/home/janca/Documents/archiv.tar.gz') + +>>> # Přepsání jména souboru (poslední části cesty) +>>> cesta.with_name('hrad.jpeg') +PosixPath('/home/janca/Documents/hrad.jpeg') + +>>> # Přepsání koncovky +>>> cesta.with_suffix('.bz2') +PosixPath('/home/janca/Documents/archiv.tar.bz2') + +>>> # Pokud existující koncovka není, `with_suffix` ji přidá +>>> Path('myproject/README').with_suffix('.xz') +PosixPath('myproject/README.xz') +``` + + +## Zkoumání disku + +Všechno uvedené výše jsou čistě „textové“ operace – pracují jen se jmény. +Soubor `archiv.zip` (ani jiné) počítači mít, aby ses dostal{{a}} k příponě +nebo ke jménům nadřazených adresářů. + +> [note] +> Dokonce si můžeš vyzkoušet, jak by to fungovalo na jiném systému – místo `Path` +> naimportuj a použij `PureWindowsPath` nebo `PurePosixPath`, které reprezentují +> Windowsové, resp. Unixové cesty. + +Zamysli se: k čemu se hodí umět pojmenovat soubor, který neexistuje? +{% filter solution %} +Jméno potřebuješ třeba když chceš soubor vytvořit. +{% endfilter %} + +Teď se dostaneme k operacím pro které je potřeba mít přístup k souborovému +systému. + +Nejdříve dvě funkce, které vrací cesty k užitečným adresářům: + +```pycon +>>> # Aktuální adresář +>>> Path.cwd() +PosixPath('/home/janca/pyladies/barvy') + +>>> # Můj domovský adresář +>>> Path.home() +PosixPath('/home/janca') +``` + +A základní otázky – existuje daný soubor? +Je to normální soubor nebo adresář? + +```pycon +>>> # Existuje na té ukázkové cestě nějaký soubor? +>>> cesta.exists() +False + +>>> # Existuje můj domovský adresář? +>>> Path.home().exists() +True + +>>> # A je to vůbec adresář? +>>> Path.home().is_dir() +True + +>>> # Je to normální datový soubor? +>>> Path.home().is_file() +False +``` + + +## Ukázka + +Abychom měli všichni stejné podmínky, stáhni si na další experimenty +[archiv s testovacími soubory](static/archiv.tar.gz). +Dej si ho do aktuálního adresáře (`Path.cwd()`), a pak ho rozbal pomocí +`tarfile`: + +```pycon +>>> import tarfile + +>>> cesta_k_archivu = Path("archiv.tar.gz") + +>>> # Co je v archivu? +>>> tarfile.open(cesta_k_archivu, 'r|gz').getnames() +['soubory', + 'soubory/hrad.jpeg', + 'soubory/hrad.attribution', + 'soubory/.gitignore', + 'soubory/kolecko.png', + 'soubory/texty', + 'soubory/texty/vodnik.txt', + 'soubory/texty/lidove', + 'soubory/texty/lidove/pes.txt', + 'soubory/texty/lidove/holka.txt', + 'soubory/texty/vladimir.txt', + 'soubory/texty/cizojazycne', + 'soubory/texty/cizojazycne/iroha.txt', + 'soubory/texty/cizojazycne/witch.txt', + 'soubory/hlad.txt', + 'soubory/hraz.attribution', + 'soubory/ententyky.txt', + 'soubory/hraz.jpeg', + 'soubory/README'] + +>>> # Extrakce archivu. (Kdybys to zkoušel/a pro jiné archivy, vždy před +>>> # rozbalením zkontroluj cesty všech souborů v archivu -- ať se rozbalením +>>> # nepřepíše nějaký důležitý soubor!) +>>> tarfile.open(cesta_k_archivu, 'r|gz').extractall() +``` + +Rozbalením archivu vznikl `./soubory/` (tedy: adresář `soubory` v aktuálním +adresáři). +Pojď se mu kouknout na zoubek: + +```pycon +>>> zaklad = Path('./soubory') +>>> zaklad +PosixPath('soubory') + +>>> print('Je to adresář?', zaklad.is_dir()) +Je to adresář? True +>>> print('Je to normální soubor?', zaklad.is_file()) +Je to normální soubor? False +``` + +Podle informací o archivu je v soubory nějaký `ententyky.txt` – podle přípony +soubor s textem. + +```pycon +>>> ententyky = zaklad / 'ententyky.txt' +>>> print('Je to adresář?', ententyky.is_dir()) +Je to adresář? False +>>> print('Je to normální soubor?', ententyky.is_file()) +Je to normální soubor? True +``` + +Objekty `Path` lze používat v naprosté většině situací, kdy jde použít cesta +jako řetězec. +Například pro funkci `open`: + +```python +with open(ententyky, encoding='utf-8') as file: + print(file.read()) +``` + +`Path` ale má `open` i jako metodu: + +```python +with ententyky.open(encoding='utf-8') as file: + print(file.read()) +``` + +A protože je čtení celého textového obsahu souboru docela užitečné, +existuje i zkratka která soubor otevře, přečte a zavře najednou: + +```python +print(ententyky.read_text()) +``` + +(Větší soubory je ale lepší otevřít ve `with` a zpracovávat třeba po řádcích, +aby se obsah nemusel do paměti počítače načíst celý najednou.) + +Existuje i `write_text`: + +```python +cesta = Path.cwd() / 'pisnicka.txt' +cesta.write_text('Holka modrooká\nNesedávej u potoka!') +``` + +{# +## Zpátky na stromy... tedy řetězce + +Ve většině případů jde Path použít tam, kde se cesta dá zadat jako řetězec. Ne ale vždy – pathlib existuje teprve od roku 2014, a některé Pythonní knihovny stále ještě vyžadují řetězce. + +Jedna z výjimek je IPython.display.Image, která umí v Notebooku vykreslit obrázek. + +```pycon +>>> from IPython.display import Image +``` + +Image potřebuje (aspoň začátkem roku 2018) řetězcovou cestu. +Příkaz `Image(base / 'hrad.jpeg')` mi skončil chybou typu – +`TypeError: a bytes-like object is required, not 'PosixPath'`. + +V takových případech ale stačí Path převést na řetězec. + +```pycon +>>> str(base / 'hrad.jpeg') +'soubory/hrad.jpeg' + +>>> Image(str(base / 'hrad.jpeg')) + +>>> # Informace o autorství obrázku (díky, Millenium187!) +>>> print(base.joinpath('hrad.attribution').read_text()) + +"hrad.jpg" is (c) 2011, Wikipedia user Millenium187 + https://commons.wikimedia.org/wiki/User:Millenium187 +Here used under the Creative Commons Attribution-Share Alike 3.0 Unported license. + +See: https://commons.wikimedia.org/wiki/File:Hrad_%C5%A0pilberk_II.jpg +``` +#} + +## A co adresáře? + +I s adresáři umí `pathlib` pracovat. +Nejzákladnější operace je získání cest k obsaženým souborům: + +```pycon +>>> zaklad.iterdir() + +``` + +Metoda iterdir opět vrací *iterátor* – objekt, přes který musíš „projít“ +(cyklem for, převedením na seznam ap.), abys z něj dostal{{a}} obsah. + +```pycon +>>> list(zaklad.iterdir()) +[PosixPath('soubory/hrad.jpeg'), + PosixPath('soubory/hrad.attribution'), + PosixPath('soubory/.gitignore'), + PosixPath('soubory/kolecko.png'), + PosixPath('soubory/texty'), + PosixPath('soubory/hlad.txt'), + PosixPath('soubory/hraz.attribution'), + PosixPath('soubory/ententyky.txt'), + PosixPath('soubory/hraz.jpeg'), + PosixPath('soubory/README')] + +>>> for cesta in zaklad.iterdir(): +>>> print(cesta) +soubory/hrad.jpeg +soubory/hrad.attribution +soubory/.gitignore +soubory/kolecko.png +soubory/texty +soubory/hlad.txt +soubory/hraz.attribution +soubory/ententyky.txt +soubory/hraz.jpeg +soubory/README +``` + +{# + +## Glob Glob + +Zajímavější operace je ale `glob`, která vyfiltruje soubory, které odpovídají +určité šabloně. + +V šabloně můžeš použít `*`, které odpovídá 0 a více písmenům +(v rámci jména jednoho souboru): + +```pycon +>>> # Soubory končící na ".txt" +>>> list(base.glob('*.txt')) +[PosixPath('soubory/hlad.txt'), PosixPath('soubory/ententyky.txt')] + +>>> # Soubory, které mají ve jméně tečku +>>> list(base.glob('*.*')) +[PosixPath('soubory/hrad.jpeg'), + PosixPath('soubory/hrad.attribution'), + PosixPath('soubory/.gitignore'), + PosixPath('soubory/kolecko.png'), + PosixPath('soubory/hlad.txt'), + PosixPath('soubory/hraz.attribution'), + PosixPath('soubory/ententyky.txt'), + PosixPath('soubory/hraz.jpeg')] +``` + +… nebo `?`, což odpovídá jednomu písmenu: + +```pycon +>>> # Slovo na čtyři, první je `h` a třetí `a` +>>> list(base.glob('h?a?.*')) +[PosixPath('soubory/hrad.jpeg'), + PosixPath('soubory/hrad.attribution'), + PosixPath('soubory/hlad.txt'), + PosixPath('soubory/hraz.attribution'), + PosixPath('soubory/hraz.jpeg')] +``` + +Případně jde použít výčet písmen v hranatých závorkách, viz modul fnmatch. + +```pycon +>>> list(base.glob('h?a[zd].????')) +[PosixPath('soubory/hrad.jpeg'), PosixPath('soubory/hraz.jpeg')] +>>> list(base.glob('[!hv]*')) +[PosixPath('soubory/.gitignore'), + PosixPath('soubory/kolecko.png'), + PosixPath('soubory/texty'), + PosixPath('soubory/ententyky.txt'), + PosixPath('soubory/README')] +``` + +Poslední speciální kombinace je `**`. +Dvě hvězdičky odpovídají základnímu adresáři a všem jeho podadresářům, +pod-podadresářům, pod-pod-podadresářům atd. + +```pycon +>>> list(base.glob('**')) +[PosixPath('soubory'), + PosixPath('soubory/texty'), + PosixPath('soubory/texty/lidove'), + PosixPath('soubory/texty/cizojazycne')] +``` + +S pomocí ** se často hledají soubory s danou příponou: + +```pycon +>>> list(base.glob('**/*.txt')) +[PosixPath('soubory/hlad.txt'), + PosixPath('soubory/ententyky.txt'), + PosixPath('soubory/texty/vodnik.txt'), + PosixPath('soubory/texty/vladimir.txt'), + PosixPath('soubory/texty/lidove/pes.txt'), + PosixPath('soubory/texty/lidove/holka.txt'), + PosixPath('soubory/texty/cizojazycne/iroha.txt'), + PosixPath('soubory/texty/cizojazycne/witch.txt')] +``` + +#} + + +## Strom adresářů – rekurze + +Adresáře, podadresáře a soubory v nich tvoří strukturu, na kterou se často +používají rekurzivní funkce. + +Tady je funkce `vypis_soubory`, která vypíše všechny soubory v daném adresáři. +Před každé jméno dá odrážku `-`, aby to líp vypadalo: + +```python +from pathlib import Path + +def vypis_soubory(odrazka, adresar): + """Vypíše odrážkový seznam jmen souborů v daném adresáři""" + for soubor in adresar.iterdir(): + print(odrazka, soubor.name) + +vypis_soubory('-', Path.cwd()) +``` + +Odrážka se dá zadat: + +```python +vypis_soubory('*', Path.cwd()) +vypis_soubory(' *', Path.cwd()) +``` + +Tahle funkce se dá změnit, aby vypsala i obsahy *podadresářů*. +Jak? +Poté, co vypíše jméno nějakého podadresáře, zavolá funkci která vypíše +obsah toho podadresáře. +Takovou funkci ale už máš napsanou – stačí trochu změnit odrážku, aby bylo +poznat co je podadresář. + +```python +from pathlib import Path + +def vypis_soubory(odrazka, adresar): + """Vypíše odrážkový seznam jmen souborů v daném adresáři i podadresářích""" + for soubor in adresar.iterdir(): + print(odrazka, soubor.name) + if soubor.is_dir(): + vypis_soubory(' ' + odrazka, soubor) + +vypis_soubory('-', Path.cwd()) +``` + +Podobně lze například spočítat soubory v nějakém adresáři (i všech +podadresářích). + +```python +from pathlib import Path + +def spocitej_normalni_soubory(adresar): + """Vrátí počet normálních souborů v daném adresáři i všech podadresářích""" + pocet = 0 + for soubor in adresar.iterdir(): + if soubor.is_dir(): + pocet = pocet + spocitej_normalni_soubory(soubor) + elif soubor.is_file(): + pocet = pocet + 1 + return pocet + +print(spocitej_normalni_soubory(Path.cwd())) +``` diff --git a/lessons/intro/pathlib/info.yml b/lessons/intro/pathlib/info.yml new file mode 100644 index 0000000000..c47a93508f --- /dev/null +++ b/lessons/intro/pathlib/info.yml @@ -0,0 +1,5 @@ +title: Adresáře, soubory a cesty +style: md +attribution: +- Pro PyLadies CZ napsal Petr Viktorin, 2018-2019. +license: cc-by-sa-40 diff --git a/lessons/intro/pathlib/static/archiv.tar.gz b/lessons/intro/pathlib/static/archiv.tar.gz new file mode 100644 index 0000000000..7df609543e Binary files /dev/null and b/lessons/intro/pathlib/static/archiv.tar.gz differ