Pohled pod kapotu JVM – volání funkcí a použití generátorů i uzávěrů v Python VM (2)

Pavel Tišnovský 2. 9. 2014

Na předchozí část seriálu o JVM (a dalších VM), v níž jsme si popsali volání funkcí a metod v Python VM, dnes navážeme, protože si vysvětlíme princip implementace generátorů a uzávěrů v Python VM. Díky podpoře uzávěrů a zejména generátorů je možné v Pythonu psát některé algoritmy velmi elegantním způsobem.

Obsah

1. Generátory v Pythonu a v Python VM

2. Demonstrační příklad Test32.py: jednoduchý generátor

3. Překlad demonstračního příkladu Test32.py do bajtkódu Python VM

4. Demonstrační příklad Test33.py: generátor konečné sekvence hodnot

5. Překlad demonstračního příkladu Test33.py do bajtkódu Python VM

6. Uzávěry v Pythonu a v Python VM

7. Demonstrační příklad Test34.py: jednoduchý uzávěr vytvořený v Pythonu

8. Překlad demonstračního příkladu Test34.py do bajtkódu Python VM

9. Demonstrační příklad Test35.py: nefunkční implementace čítače realizovaného uzávěrem

10. Demonstrační příklad Test36.py: funkční implementace čítače realizovaného uzávěrem

11. Překlad demonstračního příkladu Test36.py do bajtkódu Python VM

12. Repositář se zdrojovými kódy všech pěti dnešních demonstračních příkladů

13. Generátory a uzávěry – rozdíly mezi jazyky Lua a Python

14. Odkazy na Internetu

1. Generátory v Pythonu a v Python VM

V předchozí části tohoto seriálu jsme si popsali princip volání funkcí v bajtkódu Python VM. Víme již, jak se volají „běžné“ funkce s pozičními parametry, funkce, jejichž (některé) parametry mají přiřazenou implicitní hodnotu i funkce, při jejichž volání jsou explicitně přiřazeny hodnoty k pojmenovaným parametrům. Ovšem v souvislosti s funkcemi si musíme popsat dvojici konceptů – uzávěrů (closures)generátorů (generators). Zejména koncept generátorů a obecně koprogramů (coroutines) umožňuje tvořit v Pythonu některé algoritmy velmi elegantním způsobem bez nutnosti zavádění lazy sekvencí známých z některých funkcionálních programovacích jazyků. Generátory jsou v programech použity především v programových smyčkách typu for, ve skutečnosti je však možné volat koprogram (kterým je generátor většinou implementován) i z jiné části kódu. Nicméně právě použití v programové smyčce typu for je tak idiomatické, že tuto smyčku použijeme i v některých dnešních demonstračních příkladech (proto se také budeme bavit speciálně o generátorech a ne o obecných koprogramech).

Generátorem se v Pythonu označuje specifický typ funkce určené pro postupné generování nějaké sekvence hodnot, přičemž tato sekvence může být nekonečná. Při každém volání této funkce je však vrácena vždy jen jedna hodnota – to je právě důvod, proč mohou generátory generovat teoreticky nekonečnou sekvenci, aniž by došlo k zaplnění veškeré operační paměti. Aby bylo možné generátor vůbec vytvořit, musel být koncept funkcí rozšířen a zobecněn – funkce již nemá jeden vstupní bod a její provádění neskončí při výskoku z funkce, ale v případě generátorů má funkce určitou formu vnitřní paměti (tím se přibližuje uzávěrům, i když samotná implementace je odlišná).

Aby bylo možné při každém zavolání generátoru vrátit další hodnotu generované posloupnosti a současně i zachovat vnitřní stav funkce, nepoužívá se pro vrácení a ukončení funkce klíčové slovo return ale odlišné klíčové slovo yield. Jakmile program při svém běhu (v průběhu vykonávání těla generátoru) narazí na toto klíčové slovo, přeruší běh generátoru a vrátí hodnotu výrazu uvedeného za tímto klíčovým slovem. Další volání generátoru nezačíná od začátku jeho těla, ale těsně za příkazem yield. Pokud se však při provádění generátoru dojde na konec jeho těla, ukončí se jeho běh, stejně jako je tomu u běžné funkce. Generátor se nevolá přímo, ale přes funkci/metodu next(), což vlastně znamená, že se současně jedná i o iterátor.

2. Demonstrační příklad Test32.py: jednoduchý generátor

Pochopení principu práce generátorů může být možná zpočátku poněkud složité, proto se podívejme na „školní“ příklad, jehož obdobu lze najít v mnoha učebnicích programovacího jazyka Python. V tomto příkladu je vytvořen generátor, který po každém svém zavolání vrátí další člen posloupnosti faktoriálů, tj. postupně se vrací hodnoty 1!=1, 2!=2, 3!=6, 4!=24 atd. Tělo generátoru pojmenovaného factorialGenerator() je tvořeno nekonečnou smyčkou (generuje se tedy alespoň teoreticky nekonečná posloupnost – otestujte sami!), v jejímž těle je použit příkaz yield n, který přeruší běh generátoru a současně i vrátí hodnotu dalšího vypočteného faktoriálu v posloupnosti. „Paměť generátoru“ je zde tvořena dvojicí interních proměnných nazvaných ni. Povšimněte si, že generátor se volá přímo přes metodu next(), což je v praxi poněkud neobvyklé (v dalším příkladu je ukázán jiný postup):

#
# Demonstracni priklad cislo 32.
#
# Generatory v Pythonu.
#
 
 
 
#
# Jednoduchy generator.
#
def factorialGenerator():
    n = 1
    i = 1
    while True:
        yield n
        i = i + 1
        n = n * i
 
 
 
#
# Spusteni testu.
#
def main():
    fact = factorialGenerator()
    for i in range(10):
        print(fact.next())
 
 
 
