Automatický refaktoring zdrojových kódů Pythonu s využitím nástroje Pyrefact

28. 3. 2024
Doba čtení: 26 minut

Sdílet

Ilustrační snímek Autor: Depositphotos
Ilustrační snímek
Prozatím poměrně neznámý nástroj Pyrefact, jehož základní vlastnosti si dnes popíšeme, dokáže automaticky optimalizovat a refaktorovat zdrojové kódy Pythonu. K tomu využívá abstraktního syntaktického stromu (AST).

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

11. Částečná optimalizace

12. Odstranění mrtvého kódu

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í

18. Závěrečné zhodnocení

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

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.

Poznámka: pro dále popisovaný nástroj Pyrefact bylo vytvořeno i rozšíření (plugin) do VSCode, které je dostupné na https://marketplace.visual­studio.com/items?itemName=o­lleln.pyrefact. Toto rozšíření umožní provádět automatický refaktoring přímo z tohoto IDE. My si však v dalším textu ukážeme základní volání Pyrefactu z příkazové řádky (výsledky by měly být stejné).

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))
Poznámka: podtržítko před jméno funkce bylo přidáno z toho důvodu, že se jedná o lokální funkci.

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)
Poznámka: předávané parametry do add_globals se nevyužijí. I přesto Pyrefact tento zdrojový kód transformuje bez ohlášení chyby. Tu uvidíme až v runtime, tedy po spuštění skriptu.

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)
Poznámka: oba výsledky by přitom měly být totožné, což naznačuje, že Pyrefact má stále určité mezery a není dokonalý.

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())
Poznámka: teoreticky by tato náhrada šla provést pro všechny případy, v nichž se volá kombinace operací open-close a navíc příslušná třída realizuje speciální metody __enter__ a __exit__. Obecně je podpora správců kontextu jednou z nejelegantnějších vlastností Pythonu, minimálně pro autora tohoto článku :-)

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:

  1. Uložit současný stav projektu do systému pro správu verzí
  2. Naformátovat si kódy pomocí Black s vaším nastavením
  3. Spustit Pyrefact
  4. Znovu naformátovat kódy pomocí Black s vaším nastavením
  5. Jednotlivé změny buď schválit nebo zahodit (použijte klasický diff nebo jiný podobně koncipovaný nástroj či přímo IDE).
  6. 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.

bitcoin školení listopad 24

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:

# Demonstrační příklad Stručný popis příkladu Cesta
1 01_unused_variables_input.py zdrojový kód s nevyužitými globálními proměnnými https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/01_u­nused_variables_input.py
2 01_unused_variables_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/01_u­nused_variables_output.py
       
3 02_local_variables_input.py zdrojový kód s nevyužitými lokálními proměnnými https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/02_lo­cal_variables_input.py
4 02_local_variables_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/02_lo­cal_variables_output.py
       
5 03_global_variables_input.py funkce, která přistupuje ke globálním proměnným https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/03_glo­bal_variables_input.py
6 03_global_variables_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/03_glo­bal_variables_output.py
       
7 04_if_else_input.py blok s podmínkou, v obou větvích se mění globální proměnná https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/04_if_el­se_input.py
8 04_if_else_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/04_if_el­se_output.py
       
9 05_if_else_input.py blok s podmínkou se změnou globální proměnné, která se nikde nečte https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/05_if_el­se_input.py
10 05_if_else_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/05_if_el­se_output.py
       
11 06_list_append_input.py kód provádějící postupné připojování prvků do seznamu https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/06_lis­t_append_input.py
12 06_list_append_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/06_lis­t_append_output.py
       
13 07_list_comprehension_input.py programová smyčka postupně připojující prvky do seznamu https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/07_lis­t_comprehension_input.py
14 07_list_comprehension_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/07_lis­t_comprehension_output.py
       
15 08_set_comprehension_input.py programová smyčka postupně vkládající prvky do množiny https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/08_set_com­prehension_input.py
16 08_set_comprehension_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/08_set_com­prehension_output.py
       
17 09_dict_comprehension_input.py programová smyčka postupně přidávající prvky do slovníku https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/09_dic­t_comprehension_input.py
18 09_dict_comprehension_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/09_dic­t_comprehension_output.py
       
19 10_loop_to_constant_input.py smyčka s výpočtem sumy https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/10_lo­op_to_constant_input.py
20 10_loop_to_constant_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/10_lo­op_to_constant_output.py
       
