<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="/cjs/screen.xsl" media="screen"?>
<lecture>

<meta>
  <maintitle>Python</maintitle>
  <author>Jiří Znamenáček</author>
  <title>Moduly pro komprimaci a archivaci</title>
  <date>2016-07-22</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
</meta>
<!--
  „“–…
  ←→ ↑↓ ↔↕
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->


<slide title="Úvod">

    <p>
        Poměrně často potřebuje člověk pracovat s daty v nějakém komprimovaném formátu. Ať už se jedná o soubory (či celé adresářové struktury) zabalené pomocí <em>tar</em>u či <em>gzip</em>u nebo třebas o obrazová data uvnitř PNG-obrázku.
    </p>
    <p>
        Python poskytuje ve standardní knihovně podporu pro několik komprimačních formátů – konkrétně <em>zip</em>, <em>gzip</em>, <em>bz2</em>, <em>lzma</em> a <em>tar</em>. Snad nejhezčí na tom všem je, že s archívy se pracuje téměř úplně stejně snadno jako s obyčejnými soubory.
    </p>
    <note>
        Tím mám na mysli především použití kontextu <em>with</em> a rozumná výchozí nastavení přepínačů.
    </note>

</slide>
<slide title="„Zipování“">

    <p>
        Z podporovaných je formát ZIP asi nejrozšířenější mezi různými platformami, začneme proto od něj. V principu se skládá ze dvou částí:
    </p>
    <ul>
        <li>
            knihovna <strong><em>zlib</em></strong> – podpora pro komprimaci a dekomprimaci <strong>dat</strong>;
        </li>
        <li>
            knihovna <strong><em>zipfile</em></strong> – podpora pro komprimaci a dekomprimaci <strong>souborů</strong>.
            <note>Pro vlastní práci s daty používá právě knihovnu <em>zlib</em>.</note>
        </li>
    </ul>

</slide>
<slide title="„zlib“ – komprese a dekomprese">

    <p>
        Vzhledem k výchozímu nastavení parametrů je ve většině případů komprimace a dekomprimace <strong>binárních dat</strong> velmi jednoduchá záležitost:
    </p>
    <ul>
        <li>
            <strong>zlib.compress(data[, level])</strong> – „zabalí“ binární <em>data</em> a vrátí jim odpovídající bajtový objekt.
            <note>Výchozí úroveň komprimace je standardně 6, ale můžete ji pochopitelně změnit od 0 (žádná) až po 9 (největší, ale také nejpomalejší).</note>
        </li>
        <li>
            <strong>zlib.decompress(data[, wbits[, bufsize]])</strong> – „rozbalí zabalená“ binární <em>data</em> zpět do jejich (bajtové) nekomprimované podoby.
            <note>Většinu času se o zbývající dva parametry nemusíte zajímat.</note>
        </li>
    </ul>
    <example lang="python">
        >>> xs = bytes("Pirát Pirátovič", encoding="utf-8")
        >>> xs
        b'Pir\xc3\xa1t Pir\xc3\xa1tovi\xc4\x8d'

        >>> import zlib
        >>> ys = zlib.compress(xs)
        >>> ys
        b'x\x9c\x0b\xc8,:\xbc\xb0D!\x00L\xe5\x97e\x1e\xe9\x05\x00O\x80\x08\xc6'
        >>> zlib.decompress(ys)
        b'Pir\xc3\xa1t Pir\xc3\xa1tovi\xc4\x8d'
    </example>

