Obsah
1. Automatický refaktoring zdrojových kódů Pythonu s využitím nástroje Pyrefact
2. Instalace nástroje Pyrefact
3. Odstranění nepoužívaných globálních a lokálních proměnných
4. Refaktoring globálních proměnných, k nimž se přistupuje z funkcí
5. Přenos opakujících se výpočtů před blok s podmínkou
6. Optimalizace kódu provádějící postupné připojování prvků do seznamu
7. Náhrada programových smyček se seznamy, množinami a slovníky za generátorovou notaci
8. Náhrada programové smyčky za konstantu či výpočet se složitostí O(n)
9. Zjednodušení či náhrada vnořených smyček za konstantu nebo jednodušší výraz
10. Omezení optimalizace smyček
13. Oprava neidiomatických porovnání hodnot
14. Optimalizace operací s n-rozměrnými poli (NumPy)
15. Detekce chybějícího správce kontextu
16. Nepatrně složitější skripty, které se mají optimalizovat
17. Některá omezení a tipy na praktické použití
19. Repositář s demonstračními příklady
1. Automatický refaktoring zdrojových kódů Pythonu s využitím nástroje Pyrefact
V několika předchozích článcích [1] [2] [3] [4] jsme se zabývali problematikou lexikální analýzy zdrojových kódů psaných v Pythonu a popsali jsme si i základní knihovny pro práci s abstraktním syntaktickým stromem (AST – Abstract Syntax Tree). Na tyto články jsme navázali dvojicí článků [5] [6] o knihovně LibCST, která převádí zdrojové kódy Pythonu na CST (Concrete Syntax Tree) a umožňuje i zpětný převod z CST do zdrojových kódů Pythonu.
Dnes si ukážeme praktické nasazení těchto nástrojů, zejména pak využití standardní knihovny AST. Popíšeme si totiž dnes (ještě?) prakticky neznámý nástroj nazvaný příznačně Pyrefact. Tento nástroj dokáže analyzovat zdrojové kódy Pythonu (na úrovni AST) a různými způsoby je upravovat, vylepšovat, odstraňovat mrtvý kód atd. Kvůli tomu, že se nepoužívá CST, ale pouze „obyčejný“ abstraktní syntaktický strom, jsou zdrojové kódy následně zformátovány nástrojem Black (ten je již mnohem známější). I tak však dochází ke ztrátám informace (komentáře) atd., což ještě uvidíme.
2. Instalace nástroje Pyrefact
Instalace nástroje Pyrefact je (většinou) snadná, protože je nabízen jako standardní balíček pro pip, pdm apod. My si v této kapitole ukážeme výpis zpráv z celé instalace, protože je z něho patrné, na jakých dalších knihovnách Pyrefact závisí. Jedná se o Black (formátování kódu), Sympy (symbolické operace) a taktéž o knihovnu pro zpětné vygenerování zdrojového kódu na základě AST:
$ pip install --user pyrefact Collecting pyrefact Downloading pyrefact-100-py3-none-any.whl (90 kB) |████████████████████████████████| 90 kB 380 kB/s Collecting rmspace>=7 Downloading rmspace-7-py3-none-any.whl (4.6 kB) Collecting black>=23.1.0 Downloading black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.7 MB) |████████████████████████████████| 1.7 MB 9.6 kB/s Collecting astunparse==1.6.3; python_version < "3.9" Downloading astunparse-1.6.3-py2.py3-none-any.whl (12 kB) Collecting sympy>=1.11.0 Downloading sympy-1.12-py3-none-any.whl (5.7 MB) |████████████████████████████████| 5.7 MB 4.9 MB/s Collecting compactify>=2 Downloading compactify-2.tar.gz (5.9 kB) Installing build dependencies ... done Getting requirements to build wheel ... done Installing backend dependencies ... done Preparing wheel metadata ... done Requirement already satisfied: packaging>=22.0 in ./.local/lib/python3.8/site-packages (from black>=23.1.0->pyrefact) (23.2) Requirement already satisfied: mypy-extensions>=0.4.3 in /usr/lib/python3/dist-packages (from black>=23.1.0->pyrefact) (0.4.3) Requirement already satisfied: typing-extensions>=4.0.1; python_version < "3.11" in ./.local/lib/python3.8/site-packages (from black>=23.1.0->pyrefact) (4.8.0) Requirement already satisfied: platformdirs>=2 in ./.local/lib/python3.8/site-packages (from black>=23.1.0->pyrefact) (3.11.0) Collecting click>=8.0.0 Downloading click-8.1.7-py3-none-any.whl (97 kB) |████████████████████████████████| 97 kB 922 kB/s Requirement already satisfied: tomli>=1.1.0; python_version < "3.11" in ./.local/lib/python3.8/site-packages (from black>=23.1.0->pyrefact) (2.0.1) Collecting pathspec>=0.9.0 Downloading pathspec-0.12.1-py3-none-any.whl (31 kB) Requirement already satisfied: six<2.0,>=1.6.1 in /usr/lib/python3/dist-packages (from astunparse==1.6.3; python_version < "3.9"->pyrefact) (1.14.0) Requirement already satisfied: wheel<1.0,>=0.23.0 in /usr/lib/python3/dist-packages (from astunparse==1.6.3; python_version < "3.9"->pyrefact) (0.34.2) Collecting mpmath>=0.19 Downloading mpmath-1.3.0-py3-none-any.whl (536 kB) |████████████████████████████████| 536 kB 2.0 MB/s Building wheels for collected packages: compactify Building wheel for compactify (PEP 517) ... done Created wheel for compactify: filename=compactify-2-py3-none-any.whl size=6850 sha256=dcab51ae42efd742463616c4d68f9c4b1ef035484048417debb9e5abab2cf42c Stored in directory: /home/tester/.cache/pip/wheels/58/46/2a/4574dab165b65a01166f0511fa0f3228b21bd83b56227c84c2 Successfully built compactify Installing collected packages: rmspace, click, pathspec, black, astunparse, mpmath, sympy, compactify, pyrefact Successfully installed astunparse-1.6.3 black-24.3.0 click-8.1.7 compactify-2 mpmath-1.3.0 pathspec-0.12.1 pyrefact-100 rmspace-7 sympy-1.12
3. Odstranění nepoužívaných globálních a lokálních proměnných
První ukázkou možností nabízených nástrojem Pyrefact bude odstranění nepoužívaných globálních proměnných. Například v tomto programovém kódu jsou zapsány dvě globální proměnné, které se nikde nečtou:
x = input() y = 42
Výsledkem refaktoringu bude pouze volání funkce input. Tato funkce zůstala zachována, protože má vedlejší efekt, s níž je nutné počítat:
input()
Podobně dokáže Pyrefact odstranit i nepoužívané lokální proměnné:
def add(x, y): added = x + y subtracted = x - y return added print(add(1, 2))
Výsledkem refaktoringu bude zdrojový kód s následujícím obsahem:
def _add(x, y): return x + y print(_add(1, 2))
4. Refaktoring globálních proměnných, k nimž se přistupuje z funkcí
Pyrefact se taktéž snaží o optimalizaci funkcí, ve kterých se pracuje s globálními proměnnými. Ukažme si (zcela umělý) příklad s funkcí, jež čte hodnoty dvou globálních proměnných a mění hodnotu třetí takové proměnné:
x = 1 y = 2 z = 3 def add_globals(): global x, y, z z = x + y add_globals(1, 2) print(z)
V tomto konkrétním případě se zcela odstraní přístup ke globálním proměnným, což není korektní:
Z = 3 def _add_globals(): global x, y, z _add_globals(1, 2) print(Z)
Ještě lépe je to patrné na příkladu, který se po refaktoringu chová odlišně – tedy chyba Pyrefactu:
x = 1 y = 2 z = 0 def add_globals(): global x, y, z z = x + y add_globals() print(z)
Tento skript vypíše hodnotu 3.
Po refaktoringu dostaneme:
Z = 0 def _add_globals(): global x, y, z _add_globals() print(Z)
Což je evidentně nekorektní výsledek!
5. Přenos opakujících se výpočtů před blok s podmínkou
Velmi užitečnou optimalizací je přenos těch výpočtů, které se opakují ve všech větvích podmínky, před blok s podmínkou. Vypadá to následovně:
x = input() if x == "yes": y = 1 print("ok") else: y = 1 print("skip") print(y)
Pyrefact v tomto případě přenesl příkaz (statement) y=1 před vlastní blok s podmínkou a navíc přejmenoval globální proměnné tak, aby byly zapsány s velkými písmeny:
X = input() Y = 1 if X == "yes": print("ok") else: print("skip") print(Y)
Podívejme se, co se stane ve chvíli, kdy z proměnné y nebudeme provádět čtení (tedy odstraníme poslední příkaz print):
x = input() if x == "yes": y = 1 print("ok") else: y = 1 print("skip")
Pyrefact v tomto případě proměnnou y odstranil a (což je asi chyba) ponechal konstantu 1 ve zdrojovém kódu. Tato konstanta se vyhodnotí sama na sebe a výsledek se zahodí, takže to ve skutečnosti až tak nevadí, i když zrovna Pyrefact by měl jít v tomto případě příkladem:
X = input() if X == "yes": print("ok") else: 1 print("skip")
6. Optimalizace kódu provádějící postupné připojování prvků do seznamu
Poměrně často se ve zdrojových kódech (typicky psaných vývojáři, kteří s Pythonem teprve začínají) setkáme s následujícími programovými řádky, které postupně do seznamu připojují další a další prvky:
x = [] for i in range(10): x.append(i) print(x)
Pyrefact takovou smyčku dokáže detekovat a nahradí ji za konstruktor seznamu, tedy za jediný příkaz:
X = list(range(10)) print(X)
7. Náhrada programových smyček se seznamy, množinami a slovníky za generátorovou notaci
Pokračujme v podobném tématu, s jakým jsme se setkali v předchozí kapitole. Nyní si uvedeme skript, v němž se opět postupně připojují prvky do seznamu, ovšem jejich hodnoty jsou již získány komplikovanějším způsobem:
x = [] for i in range(10): x.append(i*2) print(x)
Smyčku z tohoto skriptu je možné nahradit za generátorovou notaci (seznamu), což nástroj Pyrefact skutečně dokáže provést:
X = [i * 2 for i in range(10)] print(X)
Podobným způsobem můžeme zapsat programovou smyčku, která vkládá prvky do množiny:
x = set() for i in range(10): x.add(i*2) print(x)
Nástroj Pyrefact opět dokáže tuto skutečnost zjistit a nahradit smyčku za generátorovou notaci množiny:
X = {i * 2 for i in range(10)} print(X)
A nakonec si ukažme programovou smyčku při přidávání prvků (klíč+hodnota) do slovníku. Tato smyčka může vypadat následovně:
x = {} for i in range(10): x[i] = i * 2 print(x)
Způsob náhrady smyčky za generátorovou notaci slovníku realizovaný nástrojem Pyrefact:
X = {i: i * 2 for i in range(10)} print(X)
8. Náhrada programové smyčky za konstantu či výpočet se složitostí O(n)
V předchozích dvou kapitolách jsme viděli, že nástroj Pyrefact v některých případech dokáže nahradit smyčku za generátorovou notaci (seznamu, množiny či slovníku). To však ale není zdaleka vše, protože tento nástroj detekuje i takové programové smyčky, které lze nahradit buď za konstantu nebo za výraz volaný pouze jedenkrát (tedy bez spuštění smyčky).
Opět si to ukažme na jednoduchém příkladu. V následujícím skriptu je použita počítaná programová smyčka, která postupně zvyšuje obsah globální proměnné s tak, že výsledkem bude součet 0+1+2+…+1000:
s = 0 for i in range(1001): s += i print(s)
Jenže tento kód obsahuje pouze konstanty (0, 1001), takže lze výsledek vypočítat dopředu. Což v tomto případě Pyrefact dokáže:
S = 500500 print(S)
Pokusme se tedy Pyrefact zmást a nejprve všechny hodnoty uložit do seznamu. Posléze zavoláme funkci sum, která tyto prvky sečte:
l = [] for i in range(1001): l.append(i) s = sum(l) print(s)
Pyrefact se v tomto případě zmást nenechal a opět výsledek upravil do co nejjednodušší a nejrychlejší podoby:
S = 500500 print(S)
9. Zjednodušení či náhrada vnořených smyček za konstantu nebo jednodušší výraz
V některých případech dokáže Pyrefact optimalizovat i dvojici vnořených smyček. Příkladem můžeme být následující skript, který postupně (ve dvojici vnořených smyček) přidává prvky do seznamu. Tyto prvky jsou následně sečteny:
l = [] for i in range(1001): for j in range(1001): l.append(i) s = sum(l) print(s)
Po optimalizaci získáme tento nepochybně mnohem jednodušší i rychlejší skript:
S = 501000500 print(S)
10. Omezení optimalizace smyček
Zkusme si nyní nechat automaticky upravit nepatrně složitější skript, v němž se opět nejdříve v programové smyčce naplní seznam vypočtenými hodnotami a následně se vypočítá součet všech prvků v tomto seznamu:
l = [] c = 100 for i in range(1001): l.append(i + c) s = sum(l) print(s)
Pyrefact v tomto případě nedokáže (kupodivu) programovou smyčku nahradit za výpočet provedený v konstantním čase:
L = [] C = 100 for i in range(1001): L.append(i + C) S = sum(L) print(S)
Pokud ovšem program nepatrně upravíme – prohodíme první dva řádky – bude situace odlišná. Nejdříve si ukažme vstup:
c = 100 l = [] for i in range(1001): l.append(i + c) s = sum(l) print(s)
Výsledek refaktoringu již bude obsahovat pouze výraz s výpočtem namísto programové smyčky:
C = 100 S = 1001 * C + 500500 print(S)
11. Částečná optimalizace
V některých případech dokáže nástroj Pyrefact provést pouze částečnou optimalizaci (a ani jeho opakované spuštění nad již transformovaným kódem nepomůže). Poměrně dobrým příkladem této vlastnosti může být dvojice vnořených programových smyček, ve kterých se postupně přidávají prvky do seznamu l. A po ukončení těchto smyček se všechny prvky sečtou:
l = [] for i in range(1001): m = [] for j in range(1001): m.append(j) l.extend(m) s = sum(l) print(s)
Teoreticky by tedy opět mohlo být možné nahradit celý výpočet konstantou, tato plná optimalizace se však neprovede. Namísto toho se vnitřní programová smyčka nahradí za přímé volání range:
L = [] for _ in range(1001): m = list(range(1001)) L.extend(m) S = sum(L) print(S)
12. Odstranění mrtvého kódu
Pyrefact se poměrně důsledně snaží o nalezení a odstranění mrtvého kódu ze zdrojových textů. Nejedná se pouze o kód, který není vůbec volán, ale i o kód, který je sice spuštěn, ale nemá další význam pro běh aplikace. Ukažme si to na skriptu, v němž je takových řádků velmi mnoho:
import math import sys def foo(x): import os y = False z = False w = [] if x > 0: y = True if 0: z = True if x >= 1: return 10 return 0 print(foo(0)) print(foo(1)) print(foo(2))
Výsledkem odstranění mrtvého kódu je funkce, která skutečně obsahuje pouze důležité příkazy, které mění její návratovou hodnotu:
def _foo(x): if x >= 1: return 10 return 0 print(_foo(0)) print(_foo(1)) print(_foo(2))
13. Oprava neidiomatických porovnání hodnot
Idiomatický zápis porovnání hodnoty nějakého výrazu (třeba i jediné proměnné) s konstantami None, True či False, se provádí s využitím operátoru is a nikoli pomocí operátoru ==. I tuto relativně snadnou náhradu dokáže Pyrefact provádět:
x = input() if x == None: print("x is None") if x == True: print("x is True") if x == False: print("x is False")
Výše uvedený kód je refaktorován do této podoby:
X = input() if X is None: print("x is None") if X is True: print("x is True") if X is False: print("x is False")
14. Optimalizace operací s n-rozměrnými poli (NumPy)
Poněkud specifická, ale v některých případech o to užitečnější vlastnost Pyrefactu spočívá v tom, že dokáže zjistit některé „sekvenční“ operace s n-rozměrnými poli nástroje NumPy a nahradit tyto operace zápisem, který je rychlejší, protože využije vektorové instrukce, které NumPy nabízí pro většinu podporovaných architektur.
Opět si to ukažme na poměrně umělém příkladu (tentokrát částečně převzatém z dokumentace k Pyrefactu):
import numpy as np a = np.random.random((10, 10)) s = 0 for i in a: s += i print(s)
Sekvenční výpočet sumy je pomalý a nevyužívá možností moderních mikroprocesorů. Proto ho Pyrefact nahradí za rychlejší variantu, která vypadá následovně:
import numpy as np A = np.random.random((10, 10)) S = sum(A) print(S)
15. Detekce chybějícího správce kontextu
V dalším skriptu se provádí zdánlivě triviální operace: otevření souboru, přečtení jeho obsahu a následné uzavření:
f = open("01_unused_variables_input.py") print(f.read()) f.close()
Ve skutečnosti je ovšem v praxi mnohem výhodnější namísto explicitního uzavírání souboru (a případných testů na fakt, zda byl soubor otevřen, zda došlo k výjimce atd.) použít správce kontextu neboli context manager. I tuto náhradu dokáže Pyrefact v některých případech realizovat (i když ne vždy):
with open("01_unused_variables_input.py") as f: print(f.read())
16. Nepatrně složitější skripty, které se mají optimalizovat
Na závěr si ukažme nepatrně složitější skripty, které budou optimalizovány (či rozbity?) nástrojem Pyredact:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 """Simple preprocessor for Markdown files that handles @ character as include statement.""" input_file = "modern_python_input.md" output_file = "modern_python.md" source_prefix = ( "https://github.com/tisnik/most-popular-python-libs/blob/master/modern_python/sources/" ) source_directory = "sources/" with open(input_file, "r") as fin: with open(output_file, "w") as fout: for line in fin.readlines(): # handle @ character at the beginning of line as include statement if line[0:2] == "@ ": # retrieve file name of file to be included include = line[2:].strip() full_name = source_directory + include print("including:", full_name) # perform the inclusion within ```python block fout.write("```python\n") with open(full_name, "r") as inc: included = inc.read() fout.write(included) fout.write("```\n\n") fout.write("[Zdrojový kód příkladu]({}/{})".format(source_prefix, include)) fout.write("\n") # other lines are to be output in its original form else: fout.write(line)
Výsledek automatického refaktoringu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 """Simple preprocessor for Markdown files that handles @ character as include statement.""" INPUT_FILE = "modern_python_input.md" OUTPUT_FILE = "modern_python.md" SOURCE_DIRECTORY = "sources/" with open(INPUT_FILE, "r") as fin: with open(OUTPUT_FILE, "w") as fout: for line in fin.readlines(): # handle @ character at the beginning of line as include statement if line[0:2] == "@ ": # retrieve file name of file to be included include = line[2:].strip() full_name = SOURCE_DIRECTORY + include print("including:", full_name) # perform the inclusion within ```python block fout.write("```python\n") with open(full_name, "r") as inc: included = inc.read() fout.write(included) fout.write("```\n\n") fout.write("[Zdrojový kód příkladu]({}/{})".format(sourSOURCE_PREFIXclude)) fout.write("\n") # other lines are to be output in its original form else: fout.write(line)
V dalším případě však Pyrefact zcela odstranil smyčku pro zpracování událostí, což je evidentně nekorektní. Nejprve si opět uvedeme původní zdrojový kód:
import sys import pygame import pygame.locals TITLE = "Raster image" SCREEN_WIDTH = 600 SCREEN_HEIGHT = 300 IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 def initialize_ui(title, width, height): """Initialize Pygame display, drawing surface, and clocks.""" # set window title pygame.display.set_caption(title) # initialize window display = pygame.display.set_mode([width, height]) display.fill((0, 0, 0)) clock = pygame.time.Clock() return display, clock def event_loop(display, image1, image2, clock): while True: for event in pygame.event.get(): if event.type == pygame.locals.QUIT: pygame.quit() sys.exit() if event.type == pygame.locals.KEYDOWN: if event.key == pygame.locals.K_ESCAPE: pygame.quit() sys.exit() # all events has been processed - update scene and redraw the screen display.blit(image1, (30, 20)) display.blit(image2, (60 + image1.get_width(), 20)) # and update the whole display pygame.display.update() clock.tick(25) def render_test_rgb_image(image, green): width, height = image.get_size() for y in range(height): for x in range(width): color = (x<<16) + (green<<8) + y image.set_at((x, y), color) def main(): display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT) image1 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT]) image2 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT]) render_test_rgb_image(image1, 0) render_test_rgb_image(image2, 255) event_loop(display, image1, image2, clock) if __name__ == "__main__": main() # finito
V tomto případě ovšem není výsledek refaktoringu vůbec potěšující:
import pygame TITLE = "Raster image" SCREEN_WIDTH = 600 SCREEN_HEIGHT = 300 IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 def _initialize_ui(title, width, height): """Initialize Pygame display, drawing surface, and clocks.""" pygame.display.set_caption(title) display = pygame.display.set_mode([width, height]) display.fill((0, 0, 0)) clock = pygame.time.Clock() return (display, clock) def _render_test_rgb_image(image, green): width, height = image.get_size() for y in range(height): for x in range(width): color = (x << 16) + (green << 8) + y image.set_at((x, y), color) def _main(): display, clock = _initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT) image1 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT]) image2 = pygame.Surface([IMAGE_WIDTH, IMAGE_HEIGHT]) _render_test_rgb_image(image1, 0) _render_test_rgb_image(image2, 255) if __name__ == "__main__": _main() # finito
17. Některá omezení a tipy na praktické použití
Některé úpravy kódu, které Pyrefact provádí, nejsou (většinou) příliš akceptovatelné. Navíc Pyrefact volá Black pro naformátování výsledků (což je v pořádku), ale s nastavením odlišné délky řádků. Proto se mi osvědčil tento postup:
- Uložit současný stav projektu do systému pro správu verzí
- Naformátovat si kódy pomocí Black s vaším nastavením
- Spustit Pyrefact
- Znovu naformátovat kódy pomocí Black s vaším nastavením
- Jednotlivé změny buď schválit nebo zahodit (použijte klasický diff nebo jiný podobně koncipovaný nástroj či přímo IDE).
- Ideálně ještě před závěrečným uložením použít Ruff pro závěrečnou kontrolu (a samozřejmě spustit testy :-)
Ještě je možné si ručně některé operace v Pyrefactu zakázat, například přejmenování lokálních symbolů atd.
A poslední důležitou volbou je -s; v tomto případě Pyrefact nebude mazat ty funkce, o kterých si myslí, že nejsou volány.
18. Závěrečné zhodnocení
Projekt Pyrefact je dnes poměrně neznámý a nachází se prozatím spíše ve vývojové fázi. Ovšem již dnes se dá používat; nesmíme ovšem jeho výsledkům ve všech případech slepě věřit. Spíše se jedná o formu nápovědy, která může pomoci programátorům. A současně o alternativu k „predikcím“ kódu, na nichž jsou založeny projekty typu kopilot postavené na velkých jazykových modelech (LLM).
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 a nástroj pyrefact byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs:
20. Odkazy na Internetu
- pyrefact na PyPi
https://pypi.org/project/pyrefact/ - Repositář projektu pyrefact
https://github.com/OlleLindgren/pyrefact - pyrefact jako plugin do VSCode
https://marketplace.visualstudio.com/items?itemName=olleln.pyrefact - pyrefact-vscode-extension (repositář)
https://github.com/OlleLindgren/pyrefact-vscode-extension - Best Python Refactoring Tools for 2023
https://www.developer.com/languages/python/best-python-refactoring-tools/ - Python Refactoring: Techniques, Tools, and Best Practices
https://www.codesee.io/learning-center/python-refactoring - Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python
https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-programovaciho-jazyka-python/ - Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python (2.část)
https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-programovaciho-jazyka-python-2-cast/ - Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python (3.část)
https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-programovaciho-jazyka-python-3-cast/ - Lexikální a syntaktická analýza zdrojových kódů jazyka Python (4.část)
https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-jazyka-python-4-cast/ - Knihovna LibCST umožňující snadnou modifikaci zdrojových kódů Pythonu
https://www.root.cz/clanky/knihovna-libcst-umoznujici-snadnou-modifikaci-zdrojovych-kodu-pythonu/ - LibCST – dokumentace
https://libcst.readthedocs.io/en/latest/index.html - libCST na PyPi
https://pypi.org/project/libcst/ - libCST na GitHubu
https://github.com/Instagram/LibCST - Inside The Python Virtual Machine
https://leanpub.com/insidethepythonvirtualmachine - module-py_compile
https://docs.python.org/3.8/library/py_compile.html - Given a python .pyc file, is there a tool that let me view the bytecode?
https://stackoverflow.com/questions/11141387/given-a-python-pyc-file-is-there-a-tool-that-let-me-view-the-bytecode - The structure of .pyc files
https://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html - 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/ - O-code
http://en.wikipedia.org/wiki/O-code_machine - Abstract syntax tree
https://en.wikipedia.org/wiki/Abstract_syntax_tree - Lexical analysis
https://en.wikipedia.org/wiki/Lexical_analysis - Parser
https://en.wikipedia.org/wiki/Parsing#Parser - Parse tree
https://en.wikipedia.org/wiki/Parse_tree - Derivační strom
https://cs.wikipedia.org/wiki/Deriva%C4%8Dn%C3%AD_strom - Python doc: ast — Abstract Syntax Trees
https://docs.python.org/3/library/ast.html - Python doc: tokenize — Tokenizer for Python source
https://docs.python.org/3/library/tokenize.html - SymbolTable
https://docs.python.org/3.8/library/symtable.html - 5 Amazing Python AST Module Examples
https://www.pythonpool.com/python-ast/ - Intro to Python ast Module
https://medium.com/@wshanshan/intro-to-python-ast-module-bbd22cd505f7 - Golang AST Package
https://golangdocs.com/golang-ast-package - AP8, IN8 Regulární jazyky
http://statnice.dqd.cz/home:inf:ap8 - AP9, IN9 Konečné automaty
http://statnice.dqd.cz/home:inf:ap9 - AP10, IN10 Bezkontextové jazyky
http://statnice.dqd.cz/home:inf:ap10 - AP11, IN11 Zásobníkové automaty, Syntaktická analýza
http://statnice.dqd.cz/home:inf:ap11 - Introduction to YACC
https://www.geeksforgeeks.org/introduction-to-yacc/ - Introduction of Lexical Analysis
https://www.geeksforgeeks.org/introduction-of-lexical-analysis/?ref=lbp - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Pygments – Python syntax highlighter
http://pygments.org/ - Pygments (dokumentace)
http://pygments.org/docs/ - Write your own filter
http://pygments.org/docs/filterdevelopment/ - Write your own lexer
http://pygments.org/docs/lexerdevelopment/ - Write your own formatter
http://pygments.org/docs/formatterdevelopment/ - Jazyky podporované knihovnou Pygments
http://pygments.org/languages/ - Pygments FAQ
http://pygments.org/faq/ - Compiler Construction/Lexical analysis
https://en.wikibooks.org/wiki/Compiler_Construction/Lexical_analysis - Compiler Design – Lexical Analysis
https://www.tutorialspoint.com/compiler_design/compiler_design_lexical_analysis.htm - Lexical Analysis – An Intro
https://www.scribd.com/document/383765692/Lexical-Analysis - Python AST Visualizer
https://github.com/pombredanne/python-ast-visualizer - What is an Abstract Syntax Tree
https://blog.bitsrc.io/what-is-an-abstract-syntax-tree-7502b71bde27 - Why is AST so important
https://medium.com/@obernardovieira/why-is-ast-so-important-b1e7d6c29260 - Emily Morehouse-Valcarcel – The AST and Me – PyCon 2018
https://www.youtube.com/watch?v=XhWvz4dK4ng - Python AST Parsing and Custom Linting
https://www.youtube.com/watch?v=OjPT15y2EpE - Chase Stevens – Exploring the Python AST Ecosystem
https://www.youtube.com/watch?v=Yq3wTWkoaYY - Full Grammar specification
https://docs.python.org/3/reference/grammar.html