<?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>Bajtové objekty</title>
  <date>2011-11-10</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
</meta>
<!--
  „“–
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->


<slide title="Úvod">

  <p>
    <strong>Bajtové řetězce</strong> jsou <em>neproměnný</em> <a href="/materialy/python/types/_sequences.xml">sekvenční</a> datový typ, jehož prvky jsou <em>celá čísla</em> v rozmezí <code>&lt;0,&#160;255&gt;</code> (tedy vlastně sekvence bajtů).
  </p>
  <note>
    Existuje k nim i příslušný proměnný protějšek – <a href="bytearrays.xml">bajtová pole</a> (vlastně seznamy bajtů). Rozdíl mezi těmito dvěma objekty je podobný, jako mezi <em>řetězci</em> na jedné straně a <em>seznamy znaků</em> na straně druhé – oboje vypsané po prvcích dá to samé, ale do řetězců nemůžeme „hrabat“, zatímco se seznamy si můžeme dělat, co chceme.
  </note>
  <p>
    PS: Narozdíl od <em>Python'u 2.x</em>, kde jste si práci s unicodovými řetězci museli explicitně vyžádat a jinak bylo všechno jakýmsi tajemně se konvertujícím bajtovým objektem, musíte si naopak v <em>Python'u 3.x</em> explicitně vyžádat práci s bajtovými objekty a k žádným konverzím bez vašeho zapřičinění nedochází. <em>*SLÁVA*</em>
  </p>

</slide>
<slide title="Zavedení – přímou notací">

  <p class="enumerate">
    Bajtový řetězec je v podstatě „řetězec bajtů“, a tak ho nepřekvapivě nejsnáze zavedeme pomocí přímé (<em>literal</em>) notace jako řetězec s identifikátorem <code>b</code>:
  </p>
  <example lang="python">
>>> xb = b"ahoj"
>>> xb
b'ahoj'
>>> type(xb)
&lt;class 'bytes'>
  </example>
  <note>
    Jelikož různých znaků vyjádřitelných pomocí jednoho bajtu je právě 256 a znich pouze část spodní poloviny představuje tisknutelné znaky (stará dobrá ASCII-tabulka), setkáte se s bajtovými literály spíše v podobě hexadecimální, např. <code>b'\xf0\xf1\xf2'</code>.
  </note>
  
  <p class="enumerate">
    Podobně jako u tzv. „raw-řetězců“ můžete k identifikaci bajtových řetězců použít i velké písmeno <code>B</code>:
  </p>
  <example lang="python">
>>> xb = B"ahoj"
>>> xb
b'ahoj'
>>> type(xb)
&lt;class 'bytes'>
  </example>
</slide>
<slide title="„Řetězce“ bajtů">

  <p class="enumerate">
    Řetězce obsahují unicodové znaky, tj. sekvence bajtů, které daným kódováním určují odkazy do tabulky unicodových znaků:
  </p>
  <example lang="python">
>>> [x for x in 'ahoj']
['a', 'h', 'o', 'j']
  </example>
  <p>
    Bajtové řetězce na druhou stranu obsahují pouze bajty, tj. čísla 0-255:
  </p>
  <example lang="python">
>>> [x for x in b'ahoj']
[97, 104, 111, 106]
  </example>
  <note>
    A nepřekvapivě tedy platí například následující srovnání:
    <example lang="python">
        b'\x61\x68\x6f\x6a' == b'ahoj'  # hexadecimálně
        b'\141\150\157\152' == b'ahoj'  # oktalově
    </example>
  </note>

  <p class="enumerate">
    S výjimkou metod <em>encode()</em>, <em>format()</em> a <em>isidentifier()</em> sdílejí bajtové řetězce s řetězci unicodovými také stejné atributy:
  </p>
  <example lang="python">
>>> dir(xb)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', 
'__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', 
'__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', 
'__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
'__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 
'capitalize', 'center', 'count', 'decode', 'endswith', 'expandtabs', 'find', 
'fromhex', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 
'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 
'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 
'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 
'translate', 'upper', 'zfill']
  </example>
  <note>
    Většina z nich nám ale pro práci s bajtovými objekty moc užitečná není. Pokud ovšem nepracujete třebas na úrovni ASCII (síťové protokoly a podobně), kde se pak k bajtovým řetězcům můžete chovat jako ke skutečným a ušetříte si odbočku do UTF-8 se všemi z toho plynoucími komplikacemi.
  </note>

</slide>
<slide title="Zavedení – konstruktorem „bytes()“">

  <p>
    Druhou variantou zavedení bajtového řetězce je pomocí funkce <code>bytes()</code>. Narozdíl od <em>přímé notace</em> její použití už není tak snadné a přímočaré – výsledek totiž závisí na typu vstupního parametru:
  </p>
  
  <p class="enumerate">
    Při volání <strong>bez parametru</strong> zavede funkce <em>bytes()</em> bajtový řetězec o nulové délce:
  </p>
  <example lang="python">