#
# Ukazka disasembleru.
# (prekladu funkci do bajtkodu Python VM).
#
def disassemble():
    from dis import dis
 
    print("\nfactorialGenerator():")
    dis(factorialGenerator)
 
 
 
main()
 
#disassemble()
 
#
# Finito.
#

Tento demonstrační příklad po svém spuštění vypíše na standardní výstup následující sekvenci hodnot:

1
2
6
24
120
720
5040
40320
362880
3628800

3. Překlad demonstračního příkladu Test32.py do bajtkódu Python VM

Podívejme se na způsob překladu demonstračního příkladu Test32.py popsaného v předchozí kapitole do bajtkódu virtuálního stroje jazyka Python. Překlad funkce factorialGenerator() je zvláštní použitím instrukce YIELD_VALUE, která implementuje funkcionalitu zabezpečenou přímo v Pythonu klíčovým slovem yield. Při volání instrukce YIELD_VALUE se na vrcholu zásobníku operandů (TOS) očekává hodnota, která je předána volajícímu kódu:

factorialGenerator():
 13           0 LOAD_CONST               1 (1)         // umístit na TOS celočíselnou konstantu 1
              3 STORE_FAST               0 (n)         // a uložit ji do lokální proměnné n
 
 14           6 LOAD_CONST               1 (1)         // opět umístit na TOS celočíselnou konstantu 1
              9 STORE_FAST               1 (i)         // a uložit ji do lokální proměnné i
 
 15          12 SETUP_LOOP              37 (to 52)     // příprava na provedení programové smyčky
             15 LOAD_GLOBAL              0 (True)      // příprava pro test na tuto hodnotu (True)
             18 JUMP_IF_FALSE           29 (to 50)     // tento podmíněný skok nikdy nenastane
             21 POP_TOP                                // odstranit prvek (True) z TOS
 
 16          22 LOAD_FAST                0 (n)         // načtení hodnoty lokální proměnné n
             25 YIELD_VALUE                            // přerušení běhu generátoru a vrácení hodnoty n
             26 POP_TOP                                // odstranit prvek (hodnotu n) z TOS
 
 17          27 LOAD_FAST                1 (i)         // |
             30 LOAD_CONST               1 (1)         // | implementace operace i = i + 1
             33 BINARY_ADD                             // |
             34 STORE_FAST               1 (i)         // |
 
 18          37 LOAD_FAST                0 (n)         // |
             40 LOAD_FAST                1 (i)         // | implementace operace n = n * i
             43 BINARY_MULTIPLY                        // |
             44 STORE_FAST               0 (n)         // |
 
             47 JUMP_ABSOLUTE           15             // skok na začátek programové smyčky while
 
             50 POP_TOP                                // odstranit prvek z TOS
             51 POP_BLOCK                              // (úklid po programové smyčce)
                                                       // standardní výskok z funkce
             52 LOAD_CONST               0 (None)      // uložit konstantu None na zásobník operandů
             55 RETURN_VALUE                           // vrátit tuto konstantu (uloženou na TOS)

Samotné použití generátoru je velice snadné, protože explicitně voláme metodu fact.next(), kde fact je jméno lokální proměnné, do níž byla reference na generátor uložena. Veškerá další činnost je již zajištěna programovou smyčkou for a funkcí range() vracející svůj vlastní iterátor (který nemá s naším generátorem nic společného):

main():
 26           0 LOAD_GLOBAL              0             // načtení globální reference na funkci factorialGenerator
              3 CALL_FUNCTION            0             // volání funkce factorialGenerator() bez parametrů
              6 STORE_FAST               0 (fact)      // uložit vrácenou referenci do lokální proměnné
 
 27           9 SETUP_LOOP              31 (to 43)     // příprava na provedení programové smyčky
             12 LOAD_GLOBAL              1 (range)     // načtení globální reference na funkci range()
             15 LOAD_CONST               1 (10)        // konstanta předávaná do funkce range()
             18 CALL_FUNCTION            1             // zavolání funkce range(10)
             21 GET_ITER                               // získání iterátoru vráceného funkcí range()
 
             22 FOR_ITER                17 (to 42)     // začátek programové smyčky (vstup do další iterace)
             25 STORE_FAST               1 (i)         // tato lokální proměnná je počitadlem smyčky
 
 28          28 LOAD_FAST                0 (fact)      // příprava na volání metody fact.next()
             31 LOAD_ATTR                2 (next)      // příprava na volání metody fact.next()
             34 CALL_FUNCTION            0             // zavolání metody fact.next(), tj. provedení výpočtu
             37 PRINT_ITEM                             // výpis hodnoty vrácené generátorem
             38 PRINT_NEWLINE                          // odřádkování na standardním výstupu
 
             39 JUMP_ABSOLUTE           22             // skok na začátek programové smyčky for
             42 POP_BLOCK                              // (úklid po smyčce)
                                                       // standardní výskok z funkce
             43 LOAD_CONST               0 (None)      // uložit konstantu None na zásobník operandů
             46 RETURN_VALUE                           // vrátit tuto konstantu (uloženou na TOS)

4. Demonstrační příklad Test33.py: generátor konečné sekvence hodnot

Ve druhém demonstračním příkladu je původní generátor factorialGenerator upraven takovým způsobem, že generuje hodnoty faktoriálu pouze do chvíle, kdy je překročen nastavený limit předaný při vytváření generátoru v parametru maxn. Jakmile je limit překročen, běh generátoru je ukončen a současně se vyhodí výjimka typu StopIteration. To nás však nemusí příliš trápit v případě, že je generátor použit v programové smyčce typu for, neboť v ní se výskyt této výjimky předpokládá – ukončuje její běh. Naproti tomu při explicitním volání funkce next() by se výjimka měla zachytit (což však v našem demonstračním příkladu nemusíme dělat a také to neděláme):

#
# Demonstracni priklad cislo 33.
#
# Generatory v Pythonu.
#
 
 
 