</slide>
<slide title="„zlib“ – kontrolní součet">

    <p>
        Součástí formátu ZIP je i výpočet kontrolního součtu nad zadanými daty. Knihovna <em>zlib</em> obsahuje dvě metody:
    </p>
    <ul>
        <li>
            tradiční <strong>zlib.crc32(data[, value])</strong> – spočítá <em>Cyclic Redundancy Check</em> nad binárními <em>daty</em> a vrátí ho jako nezáporné (32-bit) číslo. Výchozí hodnota pro parametr <em>value</em> – a tedy startovní hodnota pro výpočet kontrolního součtu – je 0, použití jiného čísla (předchozího CRC) umožňuje průběžně dopočítávat CRC nad posloupností (spojených) vstupů:
            <example lang="python">
                >>> xs1 = bytes("Pirát", encoding="utf-8")
                >>> xs2 = bytes("Pirátovič", encoding="utf-8")

                >>> crc = zlib.crc32(xs1)
                >>> crc
                2613429483
                >>> crc = zlib.crc32(xs2, crc)
                >>> crc
                3196918212

                >>> zlib.crc32(xs1 + xs2)
                3196918212
            </example>
        </li>
        <li>
            <strong>zlib.adler32(data[, value])</strong> – jako předchozí, ale počítá o něco slabší (byť podstatně rychlejší) kontrolní součet typu <em>Adler-32</em>.
            <note>Narozdíl od CRC je výchozí hodnotou pro parametr <em>value</em> 1.</note>
        </li>
    </ul>
    <p>
        PS: Ani jedna z těchto metod není samozřejmě vůbec vhodná pro výpočet <em>hash</em>ů, natožpak dokonce pro kryptografii.
    </p>

</slide>
<slide title="„zlib“ – velká data">

    <p>
        Pokud byste byli nuceni pracovat s daty, která by se vám nevlezla najednou do paměti, obsahuje knihovna <em>zlib</em> metody <em>zlib.compressobj()</em> a <em>zlib.decompressobj()</em> se spoustou možností nastavení.
    </p>
    <p>
        Uvedené metody vrací objekty, které podporují (případnou částečnou) komprimaci a dekomprimaci nad zadanými (binárními) daty. Jednoduchý příklad:
    </p>
    <example lang="python">
        >>> xs = bytes("Pirát Pirátovič", encoding="utf-8")

        >>> zlib.compress(xs)
        b'x\x9c\x0b\xc8,:\xbc\xb0D!\x00L\xe5\x97e\x1e\xe9\x05\x00O\x80\x08\xc6'

        >>> z = zlib.compressobj()
        >>> z.compress(xs)
        b'x\x9c'
        >>> z.flush()
        b'\x0b\xc8,:\xbc\xb0D!\x00L\xe5\x97e\x1e\xe9\x05\x00O\x80\x08\xc6'
    </example>
    <p>
        Zde použitá data jsou samozřejmě příliš krátká, aby to celé dávalo rozumný smysl. Pro další metody a jejich použití viz <a href="https://docs.python.org/3/library/zlib.html" class="external">originální dokumentace</a>.
    </p>

</slide>
<slide title="„zipfile“">

    <p class="enumerate">
        Komprimace pomocí klasického ZIPu není sice zdaleka tak účinná jako jiné (novější) metody, ale narozdíl od většiny jiných se s daty v archívu pracuje velmi snadno – „zabalíte-li“ více souborů (nebo rovnou adresářů), je každý jednotlivý prvek přístupný nezávisle na ostatních.
    </p>
    <note>
        Narozdíl třeba od komprimací <em>7zip</em> nebo RAR, které jsou sice podstatně účinnější, ale „vybalení“ souboru „daleko od začátku archívu“ obnáší rozkomprimování celého archívu minimálně po inkriminované místo.
    </note>
    <!-- XXX: ověřit, že RAR se opravdu chová stejně jako 7zip -->

    <p class="enumerate">
        Knihovna <em>zipfile</em> slouží jako rozhraní pro komprimaci/dekomprimaci i u formátů <em>bzip2</em> a <em>LZMA</em>, kteréžto se staly součástí standardu ZIP v letech 2001, respektive 2006.
    </p>