>>> xb = bytes()
>>> xb
b''
  </example>
  
  <p class="enumerate">
    Je-li vstupním parametrem <strong>řetězec</strong>, musíme navíc přidat i kódování, které bude řídit jeho transformaci na bajtový řetězec:
  </p>
  <example lang="python">
>>> xb = bytes('Ahoj!', 'utf-8')
>>> xb
b'Ahoj!'
  </example>
  <note>
    Na pozadí se zavolá metoda řetězce <code>'Ahoj!'.encode('utf-8')</code>, která provede příslušnou konverzi.
  </note>
  
  <p class="enumerate">
    Při vstupním parametru <strong>celé číslo</strong> je vytvořen bajtový řetězec odpovídající délky obsahující samé nuly:
  </p>
  <example lang="python">
>>> xb = bytes(7)
>>> xb
b'\x00\x00\x00\x00\x00\x00\x00'
  </example>
  
  <p class="enumerate">
    Další možností je zadat na vstupu <strong>iterovatelný objekt</strong> (<em>iterable</em>), který vrací čísla v rozmezí <code>&lt;0, 255&gt;</code>. Tato čísla budou následně použita pro inicializaci (bajtového) řetězce.
  </p>
  <note>
    Tohle je mimochodem způsob, jak převést <em>jedno konkrétní číslo</em> na bajt – kód <code>bytes([123])</code> převede číslo <code>123</code> na odpovídající bajtovou hodnotu <code>0x7b</code> (která se umí zobrazit jako <code>b'{'</code>, protože náleží do tisknutelné části spodní poloviny ASCII-tabulky).
  </note>
  
  <p class="enumerate">
    Poslední možností je použít na vstupu <strong>bafr</strong> (<em>buffer</em>), v klasickém případě tedy odkaz na otevřený soubor. V takovém případě se pro inicializaci bajtového řetězce použijí data (jednotlivé bajty) z bafru.
  </p>

</slide>
<slide title="Bajtové řetězce jako sekvence">

  <p>
    Stejně jako unicodové řetězce podporují bajtové řetězce tradiční sekvenční operace:
  </p>
  <example lang="python">
>>> xb = b'ahoj'

# délka sekvence
>>> len(xb)
4

# konkrétní prvek
>>> xb[3]
106
>>> xb[-3]
104

# různé výřezy
>>> xb[1:3]
b'ho'
>>> xb[1::2]
b'hj'
>>> xb[2:]
b'oj'
>>> xb[-3:]
b'hoj'

# dotaz na výskyt prvku
>>> 111 in xb
True
>>> 110 in xb
False
>>> b'a' in xb
True
>>> b'\xf1' in xb
False
>>> xb.index(b'h')
1
>>> xb.index(b'D')
Traceback (most recent call last):
  File "&lt;stdin>", line 1, in &lt;module>
ValueError: substring not found
>>> xb.count(b'a')
1

# dvě spojené kopie
>>> xb * 2
b'ahojahoj'
  </example>

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

    <p>
        Na <a href="?slajd=5">předchozím slajdu</a> jste si mohli všimnout, že přístup k prvkům bajtového řetězce není tak úplně konzistentní s pojmenováním „řetězec“ – pro více prvků obdržíte (bajtový) řetězec, pro jeden prvek ovšem dostanete číslo:
    </p>
    <example lang="python">
        >>> xb = b'ahoj'
        >>> type(xb)
        &lt;class 'bytes'>

        # víceprvkový výřez
        >>> xb[0:2]
        b'ah'
        >>> type(xb[0:2])
        &lt;class 'bytes'>

        # jednoprvkový výřez
        >>> xb[0]
        97
        >>> type(xb[0])
        &lt;class 'int'>
    </example>
    <p>
        U (normálního) řetězce je jeden prvek prostě zase jen řetězec, byť jednoprvkový, takže má stejné vlastnosti. U bajtového řetězce se vlastnosti jednoprvkového výřezu těžce odlišují od vlastností výřezu delšího – prostě je to úplně jiný typ (celé číslo). Tak pozor na to.
    </p>
    <note>
        <em>„Well, it's not a bug, it's a feature.“</em>
    </note>

</slide>
<slide title="Bajtové řetězce jako sekvence – „for-in“">

  <p>
    A samozřejmě máme k dispozici i oblíbenou smyčku <em>for-in</em> ve všech jejích variantách:
  </p>
  <example lang="python">
>>> xb = b'ahoj'

>>> for x in xb:
...     print(x)
...
97
104
111
106

>>> for i, x in enumerate(xb):
...     print(i, x)
...
0 97
1 104
2 111
3 106

>>> for x in reversed(xb):
...   print(x)
...
106
111
104
97

>>> for x in sorted(xb):
...   print(x)
...
97
104
106
111
  </example>

</slide>
<slide title="Bajtové řetězce jako neměnitelné sekvence">

  <p>
    Stejně jako „obyčejné“ unicodové řetězce jsou řetězce bajtové <strong>neměnné</strong> (<em>immutable</em>):
  </p>
  <example lang="python">
>>> xb = b'ahoj'

>>> xb[1]
104