#
# Jednoduchy generator s limitem.
#
def factorialGenerator(maxn):
    n = 1
    i = 1
    while n < maxn:
        yield n
        i = i + 1
        n = n * i
 
 
 
#
# Spusteni testu.
#
def main():
    for fact in factorialGenerator(1e10):
        print(fact)
 
 
 
#
# Ukazka disasembleru.
# (prekladu funkci do bajtkodu Python VM).
#
def disassemble():
    from dis import dis
 
    print("\nfactorialGenerator():")
    dis(factorialGenerator)
 
 
 
main()
 
#disassemble()
 
#
# Finito.
#

Výstup tohoto demonstračního příkladu vypadá následovně:

1
2
6
24
120
720
5040
40320
362880
3628800
39916800
479001600
6227020800

5. Překlad demonstračního příkladu Test33.py do bajtkódu Python VM

Funkce factorialGenerator() se opět přeloží s využitím instrukce YIELD_VALUE. Od předchozí verze stejnojmenné funkce se nová varianta liší především ve způsobu testu ukončení vnitřní programové smyčky, která již není nekonečná. Povšimněte si, že nikde není vidět generování výjimky StopIteration, o níž jsme se zmínili výše. To je (díky OO návrhu) zajištěno interně překladačem:

factorialGenerator():
 13           0 LOAD_CONST               1 (1)         // umístit na TOS celočíselnou konstantu 1
              3 STORE_FAST               1 (n)         // a uložit ji do lokální proměnné n
 
 14           6 LOAD_CONST               1 (1)         // opět umístit na TOS celočíselnou konstantu 1
              9 STORE_FAST               2 (i)         // a uložit ji do lokální proměnné i
 
 15          12 SETUP_LOOP              43 (to 58)     // příprava na provedení programové smyčky
             15 LOAD_FAST                1 (n)         // hodnota lokální proměnné n
             18 LOAD_FAST                0 (maxn)      // se bude porovnávat s hodnotou parametru maxn
             21 COMPARE_OP               0 (<)
             24 JUMP_IF_FALSE           29 (to 56)     // podmíněný skok ZA konec smyčky
             27 POP_TOP                                // odstranit prvek (výsledek porovnání) z TOS
 
 16          28 LOAD_FAST                1 (n)         // načtení hodnoty lokální proměnné n
             31 YIELD_VALUE                            // přerušení běhu generátoru a vrácení hodnoty n
             32 POP_TOP                                // odstranit prvek (hodnotu n) z TOS
 
 17          33 LOAD_FAST                2 (i)         // |
             36 LOAD_CONST               1 (1)         // | implementace operace i = i + 1
             39 BINARY_ADD                             // |
             40 STORE_FAST               2 (i)         // |
 
 18          43 LOAD_FAST                1 (n)         // |
             46 LOAD_FAST                2 (i)         // | implementace operace n = n * i
             49 BINARY_MULTIPLY                        // |
             50 STORE_FAST               1 (n)         // |
 
             53 JUMP_ABSOLUTE           15             // skok na začátek programové smyčky while
 
             56 POP_TOP                                // odstranit prvek z TOS
             57 POP_BLOCK                              // (úklid po programové smyčce)
                                                       // standardní výskok z funkce
             58 LOAD_CONST               0 (None)      // uložit konstantu None na zásobník operandů
             61 RETURN_VALUE                           // vrátit tuto konstantu (uloženou na TOS)

Použití nové verze generátoru v programové smyčce typu for je nyní velice snadné, protože tuto funkci využijeme pro získání iterátoru, pro nějž smyčka for automaticky bude volat metodu next() až do té chvíle, než dojde k výjimce:

main():
 26           0 SETUP_LOOP              25 (to 28)
              3 LOAD_GLOBAL              0             // načtení globální reference na funkci factorialGenerator
              6 LOAD_CONST               1             // jediný parametr funkce factorialGenerator – hodnota (10000000000.0
              9 CALL_FUNCTION            1             // volání funkce factorialGenerator() s jedním parametrem
             12 GET_ITER                               // získání iterátoru vráceného funkcí factorialGenerator()
 
             13 FOR_ITER                11 (to 27)     // začátek programové smyčky (vstup do další iterace)
             16 STORE_FAST               0 (fact)      // tato proměnná je postupně naplňována hodnotami vytvářenými generátorem
 
 27          19 LOAD_FAST                0 (fact)      // načíst hodnotu vrácenou generátorem
             22 PRINT_ITEM                             // výpis hodnoty vrácené generátorem
             23 PRINT_NEWLINE                          // odřádkování na standardním výstupu
 
             24 JUMP_ABSOLUTE           13             // skok na začátek programové smyčky for
             27 POP_BLOCK                              // (úklid po smyčce)
                                                       // standardní výskok z funkce
             28 LOAD_CONST               0 (None)      // uložit konstantu None na zásobník operandů
             31 RETURN_VALUE                           // vrátit tuto konstantu (uloženou na TOS)

6. Uzávěry v Pythonu a v Python VM

V programovacím jazyku Python je kromě „obyčejných“ funkcí a generátorů možné vytváře i uzávěry (closures). Samotné použití uzávěrů je sice v Pythonu 2.x poněkud problematické (což ostatně uvidíme v navazujících kapitolách), ovšem v Pythonu 3.x je již tento nedostatek související se sémantikou rozpoznání lokálních a nelokálních proměnných odstraněn a tak mají uzávěry v Pythonu prakticky stejnou vyjadřovací sílu, jako například v programovacích jazycích Lua či JavaScript; nehledě již na většinu funkcionálních jazyků, které samozřejmě práci s uzávěry ve většině případů taktéž podporují. Uzávěry jsou navíc tak důležitou součástí Pythonu, že pro jejich implementaci jsou v bajtkódu Python VM rezervovány dvě instrukce nazvané LOAD_CLOSUREMAKE_CLOSURE, s nimiž se setkáme v navazujících kapitolách, konkrétně ve výpisech bajtkódů demonstračních příkladů.