</slide>
<slide title="„zipfile“ – úvod">

    <p>
        Základním objektem pro práci se ZIP-archívy je <strong><em>zipfile.ZipFile</em></strong>. Pomocí něj získáte přístup k již existujícím i nově vytvářeným zip-archívům. Samozřejmostí je použití uvnitř <em>kontextového manažeru</em>, tedy:
    </p>
    <example lang="python">
        from zipfile import ZipFile

        with ZipFile('spam.zip', 'w') as myzip:
            myzip.write('eggs.txt')
    </example>
    <p>
        Objekt typu <em>ZipFile</em> je možno <a href="?slajd=8">zavést s různými parametry</a> podle typu práce, který vás čeká, a také na něm podle toho volat <a href="?slajd=9">různé metody</a>.
    </p>

</slide>
<slide title="„zipfile.ZipFile“">

    <p>
        Plný konstruktor objektu typu <em>ZipFile</em> jest..
    </p>
    <pre>    zipfile.ZipFile(
        file,
        mode='r',
        compression=ZIP_STORED,
        allowZip64=True
    )</pre>
    <p>
        ..což znamená, že se otevírá existující zip-archív pro čtení a je zapnuta podpora pro archívy větší než 2 GB.
    </p>

</slide>
<slide title="„zipfile.ZipFile“ – módy otevření">

    <p>
        Podle typu otevření souboru archívu získáte následující vlastnosti:
    </p>
    <ul>
        <li>
            <strong>r</strong> – základní mód, otevře zip-archív pro čtení;
        </li>
        <li>
            <strong>w</strong> – otevře zip-archív pro zápis, přičemž pokud uvedený soubor již existoval, jeho obsah bude smazán;
        </li>
        <li>
            <strong>x</strong> – otevírá zip-archív pro zápis jako předchozí mód, ale v případě existujícího souboru nic neudělá a vyhodí výjimku <em>FileExistsError</em>;
        </li>
        <li>
            <strong>a</strong> – nejzajímavější z módů, umožňuje do již existujícího zip-archívu přidávat nové položky.
        </li>
    </ul>
    <p>
        Otevřený ZipFile-objekt poskytuje všechny své metody nezávisle na způsobu otevření, což znamená, že pokusíte-li se zavolat například <code>ZipFile.write()</code> na archívu otevřeném jako <em>'r'</em>, se zlou se potážete (konkrétně obdržíte výjimku <em>RuntimeError</em>).
    </p>

</slide>
<slide title="„zipfile.ZipFile“ – módy komprimace">

    <p>
        Typy komprimace jsou podporovány následující čtyři:
    </p>
    <ul>
        <li>
            <strong>zipfile.ZIP_STORED</strong> – výchozí nastavení, uložení souborů do zip-archívu bez komprimace;
        </li>
        <li>
            <strong>zipfile.ZIP_DEFLATED</strong> – původní komprimační schéma formátu ZIP;
        </li>
        <li>
            <strong>zipfile.ZIP_BZIP2</strong> – komprimační metoda <em>bzip2</em>;
        </li>
        <li>
            <strong>zipfile.ZIP_LZMA</strong> – komprimační metoda <em>LZMA</em>.
        </li>
    </ul>
    <p>
        Všimněte si především, že výchozím nastavením je <em>zipfile.ZIP_STORED</em>, tedy práce se zip-archívy bez komprimace. To se může hodit, pokud je třeba poskytnout data v adresářové struktuře v rámci jednoho zip-archívu, ale není třeba (nebo není z výpočetních důvodů možno) objem dat zmenšovat. Typicky to asi ale nebude to, co budete chtít – z hlediska přenositelnosti mezi různými operačními systémy je nejvhodnější volbou asi klasický původní <em>zipfile.ZIP_DEFLATED</em>.
    </p>

</slide>
<slide title="„zipfile“ – výroba archívu 1">

    <p>
        Jeden soubor do archívu přidává metoda ZipFile-objektu <strong><code>write(SOUBOR)</code></strong>:
    </p>
    <example lang="python">
        import zipfile

        with zipfile.ZipFile('soubor.zip', 'w', zipfile.ZIP_DEFLATED) as z:
            z.write('soubor.txt')
    </example>
    <p>
        Její chování můžete upravit pomocí dalších dvou nepovinných parametrů:
    </p>
    <ul>
        <li>
            <code>arcname</code> – jméno použité pro archivovaný soubor uvnitř archívu;
        </li>
        <li>
            <code>compress_type</code> – změna typu komprese oproti globálnímu zadání (<em>ZipFile(compression)</em>).
        </li>
    </ul>
    <note>
        Výchozí hodnotou obou je <em>None</em>.
    </note>
    <p>
        Obzvláště <em>arcname</em> nalezne své uplatnění, nebudou-li cesty k archivovaným souborům zadány relativně (což je ale nepřekvapivě doporučováno).
    </p>