21 11_loop_to_constant_input.py smyčka s postupným přidáváním prvků do seznamu následovaná výpočtem sumy https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/11_lo­op_to_constant_input.py
22 11_loop_to_constant_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/11_lo­op_to_constant_output.py
       
23 12_loop_to_constant_input.py dvojice vnořených programových smyček s výpočtem https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/12_lo­op_to_constant_input.py
24 12_loop_to_constant_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/12_lo­op_to_constant_output.py
       
25 13_loop_to_constant_input.py programová smyčka s přidáváním prvku do seznamu (složitější varianta) https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/13_lo­op_to_constant_input.py
26 13_loop_to_constant_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/13_lo­op_to_constant_output.py
       
27 14_loop_to_constant_input.py programová smyčka s přidáváním prvku do seznamu (složitější varianta) https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/14_lo­op_to_constant_input.py
28 14_loop_to_constant_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/14_lo­op_to_constant_output.py
       
29 15_no_full_optimization_input.py vnořené programové smyčky, manipulace s dvojicí seznamů https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/15_no_fu­ll_optimization_input.py
30 15_no_full_optimization_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/15_no_fu­ll_optimization_output.py
       
31 16_dead_code_elimination_input.py zdrojový kód s nadbytečnými programovými řádky https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/16_de­ad_code_elimination_input­.py
32 16_dead_code_elimination_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/16_de­ad_code_elimination_output­.py
       
33 17_equality_check_input.py neidiomatické testy na hodnoty None, True a False https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/17_e­quality_check_input.py
34 17_equality_check_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/17_e­quality_check_output.py
       
35 18_numpy_sum_input.py součet prvků v poli (NumPy) https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/18_num­py_sum_input.py
36 18_numpy_sum_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/18_num­py_sum_output.py
       
37 19_context_manager_input.py otevření souboru bez použití správce kontextu https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/19_con­text_manager_input.py
38 19_context_manager_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/19_con­text_manager_output.py
       
39 20_processor_input.py složitější skript, který se bude optimalizovat https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/20_pro­cessor_input.py
40 20_processor_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/20_pro­cessor_output.py
       
41 21_show_input.py složitější skript, který se bude optimalizovat https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/21_show_in­put.py
42 21_show_output.py výsledek refaktoringu nástrojem Pyrefact https://github.com/tisnik/most-popular-python-libs/blob/master/pyrefact/21_.py