7. Demonstrační příklad Test34.py: jednoduchý uzávěr vytvořený v Pythonu

První demonstrační příklad s uzávěrem je velmi prostý, protože je zde funkce (která tvoří základ uzávěru) navázána na hodnotu parametru předaného do funkce, v níž se uzávěr vytváří. Pro lepší čitelnost je funkce tvořící základ uzávěru pojmenována, ve skutečnosti by však bylo možné v jednodušších případech použít i anonymní funkce vytvořené s využitím klíčového slova lambda (zde však programovací jazyk Python omezuje těla takových funkcí na jediný výraz, což může být někdy příliš striktní, ostatně právě proto si ukazujeme použití vnitřní neanonymní funkce):

#
# Demonstracni priklad cislo 34.
#
# Jednoduchy uzaver v Pythonu.
#
 
 
 
#
# Jednoduchy uzaver v Pythonu.
#
def dummyAdder(delta):
    def add(n):
        return delta + n
    return add
 
 
 
#
# Spusteni testu.
#
def main():
    adder1 = dummyAdder(0)
    adder2 = dummyAdder(42)
    for i in range(1,11):
        result1 = adder1(i)
        result2 = adder2(i)
        print("Iteration #%d" % i)
        print("    Adder1: %d" % result1)
        print("    Adder2: %d" % result2)
 
 
 
#
# Ukazka disasembleru.
# (prekladu funkci do bajtkodu Python VM).
#
def disassemble():
    from dis import dis
 
    print("\ndummyAdder():")
    dis(dummyAdder)
 
    print("\nmain():")
    dis(main)
 
 
 
main()
 
disassemble()
 
#
# Finito.
#

Po spuštění tohoto demonstračního příkladu je patrné, že se skutečně každý uzávěr navázal na jinou hodnotu parametru předaného do funkce vytvářející uzávěr:

Iteration #1
    Adder1: 1
    Adder2: 43
Iteration #2
    Adder1: 2
    Adder2: 44
Iteration #3
    Adder1: 3
    Adder2: 45
Iteration #4
    Adder1: 4
    Adder2: 46
Iteration #5
    Adder1: 5
    Adder2: 47
Iteration #6
    Adder1: 6
    Adder2: 48
Iteration #7
    Adder1: 7
    Adder2: 49
Iteration #8
    Adder1: 8
    Adder2: 50
Iteration #9
    Adder1: 9
    Adder2: 51
Iteration #10
    Adder1: 10
    Adder2: 52

8. Překlad demonstračního příkladu Test34.py do bajtkódu Python VM

Opět nás bude zajímat, jakým způsobem se demonstrační příklad Test34.py přeložil do bajtkódu virtuálního stroje Pythonu. Nejdříve se podívejme na překlad funkce dummyAdder(), v jejímž bajtkódu můžeme vidět nové instrukce nazvané LOAD_CLOSUREMAKE_CLOSURE, které se postarají – již na takto nízké úrovni – o vytvoření uzávěru, podobně jako tomu bylo s instrukcí CLOSURE v bajtkódu Lua VM:

dummyAdder():
 13           0 LOAD_CLOSURE             0 (delta)     // příprava na navázání proměnné k uzávěru
              3 BUILD_TUPLE              1
                                                       // nyní se načte bajktód již přeložené vnitřní funkce
              6 LOAD_CONST               1 (code object add at 0xb77125c0, file "Test34.py", line 13)
              9 MAKE_CLOSURE             0             // vytvořit uzávěr a uložit referenci na něj na TOS
             12 STORE_FAST               1 (add)       // referenci taktéž do proměnné add
 
 15          15 LOAD_FAST                1 (add)       // na TOS uložit referenci na uzávěr z proměnné add
             18 RETURN_VALUE                           // vrátit tuto referenci

Samotné volání vytvořeného uzávěru již není vůbec složité, uzávěry se totiž v tomto ohledu chovají jako běžné funkce:

main():
 23           0 LOAD_GLOBAL              0 (dummyAdder)// příprava na volání funkce dummyAdder
              3 LOAD_CONST               1 (0)         // na TOS uložit konstantu 0
              6 CALL_FUNCTION            1             // zavolat funkci dummyAdder() s jedním parametrem
              9 STORE_FAST               0 (adder1)    // uložit vrácený uzávěr do lokální proměnné
 
 24          12 LOAD_GLOBAL              0 (dummyAdder)// příprava na volání funkce dummyAdder
             15 LOAD_CONST               2 (42)        // na TOS uložit konstantu 42
             18 CALL_FUNCTION            1             // zavolat funkci dummyAdder() s jedním parametrem
             21 STORE_FAST               1 (adder2)    // uložit vrácený uzávěr do lokální proměnné
 
 25          24 SETUP_LOOP              74 (to 101)    // příprava na provedení programové smyčky
             27 LOAD_GLOBAL              1 (range)     // načtení globální reference na funkci range()
             30 LOAD_CONST               3 (1)         // první konstanta předávaná do funkce range()
             33 LOAD_CONST               4 (11)        // druhá konstanta předávaná do funkce range()
             36 CALL_FUNCTION            2             // zavolání funkce range(1, 11)
             39 GET_ITER                               // získání iterátoru vráceného funkcí range()
 
             40 FOR_ITER                57 (to 100)    // začátek programové smyčky (vstup do další iterace)
             43 STORE_FAST               2 (i)         // tato lokální proměnná je počitadlem smyčky
 
 26          46 LOAD_FAST                0 (adder1)    // příprava na volání prvního uzávěru
             49 LOAD_FAST                2 (i)         // lokální proměnná i (počitadlo) bude parametrem uzávěru
             52 CALL_FUNCTION            1             // zavolat uzávěr s jedním parametrem
             55 STORE_FAST               3 (result1)   // uložit výsledek volání do lokální proměnné
 
 27          58 LOAD_FAST                1 (adder2)    // příprava na volání druhého uzávěru
             61 LOAD_FAST                2 (i)         // lokální proměnná i (počitadlo) bude parametrem uzávěru
             64 CALL_FUNCTION            1             // zavolat uzávěr s jedním parametrem
             67 STORE_FAST               4 (result2)   // uložit výsledek volání do lokální proměnné
 
 28          70 LOAD_CONST               5 ('Iteration #%d')
             73 LOAD_FAST                2 (i)
             76 BINARY_MODULO                          // operátor modulo je zde použit pro formátování výstupu
             77 PRINT_ITEM                             // výpis čísla iterace
             78 PRINT_NEWLINE                          // odřádkování
 
 29          79 LOAD_CONST               6 ('    Adder1: %d')
             82 LOAD_FAST                3 (result1)
             85 BINARY_MODULO                          // operátor modulo je zde použit pro formátování výstupu
             86 PRINT_ITEM                             // výpis hodnoty vrácené prvním uzávěrem
             87 PRINT_NEWLINE                          // odřádkování
 
 30          88 LOAD_CONST               7 ('    Adder2: %d')
             91 LOAD_FAST                4 (result2)
             94 BINARY_MODULO                          // operátor modulo je zde použit pro formátování výstupu
             95 PRINT_ITEM                             // výpis hodnoty vrácené druhým uzávěrem
             96 PRINT_NEWLINE                          // odřádkování
 
             97 JUMP_ABSOLUTE           40             // skok na začátek programové smyčky (další iterace)
 
            100 POP_BLOCK                              // úklid zásobníku po provedení smyčky
            101 LOAD_CONST               0 (None)      // uložit konstantu None na zásobník operandů
            104 RETURN_VALUE                           // vrátit tuto konstantu (uloženou na TOS)

