<?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>Deskriptory</title>
  <date>2011-05-05</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 můžeme po <em>datových</em> atributech chtít, aby se chovaly trochu také jako metody, které něco dělají, a ne jen udržovaly nějakou hodnotu. Typicky:
  </p>
  <ul>
      <li>
          vlastnosti objektů, které má smysl jak číst, tak i měnit;
      </li>
      <li>
          kontrola zapisovaných hodnot do objektů;
      </li>
      <li>
          …
      </li>
  </ul>
  <p>
    Toto chování v mnoha jazycích zajišťují tzv. <em>getters &amp; setters</em>, v Python'u je však známější pod pojmenováním <strong><em>properties</em></strong> a nebo ještě spíše pod jménem protokolu, který slouží (nejen) k jejich implementaci – <strong><em>deskriptory</em></strong> (<em>descriptors</em>).
  </p>
  <handout>
    Představte si, že vyrobíte program pro kreslení plošných objektů do canvasu. Každý grafický objekt je představován nějakým programovým objektem. Jeden z <em>datových</em> atributů tohoto programového objektu (zvaný třeba <code>color</code>) udržuje povědomí o barvě objektu grafického. Uvedený datový atribut je možno číst – dostanete aktuální barvu objektu. Ale též je do něj možné zapisovat – pak by se zřejmě měla zároveň změnit i samotná barva grafického objektu.
  </handout>

</slide>
<slide title="Příklad">

  <p>
    Ukažme si nejdříve poněkud „ukecaný“, leč poměrně přímočarý způsob, jak zavést na objektu atributy, které se budou chovat požadovaným způsobem:
  </p>
  <example lang="python">
# zavedení objektu
class Plocha:

    def __init__(self, barva=None):
        self._barva = barva
    
    def get_barvu(self):
        print("Vracím barvu.")
        return self._barva
    
    def set_barvu(self, val):
        print("Nastavuji barvu na:", val)
        self._barva = val
    
    barva = property(get_barvu, set_barvu)

# zavedení instance objektu
p = Plocha()

# „hraní si“ s atributem barva
b = p.barva
print(b)

print()

p.barva = "zelená"

print()

b = p.barva
print(b)
  </example>
  <note>
    Zde nepoužitá část pro odstranění atributu by vypadala následovně:
    <example lang="python">
        def del_barvu(self):
            print("Odstraňuji barvu.")
            del self._barva
    
        barva = property(get_barvu, set_barvu, del_barvu)
    </example>
  </note>
  
  <p>
    Spustíme-li uvedený kód, obdržíme následující výstup (vysvětlující komentáře přidány):
  </p>
  <example lang="python">
Vracím barvu.   # důsledek zavolání „b = p.barva“
None            # vrácená hodnota (po __init__() je zatím None)

Nastavuji barvu na: zelená   # důsledek zavolání „p.barva = "zelená"“

Vracím barvu.   # důsledek zavolání „b = p.barva“
zelená          # vrácená hodnota
  </example>

</slide>
<slide title="Zavedení pomocí „property()“">

  <p>
    Vestavěná funkce <code>property()</code>, kterou jsme na předchozím slajdu použili pro konstrukci <em>property</em>-atributu <em>barva</em>, má v úplnosti následující záběr:
  </p>
  <pre>
    property(fget=None, fset=None, fdel=None, doc=None)
  </pre>
  <p>
    V plné šíři tedy může vygenerovat atribut, který nabídne obslužnou funkci při čtení (<em>fget</em>), zápisu (<em>fset</em>) a odstranění atributu (<em>fdel</em>; nepříliš častá operace) plus zavedení dokumentačního řetězce (<em>doc</em>).
  </p>
  <note>
    Není-li parametr <em>doc</em> přítomen, pokusí se Python na jeho místo dosadit dokumentační řetězec funkce <em>fget</em>.
  </note>

  <p>
    Výše uvedené použití však není zrovna ideální, pokud obslužné funkce nepotřebujeme na nic jiného (což nejspíše bude nejčastější případ) – pokud je totiž nezavedeme jako dvojpodtržítkové (na začátku jména), tak nám budou zcela zbytečně „zaneřáďovat“ jmenný prostor objektu:
  </p>
  <!--example lang="python">
# Python 3.4
>>> dir(p)
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__',
 '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
 '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
 '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
 '__weakref__', 'get_barvu', 'set_barvu', 'barva']
  </example-->
  <example lang="python">
