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