9. Demonstrační příklad Test35.py: nefunkční implementace čítače realizovaného uzávěrem

Připomeňme si, že při popisu implementace uzávěrů v programovacím jazyce Lua jsme se mj. zabývali i následujícím demonstračním příkladem, v němž se ve funkci createCounter() vytvořil uzávěr navázaný na lokální proměnnou této funkce nazvanou counter. Uzávěr mohl jak číst hodnotu této proměnné, tak ji dokonce mohl bez problémů modifikovat:

--
-- Demonstracni priklad cislo 30.
--
-- Vytvareni a nasledne pouziti uzaveru (closure).
--
 
 
 
-- Vytvoreni a vraceni uzaveru, tj. funkce na niz je navazana
-- externi lokalni promenna - upvalue.
function createCounter()
    -- lokalni promenna, jejiz "zivotnost" presahuje
    -- pouhe zavolani a provedeni bloku funkce createCounter()
    local counter = 0
    -- navratovou hodnotou funkce createCounter() je anonymni
    -- funkce pracujici s promennou cnt, ktera je na tuto
    -- anonymni funkci navazana
    return function()
        -- counter se oznacuje jako "externi lokalni promenna"
        -- popr. v terminologii jazyka Lua "upvalue"
        counter = counter + 1
        return counter
    end
end
 
 
 
--
-- Spusteni testu.
--
function main()
    -- ziskame "instanci" anonymni funkce i na ni navazanou
    -- externi lokalni promennou "counter"
    -- -> closure
    local mycounter = createCounter()
    print(mycounter())
    print(mycounter())
    print(mycounter())
end
 
 
 
main()
 
 
 
--
-- Finito.
--

Přímý přepis výše uvedeného demonstračního příkladu do Pythonu by mohl vypadat následovně:

#
# Demonstracni priklad cislo 35.
#
# Nefunkcni implementace citace realizovaneho uzaverem.
#
 
 
 
#
# Jednoduchy uzaver v Pythonu.
#
def createCounter():
    counter = 0
    def next():
        counter += 1
        return counter
    return next
 
 
 
#
# Spusteni testu.
#
def main():
    counter1 = createCounter()
    counter2 = createCounter()
    for i in range(1,11):
        result1 = counter1()
        result2 = counter2()
        print("Iteration #%d" % i)
        print("    Counter1: %d" % result1)
        print("    Counter2: %d" % result2)
 
 
 
#
# Ukazka disasembleru.
# (prekladu funkci do bajtkodu Python VM).
#
def disassemble():
    from dis import dis
 
    print("\ncreateCounter():")
    dis(createCounter)
 
    print("\nmain():")
    dis(main)
 
 
 
main()
 
disassemble()
 
#
# Finito.
#

Ve skutečnosti však takto naprogramovaný uzávěr nebude funkční, protože k vázané proměnné counter sice může uzávěr přistupovat při čtení, ale nikoli už při zápisu (modifikaci). Proč dojde k chybě lze zjednodušeně řečeno vysvětlit tak, že interpret Pythonu musí mít informaci o tom, že proměnná counter není interní proměnnou uzávěru, ale vázanou (tedy nelokální) proměnnou. Z tohoto důvodu při spuštění tohoto příkladu dojde k běhové výjimce. Proto je zapotřebí dávat pozor na to, že ne všechny uzávěry implementované v programovacích jazycích jakými jsou Lua či JavaScript lze bez problémů přímo přepsat do Pythonu:

Traceback (most recent call last):
  File "Test35.py", line 51, in <module<
    main()
  File "Test35.py", line 28, in main
    result1 = counter1()
  File "Test35.py", line 15, in next
    counter += 1
UnboundLocalError: local variable 'counter' referenced before assignment

10. Demonstrační příklad Test36.py: funkční implementace čítače realizovaného uzávěrem