>>> xb[1] = b'H'
Traceback (most recent call last):
  File "&lt;pyshell#42>", line 1, in &lt;module>
    xb[1] = b'H'
TypeError: 'bytes' object does not support item assignment
  </example>

</slide>
<slide title="Interpolace v bajtových řetězcích pomocí „%“">

  <p>
    Jedna z užitečných metod, která bajtovým řetězcům oproti jejich unicodovým bráškům chybí, je <code>format()</code> (a s ní i formátované řetězce – <code>fb''</code> bohužel opravdu neprojde). Světe ale div se, funguje interpolace pomocí starší varianty s operátorem <code>%</code>:
  </p>
  <example lang="python">
    # jeden bajt
    >>> b'%c' % 66
    b'B'
    >>> b'%c' % b'B'
    b'B'

    # delší bajtový řetězec
    >>> b'%b' % b'abc'
    b'abc'
    >>> b'%b' % bytearray([65, 66, 67])
    b'ABC'

    # reprezentace objektu
    >>> b'%a' % 3.14
    b'3.14'
    >>> b'%a' % 'abc'
    b"'abc'"
    >>> b'%a' % b'abc'
    b"b'abc'"
  </example>
  <notes>
    <note>
      <code>%a</code> je vlastně ekvivalentem <code>repr(OBJEKT).encode('ascii', 'backslashreplace')</code>. Příklady převzaty <a href="https://www.python.org/dev/peps/pep-0461/" class="external">z dokumentace</a>.
    </note>
    <note>
      Kromě uvedených formátovacích kódů (a několika dalších kvůli kompatibilitě s řadou 2.x) podporuje procentítková interpolace <a href="/materialy/python/cmd/print.xml?slajd=7">všechny ostatní existující</a>.
    </note>
  </notes>
  <p>
    PS: Popravdě to ale ve trojkové řadě funguje až od verze 3.5, kdy byly konečně vyslyšeny hlasy pythoních nízkoúrovňových programátorů (ne všichni kvůli tomu jedou Céčko ;-) a do Python'u byla vrácena funkcionalita dvojkové řady. Ostatně to byla jedna z velkých kritik a hlavních důvodů, proč v těchto případech zůstat u Python'u 2.x.
  </p>

</slide>
<slide title="Dekódování textových řetězců – „BYTES.decode()“ vs „STR.encode()“">

  <p>
    Na úrovni binárních dat představují textové řetězce jisté sekvence bajtů. Přitom typicky podle <em>zvoleného kódování</em> zabírají různé znaky různý počet bajtů. Python poskytuje metody pro konverzi mezi řetězci a jejich odpovídajícím bajtovým vyjádřením.
  </p>
  
  <p class="enumerate">
    Konverzi řetězce na odpovídající bajtový řetězec zajišťuje <em>metoda řetězce</em> <code>encode(KÓDOVÁNÍ)</code>. Ukažme si pro příklad tři různé způsoby bajtového zakódování téhož řetězce:
  </p>
  <example lang="python">
# Testovací řetězec o délce čtyř (unicodových) znaků:
>>> xs = '狼.cz'
>>> len(xs)
4

# a) v kódování UTF-8 zabírá šest bajtů
>>> xs.encode('utf-8')
b'\xe7\x8b\xbc.cz'
>>> len( xs.encode('utf-8') )
6
# b) v kódování GB18030 zabírá pět bajtů
>>> xs.encode('gb18030')
b'\xc0\xc7.cz'
>>> len( xs.encode('gb18030') )
5
# c) v kódování Big5 zabírá jiných pět bajtů než v předchozím kódování
>>> xs.encode('big5')
b'\xafT.cz'
>>> len( xs.encode('big5') )
5
  </example>
  <note>
    Metoda <em>encode()</em> tedy vezme řetězec a znak po znaku ho při <em>zvoleném kódování</em> převede na odpovídající sekvenci bajtů (často delší).
  </note>
  
  <p class="enumerate">
    Zpětnou konverzi z bajtového řetězce na textový řetězec (při daném kódování) zajišťuje <em>metoda bajtového řetězce</em> <code>decode(KÓDOVÁNÍ)</code>. Příklad:
  </p>
  <example lang="python">
# Testovací řetězec:
>>> xs = '狼.cz'

# Převod „tam a zpět“ je pochopitelně jednoznačný:
# řetězec → bajtový řetězec → řetězec
>>> xs.encode('big5').decode('big5')
'狼.cz'
  </example>
  <note>
    Metoda <em>decode()</em> tedy vezme bajtový řetězec a podle <em>zvoleného kódování</em> ho po odpovídajících skupinách bajtů převede na odpovídající sekvenci znaků (často kratší).
  </note>
  
  <hr/>
  
  <p>
    PS: Častěji ale budou bajtové objekty představovat „skutečná“ binární data, např. obrázek nebo zvuk. Pak jsou samozřejmě výše uvedené konverze k ničemu, protože takováto data málokdy budou mít rozumnou textovou podobu.
  </p>

</slide>


</lecture>
