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
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) a 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 n a i. 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_CLOSURE a MAKE_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_CLOSURE a MAKE_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.org/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.:
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
- Python Byte Code Instructions
https://docs.python.org/release/2.5.2/lib/bytecodes.html - Python 2.x: funkce range()
https://docs.python.org/2/library/functions.html#range - Python 2.x: typ iterátor
https://docs.python.org/2/library/stdtypes.html#iterator-types - Python break, continue and pass Statements
http://www.tutorialspoint.com/python/python_loop_control.htm - Python Bytecode: Fun With Dis
http://akaptur.github.io/blog/2013/08/14/python-bytecode-fun-with-dis/ - Python's Innards: Hello, ceval.c!
http://tech.blog.aknin.name/category/my-projects/pythons-innards/ - Byterun
https://github.com/nedbat/byterun - Python Byte Code Instructions
http://document.ihg.uni-duisburg.de/Documentation/Python/lib/node56.html - Python Byte Code Instructions
https://docs.python.org/3.2/library/dis.html#python-bytecode-instructions - dis – Python module
https://docs.python.org/2/library/dis.html - Comparison of Python virtual machines
http://polishlinux.org/apps/cli/comparison-of-python-virtual-machines/ - Lambda the Ultimate: Coroutines in Lua,
http://lambda-the-ultimate.org/node/438 - Coroutines Tutorial,
http://lua-users.org/wiki/CoroutinesTutorial - Lua Coroutines Versus Python Generators,
http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators - Programming in Lua 9.1 – Coroutine Basics,
http://www.lua.org/pil/9.1.html - Wikipedia CZ: Koprogram,
http://cs.wikipedia.org/wiki/Koprogram - Wikipedia EN: Coroutine,
http://en.wikipedia.org/wiki/Coroutine - Programming in Lua (first edition)
http://www.lua.org/pil/contents.html - Programming in Lua: 6 – More about Functions
http://www.lua.org/pil/6.html - Lua Lanes,
http://kotisivu.dnainternet.net/askok/bin/lanes/ - Programming in Lua: 6.1 – Closures
http://www.lua.org/pil/6.1.html - Programming in Lua: 9.1 – Coroutine Basics
http://www.lua.org/pil/9.1.html - Programming in Lua: Numeric for
http://www.lua.org/pil/4.3.4.html - Programming in Lua: break and return
http://www.lua.org/pil/4.4.html - Programming in Lua: Tables
http://www.lua.org/pil/2.5.html - Programming in Lua: Table Constructors
http://www.lua.org/pil/3.6.html - Programovací jazyk Lua
http://palmknihy.cz/web/kniha/programovaci-jazyk-lua-12651.htm - Lua: Tables Tutorial
http://lua-users.org/wiki/TablesTutorial - Lua: Control Structure Tutorial
http://lua-users.org/wiki/ControlStructureTutorial - Lua Types Tutorial
http://lua-users.org/wiki/LuaTypesTutorial - Goto Statement in Lua
http://lua-users.org/wiki/GotoStatement - Lua 5.2 sources
http://www.lua.org/source/5.2/ - Lua 5.2 sources – lopcodes.h
http://www.lua.org/source/5.2/lopcodes.h.html - Lua 5.2 sources – lopcodes.c
http://www.lua.org/source/5.2/lopcodes.c.html - For-each Loop in Java
http://www.leepoint.net/notes-java/flow/loops/foreach.html - For Loop (Wikipedia)
http://en.wikipedia.org/wiki/For_loop - Heinz Rutishauser
http://en.wikipedia.org/wiki/Heinz_Rutishauser - Parrot
http://www.parrot.org/ - Parrot languages
http://www.parrot.org/languages - Parrot Primer
http://docs.parrot.org/parrot/latest/html/docs/intro.pod.html - Parrot Opcodes
http://docs.parrot.org/parrot/latest/html/ops.html - Parrot VM
http://en.wikibooks.org/wiki/Parrot_Virtual_Machine - Parrot Assembly Language
http://www.perl6.org/archive/pdd/pdd06_pasm.html - Parrot Reference: Chapter 11 – Perl 6 and Parrot Essentials
http://oreilly.com/perl/excerpts/perl-6-and-parrot-essentials/parrot-reference.html - O-code
http://en.wikipedia.org/wiki/O-code_machine - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - GC safe-point (or safepoint) and safe-region
http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html - Safepoints in HotSpot JVM
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html - Java theory and practice: Synchronization optimizations in Mustang
http://www.ibm.com/developerworks/java/library/j-jtp10185/ - How to build hsdis
http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README - Java SE 6 Performance White Paper
http://www.oracle.com/technetwork/java/6-performance-137236.html - Lukas Stadler's Blog
http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html - How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
http://dropzone.nfshost.com/hsdis.htm - PrintAssembly
https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly - The Java Virtual Machine Specification: 3.14. Synchronization
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14 - The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 - The Java Virtual Machine Specification: 17.4. Memory Model
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 - The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html