Aby bylo možné vytvářet plnohodnotné uzávěry i v Pythonu, bylo do verze 3.x přidáno nové klíčové slovo nonlocal. Tímto klíčovým slovem je možné ve vnitřní funkci – tedy ve vlastním uzávěru – označit proměnnou, která nemá být chápána jako proměnná lokální. Ovšem ve skutečnosti můžeme uzávěr implementující čítač vytvořit i v Pythonu 2, a to pomocí malého triku: namísto skalární (celočíselné) proměnné se použije jednorozměrný seznam. Zde již interpret nebude mít problém s rozeznáním lokální proměnné od proměnné vázané, neboť význam řádků counter += 1 a counter[0] += 1 je sémanticky odlišný (interpret si je ve druhém případě jistý, že se nejedná o deklaraci nové lokální proměnné):

#
# Demonstracni priklad cislo 36.
#
# Funkcni implementace citace realizovaneho uzaverem.
#
 
 
 
#
# Jednoduchy uzaver v Pythonu.
#
def createCounter():
    counter = [0]
    def next():
        counter[0] += 1
        return counter[0]
    return next
 
 
 
#
# Spusteni testu.
#
def main():
    counter1 = createCounter()
    counter2 = createCounter()
    for i in range(1,11):
        result1 = counter1()
        result2 = counter2()
        print("Iteration #%d" % i)
        print("    Counter1: %d" % result1)
        print("    Counter2: %d" % result2)
 
 
 
#
# Ukazka disasembleru.
# (prekladu funkci do bajtkodu Python VM).
#
def disassemble():
    from dis import dis
 
    print("\ncreateCounter():")
    dis(createCounter)
 
    print("\nmain():")
    dis(main)
 
 
 
main()
 
disassemble()
 
#
# Finito.
#

Z následujícího výpisu je patrné, že tento demonstrační příklad skutečně funguje, a to i v Pythonu 2:

Iteration #1
    Counter1: 1
    Counter2: 1
Iteration #2
    Counter1: 2
    Counter2: 2
Iteration #3
    Counter1: 3
    Counter2: 3
Iteration #4
    Counter1: 4
    Counter2: 4
Iteration #5
    Counter1: 5
    Counter2: 5
Iteration #6
    Counter1: 6
    Counter2: 6
Iteration #7
    Counter1: 7
    Counter2: 7
Iteration #8
    Counter1: 8
    Counter2: 8
Iteration #9
    Counter1: 9
    Counter2: 9
Iteration #10
    Counter1: 10
    Counter2: 10

11. Překlad demonstračního příkladu Test36.py do bajtkódu Python VM

Funkce createCounter(), která vytvoří nyní již funkční čítač tvořený uzávěrem s jednou vázanou proměnnou, se přeloží do bajtkódu virtuálního stroje programovacího jazyka Python následujícím způsobem:

createCounter():
 13           0 LOAD_CONST               1 (0)         // první (a jediný) prvek seznamu má hodnotu 0
              3 BUILD_LIST               1             // vytvořit seznam o velikosti jednoho prvku
              6 STORE_DEREF              0 (counter)   // uložit seznam
 
 14           9 LOAD_CLOSURE             0 (counter)   // příprava na navázání proměnné k uzávěru
             12 BUILD_TUPLE              1
                                                       // nyní se načte bajktód již přeložené vnitřní funkce
             15 LOAD_CONST               2 (code object next at 0xb77175c0, file "Test36.py", line 14)
             18 MAKE_CLOSURE             0             // vytvořit uzávěr a uložit referenci na něj na TOS
             21 STORE_FAST               0 (next)      // na TOS uložit referenci na uzávěr z proměnné add
 
 17          24 LOAD_FAST                0 (next)      // na TOS uložit referenci na uzávěr z proměnné next
             27 RETURN_VALUE                           // vrátit tuto referenci

Samotné volání vytvořeného uzávěru se podobá volání, s nímž jsme se setkali již v předchozích kapitolách:

main():
 25           0 LOAD_GLOBAL              0 (createCounter)// příprava na volání funkce createCounter
              3 CALL_FUNCTION            0             // zavolat funkci createCounter() bez parametrů
              6 STORE_FAST               0 (counter1)  // uložit vrácený uzávěr do lokální proměnné
 
 26           9 LOAD_GLOBAL              0 (createCounter)// příprava na volání funkce createCounter
             12 CALL_FUNCTION            0             // zavolat funkci createCounter() bez parametrů
             15 STORE_FAST               1 (counter2)  // uložit vrácený uzávěr do lokální proměnné
 
 27          18 SETUP_LOOP              68 (to 89)     // příprava na provedení programové smyčky
             21 LOAD_GLOBAL              1 (range)     // načtení globální reference na funkci range()
             24 LOAD_CONST               1 (1)         // první konstanta předávaná do funkce range()
             27 LOAD_CONST               2 (11)        // druhá konstanta předávaná do funkce range()
             30 CALL_FUNCTION            2             // zavolání funkce range(1, 11)
             33 GET_ITER                               // získání iterátoru vráceného funkcí range()
 
             34 FOR_ITER                51 (to 88)     // začátek programové smyčky (vstup do další iterace)
             37 STORE_FAST               2 (i)         // tato lokální proměnná je počitadlem smyčky
 
 28          40 LOAD_FAST                0 (counter1)  // příprava na volání prvního uzávěru
             43 CALL_FUNCTION            0             // zavolat uzávěr bez předání parametrů
             46 STORE_FAST               3 (result1)   // uložit výsledek volání do lokální proměnné
 
 29          49 LOAD_FAST                1 (counter2)  // příprava na volání prvního uzávěru
             52 CALL_FUNCTION            0             // zavolat uzávěr bez předání parametrů
             55 STORE_FAST               4 (result2)   // uložit výsledek volání do lokální proměnné
 
 30          58 LOAD_CONST               3 ('Iteration #%d')
             61 LOAD_FAST                2 (i)
             64 BINARY_MODULO                          // operátor modulo je zde použit pro formátování výstupu
             65 PRINT_ITEM                             // výpis čísla iterace
             66 PRINT_NEWLINE                          // odřádkování
 
 31          67 LOAD_CONST               4 ('    Counter1: %d')
             70 LOAD_FAST                3 (result1)
             73 BINARY_MODULO                          // operátor modulo je zde použit pro formátování výstupu
             74 PRINT_ITEM                             // výpis hodnoty vrácené prvním uzávěrem
             75 PRINT_NEWLINE                          // odřádkování
 
 32          76 LOAD_CONST               5 ('    Counter2: %d')
             79 LOAD_FAST                4 (result2)
             82 BINARY_MODULO                          // operátor modulo je zde použit pro formátování výstupu
             83 PRINT_ITEM                             // výpis hodnoty vrácené druhým uzávěrem
             84 PRINT_NEWLINE                          // odřádkování
 
             85 JUMP_ABSOLUTE           34             // skok na začátek programové smyčky (další iterace)
 
             88 POP_BLOCK                              // úklid zásobníku po provedení smyčky
             89 LOAD_CONST               0 (None)      // uložit konstantu None na zásobník operandů
             92 RETURN_VALUE                           // vrátit tuto konstantu (uloženou na TOS)