</slide>
<slide title="Příklad: Jak „zabalit“ adresářový strom">

    <p class="enumerate">
        Jelikož <em>zipfile</em> umí jako nejvyšší jednotku zabalit jeden konkrétní soubor, musíme si pro zabalení adresáře (v příkladu jím jest adresář <em>data</em>) trochu pomoci. Následuje skript předpokládající idealizovaný stav, kdy je adresář k zabalení na stejné úrovni souborového systému jako vlastní skript:
    </p>
    <example lang="python" src="_files/zipping/zipping.1.py" />
    <p class="enumerate">
        Ne vždy je však možné doporučený požadavek relativnosti cest rozumně dodržet. Pak se nám právě hodí parametr <em>arcname</em>:
    </p>
    <example lang="python" src="_files/zipping/zipping.2.py" />

</slide>
<slide title="„zipfile“ – výroba archívu 2">

    <p>
        Velmi zajímavou možností je posílat do archívu řetězce a „pouze“ jim určit, pod jakým souborovým jménem se v něm mají uložit. K tomu slouží metoda ZipFile-objektu:
    <blockquote>
        writestr(zinfo_or_arcname, ŘETĚZEC)
    </blockquote>
    </p>
    <p>
        Důležitým je zde první parametr metody <em>zinfo_or_arcname</em>, který právě určuje, kde řetězcová data skončí. Kromě obyčejného řetězce (určujícího cestu uvnitř archívu stejně jako dříve představený parametr <em>arcname</em>) jím totiž může také být instance objektu <strong><em>zipfile.ZipInfo</em></strong>, který zjednodušeně řečeno drží metadata jednotlivých souborů uvnitř archívu.
    </p>

</slide>
<slide title="„zipfile“ – informace o archívu 1">

    <p>
        Máme-li naopak již existující archív, můžeme se o něm spoustu věcí dozvědět, aniž bychom ho rozbalovali:
    </p>

    <p class="enumerate">
        Test archívu pomocí <strong><code>ZipFile.testzip()</code></strong>:
    </p>
    <example layout="vertical">
        <program lang="python" src="_files/zipping/zipping.3a.py" />
        <out lang="text" src="_files/zipping/zipping.3a.out" />
    </example>
    <p>
        Pro neporušený archív vrátí <em>None</em>, pro porušený jméno prvního souboru v archívu, u kterého selhala kontrola (kontrola CRC a hlavičky).
    </p>
    
    <p class="enumerate">
        Obsah archívu přehledně pomocí <strong><code>ZipFile.printdir()</code></strong>:
    </p>
    <example layout="vertical">
        <program lang="python" src="_files/zipping/zipping.3b.py" />
        <out lang="text" src="_files/zipping/zipping.3b.out" />
    </example>
    
    <p class="enumerate">
        Pouze jména souborů pomocí <strong><code>ZipFile.namelist()</code></strong>:
    </p>
    <example layout="vertical">
        <program lang="python" src="_files/zipping/zipping.3c.py" />
        <out lang="text" src="_files/zipping/zipping.3c.out" />
    </example>