>>> dir(p)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
 '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
 '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
 '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
 '__str__', '__subclasshook__', '__weakref__',
 '_barva', 'barva', 'del_barva', 'get_barva', 'set_barva']
  </example>
  <note>
      Atribut <em>_barva</em> je v případě <em>properties</em> v některých verzích Python'u schovaný (například 3.4), ale většinou tedy ne.
  </note>

</slide>
<slide title="Zavedení pomocí dekorátorů">

  <p>
    Od Python'u 2.6 se (nejen) uvedený problém zaneřáďování jmenného prostoru řeší pomocí dekorátorů. Bohužel vybraný způsob zápisu je (nejen podle mě) velmi matoucí a nepřehledný:
  </p>
  <example lang="python">
class Plocha:

    def __init__(self, barva=None):
        self._barva = barva

    @property
    def barva(self):
        print("Vracím barvu.")
        return self._barva
    
    @barva.setter
    def barva(self, val):
        print("Nastavuji barvu na:", val)
        self._barva = val
  </example>
  <notes>
    <note>
        Funkce <code>property</code> tedy slouží jako dekorátor pro <em>getter</em>. Zde nepoužitý dekorátor pro odstranění atributu by byl <code>@barva.deleter</code>.
    </note>
    <note>
        Zajímavé je, že po této „operaci“ zůstal u Python'u 3.4 ve jmenném prostoru objektu už pouze atribut <code>barva</code>. Všude jinde uvidíte ovšem i atribut <code>_barva</code>. Chcete-li ho trochu více schovat, musíte použít podtržítka dvě:
        <example lang="python">
            def __init__(self, barva=None):
                self.__barva = barva
        </example>
        Je samozřejmě třeba upravit i zbývající volání.
    </note>
  </notes>

</slide>
<slide title="Funkce „property()“ versus dekorátory">

    <p>
        Dosti nešťastná volba zápisu zavedení těchto „obojetných“ atributů pomocí dekorátorů<!--, navíc dříve provázená neschováním pomocné proměnné (zde <code>_barva</code>),--> způsobila zřejmě její poměrnou neoblibu mezi pythoními programátory.
    </p>
    <p>
        Vzhledem k naprosté asymetričnosti použití dekorátorů se osobně také kloním spíše k tradičnímu používání funkce <em>property()</em> a důsledného dvojpodtržítkování všech pomocných funkcí a proměnných.
    </p>

</slide>
<slide title="Teorie I">

    <p>
        V kapitole o <a href="introspection.xml">introspekci</a> jsme viděli, že atributy objektu „žijí“ v jeho jmenném prostoru díky mapování v atributu <code>__dict__</code>. Dotaz na „běžný“ (ne-<em>property</em>) atribut se tak přeloží pomocí metod daného objektu..
    </p>
    <ul>
        <li>
            <strong><code>__getattribute__()</code></strong> pro čtení hodnoty z atributu;
        </li>
        <li>
            <strong><code>__setattr__()</code></strong> pro zápis hodnoty do atributu;
        </li>
        <li>
            <strong><code>__delattr__()</code></strong> pro odstranění atributu;
        </li>
    </ul>
    <p>
        ..na odpovídající dotaz do slovníku příslušné instance nebo některé její rodičovské třídy:
    </p>
    <example lang="python">
        # a) daný atribut je definován přímo na instanci
        INSTANCE.__dict__['ATRIBUT']

        # b) daný atribut definuje základní třída této instance
        type(INSTANCE).__dict__['ATRIBUT']

        # c) daný atribut je definován na některé předchozí rodičovské třídě
        …
    </example>

</slide>
<slide title="Teorie II">

    <p>
        Pokud se však jedná o <em>property</em>-atribut (tj. v odpovídající třídě je atribut definován jako <em>property/deskriptor</em>), všechno je jinak:
    </p>
    <blockquote>
        <em>Property</em>-atribut je ve slovníku <em>__dict__</em> představován celou speciální třídou s vlastními metodami, tzv. <strong>deskriptorem</strong>.
    </blockquote>
    <p>
        Deskriptorové třídy definují následující metody (ne nutně všechny najednou):
    </p>
    <ul>
        <li>
            <strong><code>__get__(self, INSTANCE, VLASTNÍK)</code></strong> – volána při získávání hodnoty atributu;
        </li>
        <li>
            <strong><code>__set__(self, INSTANCE, HODNOTA)</code></strong> – volána při ukládání hodnoty do atributu;
        </li>
        <li>
            <strong><code>__delete__(self, INSTANCE)</code></strong> – volána při odstraňování atributu.
        </li>
    </ul>