12. Repositář se zdrojovými kódy všech pěti dnešních demonstračních příkladů

Všech pět dnes popsaných a „disasemblovaných“ demonstračních příkladů bylo uloženo do Mercurial repositáře umístěného na adrese http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/. Odkazy na prozatím poslední verze těchto pěti příkladů naleznete v tabulce umístěné pod tímto odstavcem:

13. Generátory a uzávěry – rozdíly mezi jazyky Lua a Python

V programovacím jazyce Lua není podpora pro práci s koprogramy implementována formou speciální syntaktické konstrukce jazyka tak, jako je tomu v některých dalších programovacích jazycích implementujících koprogramy (neexistuje zde například klíčové slovo pro přerušení práce koprogramu, zatímco třeba Python pro podobnou činnost rezervuje slovo yield, viz předchozí kapitoly), ale – jak se již v Lua stalo dobrým zvykem – je vše řešeno pomocí funkcí a asociativních polí. Samotný koprogram je ve své podstatě pojmenovaná či anonymní funkce s vlastním zásobníkem, který je oddělený od zásobníku volajícího programu. Pro vytváření, řízení a zjišťování stavů koprogramů lze využít šest funkcí ležících v prostoru jmen nazvaném coroutine (prostor jmen není nic jiného než takto pojmenované globální asociativní pole se šesti „veřejnými“ funkcemi a několika pomocnými atributy), jejichž význam je uveden v následující tabulce.:

widgety

Název funkce Význam
coroutine.create() vytvoření koprogramu
coroutine.resume() spuštění či znovuspuštění koprogramu
coroutine.running() funkce vrátí právě běžící koprogram (pro hlavní vlákno se vrací nil)
coroutine.status() funkce vrátí aktuální stav koprogramu – zda běží, je pozastaven či zda je běh koprogramu již ukončen
coroutine.wrap() vytvoření koprogramu, vrací se funkce, která koprogram spustí
coroutine.yield() pozastavení koprogramu a případný přenos parametrů volajícímu programu

Na závěr se ještě pokusme popsat společné vlastnosti a rozdíly mezi programovacími jazyky Lua a Python v oblasti generátorů a uzávěrů:

# Vlastnost Lua Python
1 podpora uzávěrů v jazyku ano ano
2 podpora anonymních funkcí ano s omezeními
3 funkce je plnohodnotný datový typ ano ano
4 podpora uzávěrů v bajtkódu ano ano
5 vázané „lokální externí proměnné“ ano Python 2: omezení, Python 3: ano
       
6 podpora koprogramů/generátorů v jazyku ano (přes funkce) ano
7 pozastavení koprogramu s návratovou hodnotou coroutine.yield(value) yield value
8 podpora koprogramů/generátorů v bajtkódu ne ano

(ještě „politicky složitější“ situace nastane při porovnání s programovacím jazykem Java a především s bajtkódem JVM).