</slide>
<slide title="„zipfile“ – informace o archívu 2">

    <p>
        Informace o souborech v archívu se také dají získat jako objekty typu <strong><em>zipfile.ZipInfo</em></strong>. Máme přitom dvě možnosti – pro všechny soubory najednou nebo pro jeden konkrétní.
    </p>
    
    <p class="enumerate">
        Informace o jednotlivých souborech pomocí <strong><code>ZipFile.infolist()</code></strong>:
    </p>
    <example layout="vertical">
        <program lang="python" src="_files/zipping/zipping.3d.py" />
        <out lang="text" src="_files/zipping/zipping.3d.out" />
    </example>
    
    <p class="enumerate">
        Informace o jednotlivých souborech pomocí <strong><code>ZipFile.getinfo(CESTA)</code></strong>:
    </p>
    <example layout="vertical">
        <program lang="python" src="_files/zipping/zipping.3e.py" />
        <out lang="text" src="_files/zipping/zipping.3e.out" />
    </example>

</slide>
<slide title="„zipfile“ – čtení z archívu">

    <p>
        Data z jednoho konkrétního souboru v archívu můžeme přečíst pomocí metody <strong><code>ZipFile.read(CESTA, pwd=None)</code></strong>:
    </p>
    <example layout="vertical">
        <program lang="python" src="_files/zipping/zipping.4a.py" />
        <out lang="text" src="_files/zipping/zipping.4a.out" />
    </example>
    <p>
        Data jsou vrácena jako bajtový řetězec. Mají-li představovat text, rozkódovat si je musíte sami.
    </p>
    <note>
        Parametr <em>pwd</em> slouží k případnému přenastavení hesla (případně nastaveného již dříve na globální úrovni pomocí <em>ZipFile.setpassword(pwd)</em>) pro chráněný archív.
    </note>

</slide>
<slide title="„zipfile“ – extrakce z archívu">

    <p class="enumerate">
        Další možností, jak se k datům v archívu dostat, je jednoduše je „rozbalit“ na disk. Jde to buď pro jeden soubor (<strong><code>extract(PRVEK, path=None, pwd=None)</code></strong>) nebo pro všechny (<strong><code>extractall(path=None, members=None, pwd=None)</code></strong>).
    </p>
    <blockquote>
        Přestože se knihovna <em>zipfile</em> snaží případné absolutní i relativní cesty osekat na zcela místní (a také odstraňuje pro danou platformu neznámé znaky), vřele se NEdoporučuje bezhlavě rozbalovat cizí archívy bez kontroly jejich obsahu!
    </blockquote>

    <p class="enumerate">
        Obě metody umožňují změnit místo extrakce z aktuálního adresáře na jiný pomocí parametru <em>path</em> a také případně přenastavit heslo (parametr <em>pwd</em>). Metodu <em>extractall()</em> je navíc možno doplnit o podseznam seznamu souborů v archívu vráceného metodou <em>ZipFile.namelist()</em>.
    </p>

</slide>
<slide title="„zipfile“ – extrakce z archívu (ukázka)">

    <p class="enumerate">
        Jeden konkrétní soubor:
    </p>
    <example lang="python" src="_files/zipping/zipping.4b.py" />
    
    <p class="enumerate">
        Vybraných vícero souborů:
    </p>
    <example lang="python" src="_files/zipping/zipping.4c.py" />

</slide>
<slide title="Poznámky">

    <p>
        Knihovna <em>zipfile</em> toho umí víc (mimo jiné připisovat až 64 kB dlouhé komentáře k archívům pomocí parametru <strong><code>ZipFile.comment</code></strong> nebo balit archívy pythoních zdrojových souborů).
    </p>
    <p>
        Podobně <strong><code>ZipInfo</code></strong> objekty obsahují spoustu zajímavých informací, především:
    </p>
    <example lang="python">
        ['CRC', 'FileHeader', …, 'comment', 'compress_size', 'compress_type',
         'create_system', 'create_version', 'date_time', 'external_attr',
         'extra', 'extract_version', 'file_size', 'filename', 'flag_bits',
         'header_offset', 'internal_attr', 'orig_filename', 'reserved', 'volume']
    </example>
    <p>
        Pro základní práci uvedené však ale snad stačí.
    </p>
    <!--note>
        Pravda, nezmínil jsem se vůbec o specifikách formátu <em>gzip</em>, natožpak <em>tar</em>. Snad to někdy udělám.
    </note-->

</slide>


</lecture>