</slide>
<slide title="Teorie III">

    <p>
        Pokus o provedení příslušné operace na <em>property</em>-atributu (opět samozřejmě zprostředkovaný voláním metod objektu <code>__getattribute__()</code>, resp. <code>__setattr__()</code>, resp. <code>__delattr__()</code>) pak způsobí zavolání odpovídající „magické“ metody deskriptoru:
    </p>
    <example lang="python">
        # A) čtení
        INSTANCE.ATRIBUT
           &lt;=>   
        type(INSTANCE).__dict__['ATRIBUT'].__get__(INSTANCE, type(INSTANCE))

        # B) zápis
        INSTANCE.ATRIBUT = value
           &lt;=>   
        type(INSTANCE).__dict__['ATRIBUT'].__set__(INSTANCE, type(INSTANCE), value)

        # C) odstranění
        del INSTANCE.ATRIBUT
           &lt;=>   
        type(INSTANCE).__dict__['ATRIBUT'].__delete__(INSTANCE)
    </example>
    <note>
        Pro příslušné dotazy na <em>třídě</em> to dopadne trošku jinak, např. <code>TŘÍDA.ATRIBUT &lt;=> TŘÍDA.__dict__['ATRIBUT'].__get__(None, TŘÍDA)</code> apod.
    </note>
    <p>
        PS: Existuje i magická metoda <code>__getattr__()</code>. Narozdíl od <em>__getattribute__()</em>, která se volá vždy, je-li k dispozici, dochází k volání <em>__getattr__()</em> pouze tehdy, nepodaří-li se daný atribut nalézt jiným způsobem (a je-li definována).
    </p>

</slide>
<slide title="Teorie IV">

    <p>
        Tak zvané <strong>datové deskriptory</strong> typicky definují metody <code>__get__()</code> i <code>__set__()</code>, <strong><u>ne</u>datové deskriptory</strong> (tedy především funkce aneb metody) na druhou stranu pouze metodu <code>__get__()</code>.
    </p>
    <p>
        V praxi největší rozdíl mezi nimi je pak především ten, že metody (jakožto nedatové deskriptory) můžete <strong>na instancích</strong> „přepsat“ (jednoduše pod daným jménem uložíte něco nového), zatímco datové deskriptory nikoli (protože pokus o přiřazení vyvolá specifické chování deskriptoru). Čili především:
    </p>
    <ul>
        <li>
            metody objektů instanční, třídní i statické (tedy jinými slovy především dekorátorové funkce <em>classmethod()</em> a <em>staticmethod()</em>) jsou implementovány jako nedatové deskriptory, tudíž jsou na instancích předefinovatelné;
        </li>
        <li>
            funkce <em>property()</em> (a tudíž i s ní zavedené atributy) je implementována jako datový deskriptor, tudíž instance nemohou změnit chování daného atributu.
        </li>
    </ul>

<!-- poznámka pro chování Python'u 2.x versus 3.x
  <p>
    U nedatových deskriptorů první parametr metody <em>__get__()</em> zjevně určuje, zda bude daná metoda <em>bound</em> nebo nikoli:
  </p>
  
  <example lang="python">
# Python 2.x
>>> class D(object):
       def f(self, x):
          return x
... 
>>> d = D()
>>> D.__dict__['f'] # Stored internally as a function
&lt;function f at 0x00C45070>
>>> D.f             # Get from a class becomes an unbound method
&lt;unbound method D.f>
>>> d.f             # Get from an instance becomes a bound method
&lt;bound method D.f of &lt;__main__.D object at 0x00B18C90>>
  </example>
  
  <example lang="python">
# Python 3.x
>>> class D(object):
...     def f(self, x):
...         return x
... 
>>> D.__dict__['f']
11: <function f at 0x00F4B1E0>
>>> D.f
12: <function f at 0x00F4B1E0>
>>> d.f
13: <bound method D.f of <__main__.D object at 0x00F3FC90>>
  </example>
-->

</slide>
<slide title="Poznámky">

  <p>
    Deskriptory v Pyhton'u slouží k implementaci celé řady základních věcí:
  </p>
  <ul>
    <li>funkce;</li>
    <li>atributy tříd s kontrolovaným přístupem (<em>properties</em>);</li>
    <li>metody tříd (a to jak instanční, tak i třídní a statické);</li>
    <li>reference na rodičovské třídy (pomocí funkce <em>super()</em>);</li>
    <li>...</li>
  </ul>

</slide>
  

</lecture>