20. Odkazy na Internetu

  1. pyrefact na PyPi
    https://pypi.org/project/pyrefact/
  2. Repositář projektu pyrefact
    https://github.com/OlleLin­dgren/pyrefact
  3. pyrefact jako plugin do VSCode
    https://marketplace.visual­studio.com/items?itemName=o­lleln.pyrefact
  4. pyrefact-vscode-extension (repositář)
    https://github.com/OlleLin­dgren/pyrefact-vscode-extension
  5. Best Python Refactoring Tools for 2023
    https://www.developer.com/lan­guages/python/best-python-refactoring-tools/
  6. Python Refactoring: Techniques, Tools, and Best Practices
    https://www.codesee.io/learning-center/python-refactoring
  7. 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/
  8. 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/
  9. 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/
  10. 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/
  11. 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/
  12. LibCST – dokumentace
    https://libcst.readthedoc­s.io/en/latest/index.html
  13. libCST na PyPi
    https://pypi.org/project/libcst/
  14. libCST na GitHubu
    https://github.com/Instagram/LibCST
  15. Inside The Python Virtual Machine
    https://leanpub.com/insidet­hepythonvirtualmachine
  16. module-py_compile
    https://docs.python.org/3­.8/library/py_compile.html
  17. Given a python .pyc file, is there a tool that let me view the bytecode?
    https://stackoverflow.com/qu­estions/11141387/given-a-python-pyc-file-is-there-a-tool-that-let-me-view-the-bytecode
  18. The structure of .pyc files
    https://nedbatchelder.com/blog/200804/the_str­ucture_of_pyc_files.html
  19. Python Bytecode: Fun With Dis
    http://akaptur.github.io/blog/2013/08/14/pyt­hon-bytecode-fun-with-dis/
  20. Python's Innards: Hello, ceval.c!
    http://tech.blog.aknin.na­me/category/my-projects/pythons-innards/
  21. Byterun
    https://github.com/nedbat/byterun
  22. Python Byte Code Instructions
    http://document.ihg.uni-duisburg.de/Documentation/Pyt­hon/lib/node56.html
  23. Python Byte Code Instructions
    https://docs.python.org/3­.2/library/dis.html#python-bytecode-instructions
  24. dis – Python module
    https://docs.python.org/2/li­brary/dis.html
  25. Comparison of Python virtual machines
    http://polishlinux.org/ap­ps/cli/comparison-of-python-virtual-machines/
  26. O-code
    http://en.wikipedia.org/wiki/O-code_machine
  27. Abstract syntax tree
    https://en.wikipedia.org/wi­ki/Abstract_syntax_tree
  28. Lexical analysis
    https://en.wikipedia.org/wi­ki/Lexical_analysis
  29. Parser
    https://en.wikipedia.org/wi­ki/Parsing#Parser
  30. Parse tree
    https://en.wikipedia.org/wi­ki/Parse_tree
  31. Derivační strom
    https://cs.wikipedia.org/wi­ki/Deriva%C4%8Dn%C3%AD_strom
  32. Python doc: ast — Abstract Syntax Trees
    https://docs.python.org/3/li­brary/ast.html
  33. Python doc: tokenize — Tokenizer for Python source
    https://docs.python.org/3/li­brary/tokenize.html
  34. SymbolTable
    https://docs.python.org/3­.8/library/symtable.html
  35. 5 Amazing Python AST Module Examples
    https://www.pythonpool.com/python-ast/
  36. Intro to Python ast Module
    https://medium.com/@wshanshan/intro-to-python-ast-module-bbd22cd505f7
  37. Golang AST Package
    https://golangdocs.com/golang-ast-package
  38. AP8, IN8 Regulární jazyky
    http://statnice.dqd.cz/home:inf:ap8
  39. AP9, IN9 Konečné automaty
    http://statnice.dqd.cz/home:inf:ap9
  40. AP10, IN10 Bezkontextové jazyky
    http://statnice.dqd.cz/home:inf:ap10
  41. AP11, IN11 Zásobníkové automaty, Syntaktická analýza
    http://statnice.dqd.cz/home:inf:ap11
  42. Introduction to YACC
    https://www.geeksforgeeks­.org/introduction-to-yacc/
  43. Introduction of Lexical Analysis
    https://www.geeksforgeeks­.org/introduction-of-lexical-analysis/?ref=lbp
  44. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  45. Pygments – Python syntax highlighter
    http://pygments.org/
  46. Pygments (dokumentace)
    http://pygments.org/docs/
  47. Write your own filter
    http://pygments.org/docs/fil­terdevelopment/
  48. Write your own lexer
    http://pygments.org/docs/le­xerdevelopment/
  49. Write your own formatter
    http://pygments.org/docs/for­matterdevelopment/
  50. Jazyky podporované knihovnou Pygments
    http://pygments.org/languages/
  51. Pygments FAQ
    http://pygments.org/faq/
  52. Compiler Construction/Lexical analysis
    https://en.wikibooks.org/wi­ki/Compiler_Construction/Le­xical_analysis
  53. Compiler Design – Lexical Analysis
    https://www.tutorialspoin­t.com/compiler_design/com­piler_design_lexical_analy­sis.htm
  54. Lexical Analysis – An Intro
    https://www.scribd.com/do­cument/383765692/Lexical-Analysis
  55. Python AST Visualizer
    https://github.com/pombredanne/python-ast-visualizer
  56. What is an Abstract Syntax Tree
    https://blog.bitsrc.io/what-is-an-abstract-syntax-tree-7502b71bde27
  57. Why is AST so important
    https://medium.com/@obernar­dovieira/why-is-ast-so-important-b1e7d6c29260
  58. Emily Morehouse-Valcarcel – The AST and Me – PyCon 2018
    https://www.youtube.com/wat­ch?v=XhWvz4dK4ng
  59. Python AST Parsing and Custom Linting
    https://www.youtube.com/wat­ch?v=OjPT15y2EpE
  60. Chase Stevens – Exploring the Python AST Ecosystem
    https://www.youtube.com/wat­ch?v=Yq3wTWkoaYY
  61. Full Grammar specification
    https://docs.python.org/3/re­ference/grammar.html
ikonka

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.