14. Odkazy na Internetu

  1. Python Byte Code Instructions
    https://docs.python.org/re­lease/2.5.2/lib/bytecodes­.html
  2. Python 2.x: funkce range()
    https://docs.python.org/2/li­brary/functions.html#range
  3. Python 2.x: typ iterátor
    https://docs.python.org/2/li­brary/stdtypes.html#itera­tor-types
  4. Python break, continue and pass Statements
    http://www.tutorialspoint­.com/python/python_loop_con­trol.htm
  5. Python Bytecode: Fun With Dis
    http://akaptur.github.io/blog/2013/08/14/pyt­hon-bytecode-fun-with-dis/
  6. Python's Innards: Hello, ceval.c!
    http://tech.blog.aknin.na­me/category/my-projects/pythons-innards/
  7. Byterun
    https://github.com/nedbat/byterun
  8. Python Byte Code Instructions
    http://document.ihg.uni-duisburg.de/Documentation/Pyt­hon/lib/node56.html
  9. Python Byte Code Instructions
    https://docs.python.org/3­.2/library/dis.html#python-bytecode-instructions
  10. dis – Python module
    https://docs.python.org/2/li­brary/dis.html
  11. Comparison of Python virtual machines
    http://polishlinux.org/ap­ps/cli/comparison-of-python-virtual-machines/
  12. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  13. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  14. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators
  15. Programming in Lua 9.1 – Coroutine Basics,
    http://www.lua.org/pil/9.1.html
  16. Wikipedia CZ: Koprogram,
    http://cs.wikipedia.org/wi­ki/Koprogram
  17. Wikipedia EN: Coroutine,
    http://en.wikipedia.org/wi­ki/Coroutine
  18. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  19. Programming in Lua: 6 – More about Functions
    http://www.lua.org/pil/6.html
  20. Lua Lanes,
    http://kotisivu.dnainternet­.net/askok/bin/lanes/
  21. Programming in Lua: 6.1 – Closures
    http://www.lua.org/pil/6.1.html
  22. Programming in Lua: 9.1 – Coroutine Basics
    http://www.lua.org/pil/9.1.html
  23. Programming in Lua: Numeric for
    http://www.lua.org/pil/4.3.4.html
  24. Programming in Lua: break and return
    http://www.lua.org/pil/4.4.html
  25. Programming in Lua: Tables
    http://www.lua.org/pil/2.5.html
  26. Programming in Lua: Table Constructors
    http://www.lua.org/pil/3.6.html
  27. Programovací jazyk Lua
    http://palmknihy.cz/web/kni­ha/programovaci-jazyk-lua-12651.htm
  28. Lua: Tables Tutorial
    http://lua-users.org/wiki/TablesTutorial
  29. Lua: Control Structure Tutorial
    http://lua-users.org/wiki/ControlStruc­tureTutorial
  30. Lua Types Tutorial
    http://lua-users.org/wiki/LuaTypesTutorial
  31. Goto Statement in Lua
    http://lua-users.org/wiki/GotoStatement
  32. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  33. Lua 5.2 sources – lopcodes.h
    http://www.lua.org/source/5­.2/lopcodes.h.html
  34. Lua 5.2 sources – lopcodes.c
    http://www.lua.org/source/5­.2/lopcodes.c.html
  35. For-each Loop in Java
    http://www.leepoint.net/notes-java/flow/loops/foreach.html
  36. For Loop (Wikipedia)
    http://en.wikipedia.org/wiki/For_loop
  37. Heinz Rutishauser
    http://en.wikipedia.org/wi­ki/Heinz_Rutishauser
  38. Parrot
    http://www.parrot.org/
  39. Parrot languages
    http://www.parrot.org/languages
  40. Parrot Primer
    http://docs.parrot.org/pa­rrot/latest/html/docs/intro­.pod.html
  41. Parrot Opcodes
    http://docs.parrot.org/pa­rrot/latest/html/ops.html
  42. Parrot VM
    http://en.wikibooks.org/wi­ki/Parrot_Virtual_Machine
  43. Parrot Assembly Language
    http://www.perl6.org/archi­ve/pdd/pdd06_pasm.html
  44. Parrot Reference: Chapter 11 – Perl 6 and Parrot Essentials
    http://oreilly.com/perl/excerpts/perl-6-and-parrot-essentials/parrot-reference.html
  45. O-code
    http://en.wikipedia.org/wiki/O-code_machine
  46. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  47. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  48. GC safe-point (or safepoint) and safe-region
    http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html
  49. Safepoints in HotSpot JVM
    http://blog.ragozin.info/2012/10/sa­fepoints-in-hotspot-jvm.html
  50. Java theory and practice: Synchronization optimizations in Mustang
    http://www.ibm.com/develo­perworks/java/library/j-jtp10185/
  51. How to build hsdis
    http://hg.openjdk.java.net/jdk7/hot­spot/hotspot/file/tip/src/sha­re/tools/hsdis/README
  52. Java SE 6 Performance White Paper
    http://www.oracle.com/technet­work/java/6-performance-137236.html
  53. Lukas Stadler's Blog
    http://classparser.blogspot­.cz/2010/03/hsdis-i386dll.html
  54. How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
    http://dropzone.nfshost.com/hsdis.htm
  55. PrintAssembly
    https://wikis.oracle.com/dis­play/HotSpotInternals/Prin­tAssembly
  56. The Java Virtual Machine Specification: 3.14. Synchronization
    http://docs.oracle.com/ja­vase/specs/jvms/se7/html/jvms-3.html#jvms-3.14
  57. The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4
  58. The Java Virtual Machine Specification: 17.4. Memory Model
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.4
  59. The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.7
  60. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  61. ASM Home page
    http://asm.ow2.org/
  62. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  63. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  64. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  65. BCEL Home page
    http://commons.apache.org/bcel/
  66. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  67. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  68. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  69. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  70. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  71. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  72. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  73. Javassist
    http://www.jboss.org/javassist/
  74. Byteman
    http://www.jboss.org/byteman
  75. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  76. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  77. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  78. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  79. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  80. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  81. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  82. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  83. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  84. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  85. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  86. Cobertura
    http://cobertura.sourceforge.net/
  87. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html
Našli jste v článku chybu?
Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

Podnikatel.cz: Udělali jsme velkou chybu, napsal Čupr

Udělali jsme velkou chybu, napsal Čupr

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

DigiZone.cz: Test LG 55UH750V aneb Cena/výkon

Test LG 55UH750V aneb Cena/výkon

DigiZone.cz: Mordparta: trochu podchlazený 87. revír

Mordparta: trochu podchlazený 87. revír

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

Vitalia.cz: 5 pravidel proti infekci močových cest

5 pravidel proti infekci močových cest

DigiZone.cz: Ginx TV: pořad o počítačových hráčích

Ginx TV: pořad o počítačových hráčích

DigiZone.cz: Rapl: seriál, který vás smíří s ČT

Rapl: seriál, který vás smíří s ČT

Lupa.cz: Hackeři mají data z půlmiliardy účtů Yahoo

Hackeři mají data z půlmiliardy účtů Yahoo

DigiZone.cz: Numan Two: rozhlasový přijímač s CD

Numan Two: rozhlasový přijímač s CD

Vitalia.cz: Když všichni seli řepku, on vsadil na dýně

Když všichni seli řepku, on vsadil na dýně

Podnikatel.cz: Instalatér, malíř a elektrikář. "Vymřou"?

Instalatér, malíř a elektrikář. "Vymřou"?

Vitalia.cz: dTest odhalil ten nejlepší kečup

dTest odhalil ten nejlepší kečup

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští

Vitalia.cz: Kterou dýni můžete jíst za syrova?

Kterou dýni můžete jíst za syrova?

DigiZone.cz: Wimbledon na Nova Sport až do 2019

Wimbledon na Nova Sport až do 2019

Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

Vitalia.cz: Tahák, jak vyzrát nad zápachem z úst

Tahák, jak vyzrát nad zápachem z úst