Typová inference v Pythonu prováděná v runtime

4. 1. 2024
Doba čtení: 33 minut

Sdílet

Autor: © Andrea Danti - Fotolia.com
Dnes se seznámíme s užitečným projektem MonkeyType. Umožňuje odvodit datové typy argumentů funkcí a metod na základě sledování činnosti běžící aplikace. Získané typové informace lze automaticky přidat do kódu.

Obsah

1. Typová inference v Pythonu prováděná v runtime

2. Princip činnosti nástroje MonkeyType

3. Instalace nástroje MonkeyType

4. Modul, který budeme zkoumat

5. Zavolání funkcí z analyzovaného modulu

6. Výsledky analýzy uložené v databázi

7. Vygenerování stub souboru, automatické přidání typových informací do analyzovaného modulu

8. Volání funkcí z analyzovaného modulu s různými parametry

9. Automatické přidání typů k algoritmu výpočtu Ackermannovy funkce

10. Postup při manuální nebo automatizované úpravě stávajících projektů bez typových anotací

11. Zdrojový kód po základním refaktoringu, ovšem bez typových informací

12. Analýza projektu

13. Výsledek po přidání typových informací

14. Kontrola nástrojem Mypy

15. Problematické chování nástroje MonkeyType

16. n-tice s mnoha prvky

17. Pád v případě použití typu Callable na starších verzích Pythonu

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

19. Odkazy na další články o typových anotacích v Pythonu

20. Odkazy na Internetu

1. Typová inference v Pythonu prováděná v runtime

Na stránkách Roota jsme se již několikrát zabývali problematikou typových anotací (type annotations, type hints) v programovacím jazyku Python (viz též odkazy na příslušné články uvedené v devatenácté kapitole). Připomeňme si, že se jedná o rozšíření jazyka Python o novou syntaxi i sémantiku, která umožňuje dobrovolně (a klidně jen na určitých místech v programovém kódu) specifikovat typy proměnných, argumentů funkcí a metod, návratové typy funkcí a metod, ale i generické typy vztažené ke třídám a funkcím. Tyto přidané typové informace se, i když je standardní CPython prakticky ignoruje, již dnes používají k několika účelům. Především k zajištění korektnosti zdrojových kódů z pohledu typového systému (do funkce akceptující řetězec nelze předat celé číslo atd.), ale taktéž k lepším optimalizacím, které díky těmto poměrně důležitým informacím mohou provádět AOT (ahead of time) a JIT (just in time) překladače Pythonu. A v neposlední řadě je informace o typech užitečným prvkem samodokumentujícího se kódu a využívají ji některá integrovaná vývojová prostředí (a LSP).

Taktéž jsme si již v předchozích článcích řekli, že nástroje typu Mypy do jisté míry dokážou provádět typovou inferenci, tj. zjištění, jakého typu mají být například argumenty funkcí nebo metod na základě detekce, s jakými parametry (resp. jejich typy) je funkce volána. Ovšem tato typová inference je v Mypy omezena na statickou analýzu. Zejména pro starší zdrojové kódy, které neobsahují typové anotace, by však bylo zajímavé a užitečné použít nějaký nástroj, který tyto typové anotace dokáže do zdrojového kódu automaticky či alespoň poloautomaticky přidat, a to na základě analýzy provedené za běhu aplikace (runtime). Tímto způsobem by bylo možné zjistit, jak je programový kód používaný reálně. A právě taková funkcionalita je realizována v nástroji MonkeyType, s nímž se seznámíme v dnešním článku.

Poznámka: hned na úvod je nutné předeslat, že výsledky analýzy provedené nástrojem MonkeyType nemusí být vždy dokonalé nebo ucelené a mnohdy je tedy nutné provést ruční úpravy. Ostatně i v IT platí, že nic není zcela zadarmo.

2. Princip činnosti nástroje MonkeyType

Pojďme si nyní alespoň ve stručnosti naznačit, jak vlastně nástroj MonkeyType pracuje. Není to ve skutečnosti nic složitého (minimálně ne v ekosystému Pythonu, který nabízí plnohodnotnou introspekci). Nástroji MonkeyType je nutné předat jméno skriptu, který se má spustit. Skript (což může být spouštěcí skript i rozsáhlého projektu) je následně skutečně spuštěn, ovšem MonkeyType přitom sleduje volání všech funkcí a metod. Průběžně si zaznamenává jak konkrétní typy předaných hodnot, tak i typy návratových hodnot. Tyto informace jsou uloženy do lokální databáze SQLite, z níž se posléze mohou přečíst, analyzovat a dále zpracovat. Jakmile je skript dokončen (lze ho totiž pochopitelně spustit vícekrát, například s odlišnými parametry atd.), můžeme MonkeyType nechat provést tři operace:

  1. Vypsat si jména všech modulů, jejichž funkce a metody byly volány.
  2. Nechat si vygenerovat takzvaný stub soubor s informacemi o typech parametrů atd. Tento soubor je možné zpracovat dalšími nástroji (například ho podporuje i výše zmíněný Mypy atd.), popř. se může stát součástí projektu – typové informace tedy budou odděleny od programového kódu.
  3. Alternativně je možné si nechat přidat informace o typech přímo do originálních zdrojových kódů. Výsledek je pochopitelně možné upravit či v některých případech je spíše bude nutné opravit. A výsledek by měl projít (striktní) statickou analýzou nástrojem Mypy.

3. Instalace nástroje MonkeyType

Ve druhé části článku si ukážeme činnost MonkeyType prakticky, na několika demonstračních příkladech. Samotná instalace tohoto nástroje je snadná a rychlá, protože má jen minimum závislostí. Pro jeho instalaci můžeme použít standardní utilitu pip, popř. pip3 (na systémech s více instalacemi Pythonu) a provést instalaci pouze pro aktivního uživatele (přepínač –user):

$ pip3 install --user monkeytype
 
Collecting monkeytype
  Downloading MonkeyType-23.3.0-py3-none-any.whl (40 kB)
     |████████████████████████████████| 40 kB 1.2 MB/s
Requirement already satisfied: mypy-extensions in ./.local/lib/python3.8/site-packages (from monkeytype) (1.0.0)
Collecting libcst>=0.4.4
  Downloading libcst-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.2 MB)
     |████████████████████████████████| 3.2 MB 1.2 MB/s
Requirement already satisfied: typing-extensions>=3.7.4.2 in ./.local/lib/python3.8/site-packages (from libcst>=0.4.4->monkeytype) (4.7.1)
Requirement already satisfied: pyyaml>=5.2 in /usr/lib/python3/dist-packages (from libcst>=0.4.4->monkeytype) (5.3.1)
Collecting typing-inspect>=0.4.0
  Downloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)
Installing collected packages: typing-inspect, libcst, monkeytype
Successfully installed libcst-1.1.0 monkeytype-23.3.0 typing-inspect-0.9.0
Poznámka: povšimněte si, že se mj. jako tranzitivní závislost instaluje i balíček nazvaný libcst. Jedná se o velmi zajímavou a užitečnou knihovnu používanou pro transformaci zdrojových kódů, ke které se ještě vrátíme v samostatném článku.

4. Modul, který budeme zkoumat

První modul naprogramovaný v jazyce Python, který budeme zkoumat a zjišťovat, s jakými typy parametrů jsou volané v něm definované funkce, je velmi malý (až triviální). Obsahuje pouze dvě funkce nazvané add a inc. U těchto funkcí nejsou uvedeny žádné typové informace (což je v Pythonu stále obvyklé), což znamená, že například první funkci můžeme volat s parametry typu celé číslo, s hodnotami s plovoucí řádovou čárkou, seznamy, řetězci, libovolnými objekty, v jejichž třídě je přetížen operátor + atd.:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
 
def add(a, b):
    return a + b
 
 
def inc(a):
    return a + 1
Poznámka: v rámci dalších kapitol budeme pochopitelně zkoumat složitější zdrojový kód, který navíc bude obsahovat různé datové typy.

5. Zavolání funkcí z analyzovaného modulu

Modul adder je uložen v samostatném adresáři se stejným jménem (tedy obsahuje jen dvojici souborů adder/__init.py__ a adder/adder.py). V aktuálním adresáři vytvoříme nějaký pomocný skript, například pojmenovaný test_adder1.py. V tomto skriptu naimportujeme volané funkce z modulu adder a posléze tyto funkce skutečně zavoláme. Povšimněte si, že volaným funkcím předáváme hodnoty typu int (celá čísla):

from adder.adder import add, inc
 
print(add(1, 2))
print(inc(41))

Nyní nastává důležitý okamžik. Spustíme tento testovací skript, ale nikoli přímo (python test_adder1.py), ale nepřímo přes nástroj MonkeyType příkazem run:

$ monkeytype run test_adder_1.py

Skript se zdánlivě spustí obvyklým způsobem; ostatně vypíše i očekávané výsledky:

3
42

Ovšem povšimněte si, že kromě toho vznikl v aktuálním adresáři i soubor monkeytype.sqlite3. Ten využijeme v dalších krocích.

Poznámka: je zde ještě jeden rozdíl – analyzovaná aplikace běží pomaleji, v některých případech až o řád pomaleji, než bez použití MonkeyType!

6. Výsledky analýzy uložené v databázi

Příkazem list-modules předaného nástroji MonkeyType si můžeme nechat vypsat plná jména všech modulů, jejichž funkce a metody byly při analýze skriptu (projektu) volány. V našem konkrétním případě bychom měli získat pouze jméno jediného modulu:

$ monkeytype list-modules
 
adder.adder

Informace o volaných funkcích a metodách (včetně jejich modulu) jsou uloženy v lokální SQLite databázi, konkrétně v souboru se jménem monkeytype.sqlite3. Nic nám pochopitelně nebrání v tom si obsah této databáze prohlédnout. Použijeme k tomu klienta sqlite3. Nejprve databázový soubor otevřeme:

$ sqlite3 monkeytype.sqlite3
 
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.

Dále si necháme vypsat jména všech tabulek v databázi:

sqlite> .tables
 
monkeytype_call_traces

Vidíme, že zde existuje jen jediná tabulka, jejíž schéma je do značné míry samopopisné:

sqlite> .schema monkeytype_call_traces
 
CREATE TABLE monkeytype_call_traces (
  created_at  TEXT,
  module      TEXT,
  qualname    TEXT,
  arg_types   TEXT,
  return_type TEXT,
  yield_type  TEXT);

Vypišme si obsah této tabulky:

sqlite> select * from monkeytype_call_traces ;
 
2023-12-30 14:16:19.170512|adder.adder|add|{"a": {"module": "builtins", "qualname": "int"}, "b": {"module": "builtins", "qualname": "int"}}|{"module": "builtins", "qualname": "int"}|
2023-12-30 14:16:19.170533|adder.adder|inc|{"a": {"module": "builtins", "qualname": "int"}}|{"module": "builtins", "qualname": "int"}|
Poznámka: v dalších verzích nástroje MonkeyType se schéma databáze může měnit. Už nyní existuje patch, který se snaží schéma databáze normalizovat a taktéž omezit duplicitní záznamy (které běžně vznikají).

7. Vygenerování stub souboru, automatické přidání typových informací do analyzovaného modulu

S obsahem databáze však většinou není zapotřebí manipulovat přímo, protože příslušné operace jsou již implementovány přímo v nástroji MonkeyType. Nejdříve si necháme vygenerovat takzvaný stub soubor, který obsahuje pouze informace o datových typech proměnných a funkcí, nikoli však jejich těla:

$ monkeytype stub adder.adder

Výsledek by měl vypadat následovně:

def add(a: int, b: int) -> int: ...
 
 
def inc(a: int) -> int: ...
Poznámka: o stub souborech vyjde samostatný článek. Mají hned několik způsobů použití a mj. zajišťují i zpětnou kompatibilitu (pokud je vyžadována).

Kromě stub souboru můžeme nástroj MonkeyType instruovat, aby typové informace přidal přímo do zdrojového kódu analyzovaného modulu. Provádí se to následujícím způsobem:

$ monkeytype apply adder.adder

Kromě zpráv vypsaných na standardní výstup by se měl zdrojový kód upravit do této finální (korektní) podoby:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
def add(a: int, b: int) -> int:
    return a+b
 
def inc(a: int) -> int:
    return a+1

8. Volání funkcí z analyzovaného modulu s různými parametry

Ve druhém kroku se pokusíme funkce z modulu adder, tj. funkce nazvané add a inc, volat s odlišnými typy parametrů. Konkrétně to znamená, že funkci add předáme dvojici řetězců a dvojici seznamů. Funkci inc pak předáme celé číslo a hodnotu s plovoucí řádovou čárkou:

from adder.adder import add, inc
 
print(add(1, 2))
print(add("foo", "bar"))
print(add([1, 2, 3], [4, 5, 6]))
 
print(inc(41))
print(inc(1.5))

V SQLite databázi by se měly po provedení těchto kroků objevit záznamy o odlišných parametrech. Databáze by měla vypadat zhruba následovně:

sqlite> select * from monkeytype_call_traces;
 
2023-12-30 14:19:27.525313|adder.adder|add|{"a": {"module": "builtins", "qualname": "int"}, "b": {"module": "builtins", "qualname": "int"}}|{"module": "builtins", "qualname": "int"}|
2023-12-30 14:19:27.525338|adder.adder|add|{"a": {"module": "builtins", "qualname": "str"}, "b": {"module": "builtins", "qualname": "str"}}|{"module": "builtins", "qualname": "str"}|
2023-12-30 14:19:27.525373|adder.adder|add|{"a": {"elem_types": [{"module": "builtins", "qualname": "int"}], "module": "typing", "qualname": "List"}, "b": {"elem_types": [{"module": "builtins", "qualname": "int"}], "module": "typing", "qualname": "List"}}|{"elem_types": [{"module": "builtins", "qualname": "int"}], "module": "typing", "qualname": "List"}|
2023-12-30 14:19:27.525387|adder.adder|inc|{"a": {"module": "builtins", "qualname": "int"}}|{"module": "builtins", "qualname": "int"}|
2023-12-30 14:19:27.525401|adder.adder|inc|{"a": {"module": "builtins", "qualname": "float"}}|{"module": "builtins", "qualname": "float"}|

Důležitější jsou však typové informace na úrovni zdrojového kódu. Ty si můžeme nechat zobrazit (stub soubor) nebo přímo přidat do zdrojového kódu analyzovaného modulu, podobně jako v předchozí kapitole:

$ monkeytype stub adder.adder

Získáme:

from typing import (
    List,
    Union,
)
 
 
def add(a: Union[int, List[int], str], b: Union[int, List[int], str]) -> Union[int, List[int], str]: ...
 
 
def inc(a: Union[int, float]) -> Union[int, float]: ...

Modifikace zdrojového kódu:

$ monkeytype apply adder.adder

S výsledkem:

 #!/usr/bin/env python3
 # vim: set fileencoding=utf-8
 
from typing import List, Union
 
def add(a: Union[int, List[int], str], b: Union[int, List[int], str]) -> Union[int, List[int], str]:
    return a+b
 
def inc(a: Union[int, float]) -> Union[int, float]:
    return a+1

9. Automatické přidání typů k algoritmu výpočtu Ackermannovy funkce

Naprosto stejným postupem si můžeme nechat přidat typové informace do algoritmu pro výpočet Ackermannovy funkce. Původní podoba zdrojového kódu je následující:

# Výpočet Ackermannovy funkce, založeno na konstrukci if
 
def A(m, n):
    """Ackermannova funkce."""
    if m == 0:
        return n + 1
    if n == 0:
        return A(m - 1, 1)
    return A(m - 1, A(m, n - 1))
 
 
# otestování korektnosti výpočtu Ackermannovy funkce
for m in range(4):
    for n in range(5):
        print(m, n, A(m, n))

Nástrojem MonkeyType přitom budeme spouštět tento skript:

import ackermann
 
ackermann.A(3, 3)

A po automatické modifikaci kódu bychom měli získat algoritmus s plnými typovými informacemi:

# Výpočet Ackermannovy funkce, založeno na konstrukci if
 
def A(m: int, n: int) -> int:
    """Ackermannova funkce."""
    if m == 0:
        return n + 1
    if n == 0:
        return A(m - 1, 1)
    return A(m - 1, A(m, n - 1))
 
 
# otestování korektnosti výpočtu Ackermannovy funkce
for m in range(4):
    for n in range(5):
        print(m, n, A(m, n))

10. Postup při manuální nebo automatizované úpravě stávajících projektů bez typových anotací

V článcích o knihovně Mypy jsme si ukázali, jakým způsobem by se mohlo postupovat při úpravě stávajících projektů napsaných v Pythonu, které prozatím nepoužívají typové anotace. Celý postup jsme shrnuli do několika bodů:

  1. Refaktoring, ideálně tak, aby se nepoužívaly globální proměnné a globální kód
  2. Použití Mypy pro nalezení chybějících typových anotací
  3. Postupné doplnění typových anotací
  4. Odstranění reálných chyb nalezených nástrojem Mypy

Druhý, třetí i čtvrtý bod lze do značné míry automatizovat s využitím nástroje MonkeyType. Ukážeme si to na stejném demonstračním příkladu, do jakého jsme ručně přidávali typové informace. Nyní tuto operaci ponecháme na nástroji MonkeyType a porovnáme výsledek s ruční prací.

11. Zdrojový kód po základním refaktoringu, ovšem bez typových informací

Původní zdrojový kód demonstračního příkladu tak, jak byl kdysi ukázán v článku o knihovně Pygame, naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites1.py. Tento kód pochopitelně neobsahuje žádné typové informace (anotace) a bude ho zapotřebí poněkud refaktorovat.

Při refaktoringu postupně některé části zdrojového kódu vložíme do funkcí a zajistíme, aby se nepoužívaly globální proměnné (pouze globální „konstanty“, i když koncept pravých konstant v Pythonu není). Refaktorovaný kód sice stále nepoužívá typové anotace, ale už je na tuto důležitou změnu již náležitě připraven:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
import pygame
import sys
 
# Nutno importovat kvůli konstantám QUIT atd.
from pygame.locals import *
 
# Velikost okna aplikace
WIDTH = 320
HEIGHT = 240
 
# Konstanty s n-ticemi představujícími základní barvy
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GRAY = (128, 128, 128)
YELLOW = (255, 255, 0)
 
CAPTION = "Sprites in Pygame"
 
 
# Třída představující sprite zobrazený jako jednobarevný čtverec.
class BlockySprite(pygame.sprite.Sprite):
    # Konstruktor
    def __init__(self, color, size, x, y):
        # Nejprve je nutné zavolat konstruktor předka,
        # tj. konstruktor třídy pygame.sprite.Sprite:
        pygame.sprite.Sprite.__init__(self)
 
        # Vytvoření obrázku představujícího vizuální obraz spritu:
        self.image = pygame.Surface([size, size])
        self.image.fill(color)
 
        # Vytvoření obalového obdélníku
        # (velikost se získá z rozměru obrázku)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
 
        # Počáteční rychlost spritu
        self.speed_x = 0
        self.speed_y = 0
 
    # Nastavení barvy spritu, který kolidoval s hráčem
    def yellowColor(self):
        self.image.fill(YELLOW)
 
    # Nastavení barvy spritu, který nekolidoval s hráčem
    def grayColor(self):
        self.image.fill(GRAY)
 
 
def initDisplay(caption):
    # Vytvoření okna pro vykreslování
    display = pygame.display.set_mode([WIDTH, HEIGHT])
 
    # Nastavení titulku okna
    pygame.display.set_caption(caption)
 
    return display
 
 
def createSprites():
    # Objekt sdružující všechny sprity
    all_sprites = pygame.sprite.Group()
 
    # Objekt sdružující všechny sprity kromě hráče
    all_sprites_but_player = pygame.sprite.Group()
 
    # Vytvoření několika typů spritů
    #                    barva  x   y velikost
    wall1 = BlockySprite(GRAY, 50, 10, 10)
    wall2 = BlockySprite(GRAY, 15, 100, 100)
    wall3 = BlockySprite(GRAY, 15, 100, 150)
    wall4 = BlockySprite(GRAY, 15, 200, 100)
    wall5 = BlockySprite(GRAY, 15, 200, 150)
    wall6 = BlockySprite(GRAY, 15, 150, 100)
    wall7 = BlockySprite(GRAY, 15, 150, 150)
    player = BlockySprite(RED, 40, WIDTH / 2 - 20, HEIGHT / 2 - 20)
 
    # Přidání několika dalších spritů do seznamu
    # (jen jeden sprite - ten poslední - bude ve skutečnosti pohyblivý)
    all_sprites.add(wall1)
    all_sprites.add(wall2)
    all_sprites.add(wall3)
    all_sprites.add(wall4)
    all_sprites.add(wall5)
    all_sprites.add(wall6)
    all_sprites.add(wall7)
    all_sprites.add(player)
 
    # Seznam všech nepohyblivých spritů
    all_sprites_but_player.add(wall1)
    all_sprites_but_player.add(wall2)
    all_sprites_but_player.add(wall3)
    all_sprites_but_player.add(wall4)
    all_sprites_but_player.add(wall5)
    all_sprites_but_player.add(wall6)
    all_sprites_but_player.add(wall7)
 
    return all_sprites, all_sprites_but_player, player
 
 
# Posun všech spritů ve skupině na základě jejich rychlosti
def move_sprites(sprite_group, playground_width, playground_height):
    for sprite in sprite_group:
        # Posun spritu
        sprite.rect.x = sprite.rect.x + sprite.speed_x
        sprite.rect.y = sprite.rect.y + sprite.speed_y
        # Kontrola, zda sprite nenarazil do okrajů okna
        if sprite.rect.x < 0:
            sprite.rect.x = 0
            sprite.speed_x = 0
        if sprite.rect.x + sprite.rect.width > playground_width:
            sprite.rect.x = playground_width - sprite.rect.width
            sprite.speed_x = 0
        if sprite.rect.y < 0:
            sprite.rect.y = 0
            sprite.speed_y = 0
        if sprite.rect.y + sprite.rect.height > playground_height:
            sprite.rect.y = playground_height - sprite.rect.height
            sprite.speed_y = 0
 
 
# Vykreslení celé scény na obrazovku
def draw_scene(display, background_color, sprite_group):
    # Vyplnění plochy okna černou barvou
    display.fill(background_color)
    # Vykreslení celé skupiny spritů do bufferu
    sprite_group.draw(display)
    # Obnovení obsahu obrazovky (překlopení zadního a předního bufferu)
    pygame.display.update()
 
 
# Změna barvy spritu na základě kolize s hráčem
def change_colors(sprite_group, hit_list):
    # Projít všemi sprity ze skupiny, kterou detekovala kolizní funkce
    for sprite in sprite_group:
        if sprite in hit_list:
            sprite.yellowColor()
        else:
            sprite.grayColor()
 
 
# Zjistí kolize spritu se "stěnami" (nepohyblivými sprity)
def check_collisions(player, sprite_group):
    # Vytvoření seznamu spritů, které kolidují s hráčem
    hit_list = pygame.sprite.spritecollide(player, sprite_group, False)
    # Změna barev kolidujících spritů
    change_colors(sprite_group, hit_list)
    collisions = len(hit_list)
    # Přenastavení titulku okna
    caption = CAPTION + ": collisions " + str(collisions)
    pygame.display.set_caption(caption)
 
 
def mainLoop(display, clock, all_sprites, all_sprites_but_player, player):
    while True:
        # Načtení a zpracování všech událostí z fronty
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    pygame.quit()
                    sys.exit()
                # Stiskem kurzorových kláves je možné měnit směr pohybu spritu
                elif event.key == pygame.K_LEFT:
                    player.speed_x = -3
                elif event.key == pygame.K_RIGHT:
                    player.speed_x = +3
                elif event.key == pygame.K_UP:
                    player.speed_y = -3
                elif event.key == pygame.K_DOWN:
                    player.speed_y = +3
            if event.type == KEYUP:
                # Puštění kurzorových kláves vede k zastavení pohybu spritu
                if event.key == pygame.K_LEFT:
                    player.speed_x = 0
                elif event.key == pygame.K_RIGHT:
                    player.speed_x = 0
                elif event.key == pygame.K_UP:
                    player.speed_y = 0
                elif event.key == pygame.K_DOWN:
                    player.speed_y = 0
 
        move_sprites(all_sprites, display.get_width(), display.get_height())
        check_collisions(player, all_sprites_but_player)
        draw_scene(display, BLACK, all_sprites)
        clock.tick(20)
 
 
def main():
    # Inicializace knihovny Pygame
    pygame.init()
 
    clock = pygame.time.Clock()
    display = initDisplay(CAPTION)
 
    all_sprites, all_sprites_but_player, player = createSprites()
 
    mainLoop(display, clock, all_sprites, all_sprites_but_player, player)
 
 
if __name__ == "__main__":
    main()
 
 
# finito

12. Analýza projektu

Analýzu projektu, jehož zdrojový kód byl ukázán v jedenácté kapitole je snadná. Pouze si vytvoříme pomocný skript, který spustí funkci main z projektu:

import sprites
 
sprites.main()

Samotný projekt (demo) spustíme příkazem:

$ monkeytype run run_sprites.py

A nakonec si necháme automaticky upravit zdrojové kódy projektu – přidají se do něj typové informace:

$ monkeytype apply sprites
Poznámka: uvádí se jméno modulu, nikoli jméno zdrojového souboru.

13. Výsledek po přidání typových informací

A takto vypadá zdrojový soubor po automatickém přidání typových informací nástrojem MonkeyType (níže bude zobrazen diff, který je přehlednější):

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
import pygame
import sys
 
# Nutno importovat kvůli konstantám QUIT atd.
from pygame.locals import *
from pygame.sprite import Group
from pygame.surface import Surface
from pygame.time import Clock
from typing import Any, List, Tuple, Union
 
# Velikost okna aplikace
WIDTH = 320
HEIGHT = 240
 
# Konstanty s n-ticemi představujícími základní barvy
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GRAY = (128, 128, 128)
YELLOW = (255, 255, 0)
 
CAPTION = "Sprites in Pygame"
 
 
# Třída představující sprite zobrazený jako jednobarevný čtverec.
class BlockySprite(pygame.sprite.Sprite):
    # Konstruktor
    def __init__(self, color: Tuple[int, int, int], size: int, x: Union[int, float], y: Union[int, float]) -> None:
        # Nejprve je nutné zavolat konstruktor předka,
        # tj. konstruktor třídy pygame.sprite.Sprite:
        pygame.sprite.Sprite.__init__(self)
 
        # Vytvoření obrázku představujícího vizuální obraz spritu:
        self.image = pygame.Surface([size, size])
        self.image.fill(color)
 
        # Vytvoření obalového obdélníku
        # (velikost se získá z rozměru obrázku)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
 
        # Počáteční rychlost spritu
        self.speed_x = 0
        self.speed_y = 0
 
    # Nastavení barvy spritu, který kolidoval s hráčem
    def yellowColor(self) -> None:
        self.image.fill(YELLOW)
 
    # Nastavení barvy spritu, který nekolidoval s hráčem
    def grayColor(self) -> None:
        self.image.fill(GRAY)
 
 
def initDisplay(caption: str) -> Surface:
    # Vytvoření okna pro vykreslování
    display = pygame.display.set_mode([WIDTH, HEIGHT])
 
    # Nastavení titulku okna
    pygame.display.set_caption(caption)
 
    return display
 
 
def createSprites() -> Tuple[Group, Group, BlockySprite]:
    # Objekt sdružující všechny sprity
    all_sprites = pygame.sprite.Group()
 
    # Objekt sdružující všechny sprity kromě hráče
    all_sprites_but_player = pygame.sprite.Group()
 
    # Vytvoření několika typů spritů
    #                    barva  x   y velikost
    wall1 = BlockySprite(GRAY, 50, 10, 10)
    wall2 = BlockySprite(GRAY, 15, 100, 100)
    wall3 = BlockySprite(GRAY, 15, 100, 150)
    wall4 = BlockySprite(GRAY, 15, 200, 100)
    wall5 = BlockySprite(GRAY, 15, 200, 150)
    wall6 = BlockySprite(GRAY, 15, 150, 100)
    wall7 = BlockySprite(GRAY, 15, 150, 150)
    player = BlockySprite(RED, 40, WIDTH / 2 - 20, HEIGHT / 2 - 20)
 
    # Přidání několika dalších spritů do seznamu
    # (jen jeden sprite - ten poslední - bude ve skutečnosti pohyblivý)
    all_sprites.add(wall1)
    all_sprites.add(wall2)
    all_sprites.add(wall3)
    all_sprites.add(wall4)
    all_sprites.add(wall5)
    all_sprites.add(wall6)
    all_sprites.add(wall7)
    all_sprites.add(player)
 
    # Seznam všech nepohyblivých spritů
    all_sprites_but_player.add(wall1)
    all_sprites_but_player.add(wall2)
    all_sprites_but_player.add(wall3)
    all_sprites_but_player.add(wall4)
    all_sprites_but_player.add(wall5)
    all_sprites_but_player.add(wall6)
    all_sprites_but_player.add(wall7)
 
    return all_sprites, all_sprites_but_player, player
 
 
# Posun všech spritů ve skupině na základě jejich rychlosti
def move_sprites(sprite_group: Group, playground_width: int, playground_height: int) -> None:
    for sprite in sprite_group:
        # Posun spritu
        sprite.rect.x = sprite.rect.x + sprite.speed_x
        sprite.rect.y = sprite.rect.y + sprite.speed_y
        # Kontrola, zda sprite nenarazil do okrajů okna
        if sprite.rect.x < 0:
            sprite.rect.x = 0
            sprite.speed_x = 0
        if sprite.rect.x + sprite.rect.width > playground_width:
            sprite.rect.x = playground_width - sprite.rect.width
            sprite.speed_x = 0
        if sprite.rect.y < 0:
            sprite.rect.y = 0
            sprite.speed_y = 0
        if sprite.rect.y + sprite.rect.height > playground_height:
            sprite.rect.y = playground_height - sprite.rect.height
            sprite.speed_y = 0
 
 
# Vykreslení celé scény na obrazovku
def draw_scene(display: Surface, background_color: Tuple[int, int, int], sprite_group: Group) -> None:
    # Vyplnění plochy okna černou barvou
    display.fill(background_color)
    # Vykreslení celé skupiny spritů do bufferu
    sprite_group.draw(display)
    # Obnovení obsahu obrazovky (překlopení zadního a předního bufferu)
    pygame.display.update()
 
 
# Změna barvy spritu na základě kolize s hráčem
def change_colors(sprite_group: Group, hit_list: List[Union[Any, BlockySprite]]) -> None:
    # Projít všemi sprity ze skupiny, kterou detekovala kolizní funkce
    for sprite in sprite_group:
        if sprite in hit_list:
            sprite.yellowColor()
        else:
            sprite.grayColor()
 
 
# Zjistí kolize spritu se "stěnami" (nepohyblivými sprity)
def check_collisions(player: BlockySprite, sprite_group: Group) -> None:
    # Vytvoření seznamu spritů, které kolidují s hráčem
    hit_list = pygame.sprite.spritecollide(player, sprite_group, False)
    # Změna barev kolidujících spritů
    change_colors(sprite_group, hit_list)
    collisions = len(hit_list)
    # Přenastavení titulku okna
    caption = CAPTION + ": collisions " + str(collisions)
    pygame.display.set_caption(caption)
 
 
def mainLoop(display: Surface, clock: Clock, all_sprites: Group, all_sprites_but_player: Group, player: BlockySprite):
    while True:
        # Načtení a zpracování všech událostí z fronty
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    pygame.quit()
                    sys.exit()
                # Stiskem kurzorových kláves je možné měnit směr pohybu spritu
                elif event.key == pygame.K_LEFT:
                    player.speed_x = -3
                elif event.key == pygame.K_RIGHT:
                    player.speed_x = +3
                elif event.key == pygame.K_UP:
                    player.speed_y = -3
                elif event.key == pygame.K_DOWN:
                    player.speed_y = +3
            if event.type == KEYUP:
                # Puštění kurzorových kláves vede k zastavení pohybu spritu
                if event.key == pygame.K_LEFT:
                    player.speed_x = 0
                elif event.key == pygame.K_RIGHT:
                    player.speed_x = 0
                elif event.key == pygame.K_UP:
                    player.speed_y = 0
                elif event.key == pygame.K_DOWN:
                    player.speed_y = 0
 
        move_sprites(all_sprites, display.get_width(), display.get_height())
        check_collisions(player, all_sprites_but_player)
        draw_scene(display, BLACK, all_sprites)
        clock.tick(20)
 
 
def main():
    # Inicializace knihovny Pygame
    pygame.init()
 
    clock = pygame.time.Clock()
    display = initDisplay(CAPTION)
 
    all_sprites, all_sprites_but_player, player = createSprites()
 
    mainLoop(display, clock, all_sprites, all_sprites_but_player, player)
 
 
if __name__ == "__main__":
    main()
 
 
# finito

Jak jsme se již zmínili výše, bude pro vývojáře přehlednější se podívat na diff mezi původním zdrojovým kódem a kódem s přidanými typovými informacemi:

--- sprites.py  2023-12-30 15:03:41.000000000 +0100
+++ sprites_patched.py  2023-12-30 15:03:50.000000000 +0100
@@ -18,6 +18,10 @@
 
 # Nutno importovat kvůli konstantám QUIT atd.
 from pygame.locals import *
+from pygame.sprite import Group
+from pygame.surface import Surface
+from pygame.time import Clock
+from typing import Any, List, Tuple, Union
 
 # Velikost okna aplikace
 WIDTH = 320
@@ -35,7 +39,7 @@
 # Třída představující sprite zobrazený jako jednobarevný čtverec.
 class BlockySprite(pygame.sprite.Sprite):
     # Konstruktor
-    def __init__(self, color, size, x, y):
+    def __init__(self, color: Tuple[int, int, int], size: int, x: Union[int, float], y: Union[int, float]) -> None:
         # Nejprve je nutné zavolat konstruktor předka,
         # tj. konstruktor třídy pygame.sprite.Sprite:
         pygame.sprite.Sprite.__init__(self)
@@ -55,15 +59,15 @@
         self.speed_y = 0
 
     # Nastavení barvy spritu, který kolidoval s hráčem
-    def yellowColor(self):
+    def yellowColor(self) -> None:
         self.image.fill(YELLOW)
 
     # Nastavení barvy spritu, který nekolidoval s hráčem
-    def grayColor(self):
+    def grayColor(self) -> None:
         self.image.fill(GRAY)
 
 
-def initDisplay(caption):
+def initDisplay(caption: str) -> Surface:
     # Vytvoření okna pro vykreslování
     display = pygame.display.set_mode([WIDTH, HEIGHT])
 
@@ -73,7 +77,7 @@
     return display
 
 
-def createSprites():
+def createSprites() -> Tuple[Group, Group, BlockySprite]:
     # Objekt sdružující všechny sprity
     all_sprites = pygame.sprite.Group()
 
@@ -115,7 +119,7 @@
 
 
 # Posun všech spritů ve skupině na základě jejich rychlosti
-def move_sprites(sprite_group, playground_width, playground_height):
+def move_sprites(sprite_group: Group, playground_width: int, playground_height: int) -> None:
     for sprite in sprite_group:
         # Posun spritu
         sprite.rect.x = sprite.rect.x + sprite.speed_x
@@ -136,7 +140,7 @@
 
 
 # Vykreslení celé scény na obrazovku
-def draw_scene(display, background_color, sprite_group):
+def draw_scene(display: Surface, background_color: Tuple[int, int, int], sprite_group: Group) -> None:
     # Vyplnění plochy okna černou barvou
     display.fill(background_color)
     # Vykreslení celé skupiny spritů do bufferu
@@ -146,7 +150,7 @@
 
 
 # Změna barvy spritu na základě kolize s hráčem
-def change_colors(sprite_group, hit_list):
+def change_colors(sprite_group: Group, hit_list: List[Union[Any, BlockySprite]]) -> None:
     # Projít všemi sprity ze skupiny, kterou detekovala kolizní funkce
     for sprite in sprite_group:
         if sprite in hit_list:
@@ -156,7 +160,7 @@
 
 
 # Zjistí kolize spritu se "stěnami" (nepohyblivými sprity)
-def check_collisions(player, sprite_group):
+def check_collisions(player: BlockySprite, sprite_group: Group) -> None:
     # Vytvoření seznamu spritů, které kolidují s hráčem
     hit_list = pygame.sprite.spritecollide(player, sprite_group, False)
     # Změna barev kolidujících spritů
@@ -167,7 +171,7 @@
     pygame.display.set_caption(caption)
 
 
-def mainLoop(display, clock, all_sprites, all_sprites_but_player, player):
+def mainLoop(display: Surface, clock: Clock, all_sprites: Group, all_sprites_but_player: Group, player: BlockySprite):
     while True:
         # Načtení a zpracování všech událostí z fronty
         for event in pygame.event.get():

14. Kontrola nástrojem Mypy

Samozřejmě je vhodné výsledný projekt zkontrolovat (co se týče typových informací) nástrojem, který je pro tyto účely navržen. Jedná se o Mypy, který již dobře známe.

Ve skutečnosti kontrola nedopadne se stoprocentní úspěšností:

$ mypy sprites_patched.py 
 
sprites_patched.py:54: error: Incompatible types in assignment (expression has type "Union[int, float]", variable has type "int")  [assignment]
sprites_patched.py:55: error: Incompatible types in assignment (expression has type "Union[int, float]", variable has type "int")  [assignment]
sprites_patched.py:82: error: Need type annotation for "all_sprites"  [var-annotated]
sprites_patched.py:85: error: Need type annotation for "all_sprites_but_player"  [var-annotated]
Found 4 errors in 1 file (checked 1 source file)

Za některé problémy si však můžeme sami, protože první dva typy zmíněné v chybových hlášeních byly odvozeny z těchto programových řádků:

    wall1 = BlockySprite(GRAY, 50, 10, 10)
    wall2 = BlockySprite(GRAY, 15, 100, 100)
    wall3 = BlockySprite(GRAY, 15, 100, 150)
    wall4 = BlockySprite(GRAY, 15, 200, 100)
    wall5 = BlockySprite(GRAY, 15, 200, 150)
    wall6 = BlockySprite(GRAY, 15, 150, 100)
    wall7 = BlockySprite(GRAY, 15, 150, 150)
    player = BlockySprite(RED, 40, WIDTH / 2 - 20, HEIGHT / 2 - 20)

Problém spočívá v tom, že na posledním uvedeném programovém řádku jsou parametry x a y skutečně hodnotami typu float (dělení má v Pythonu 3 neceločíselný výsledek). Musíme tedy opravit původní zdrojový kód!

Striktní typová kontrola dopadne (podle očekávání) ještě hůře:

$ mypy --strict sprites_patched.py 
 
sprites_patched.py:54: error: Incompatible types in assignment (expression has type "Union[int, float]", variable has type "int")  [assignment]
sprites_patched.py:55: error: Incompatible types in assignment (expression has type "Union[int, float]", variable has type "int")  [assignment]
sprites_patched.py:80: error: Missing type parameters for generic type "Group"  [type-arg]
sprites_patched.py:82: error: Need type annotation for "all_sprites"  [var-annotated]
sprites_patched.py:85: error: Need type annotation for "all_sprites_but_player"  [var-annotated]
sprites_patched.py:122: error: Missing type parameters for generic type "Group"  [type-arg]
sprites_patched.py:143: error: Missing type parameters for generic type "Group"  [type-arg]
sprites_patched.py:153: error: Missing type parameters for generic type "Group"  [type-arg]
sprites_patched.py:163: error: Missing type parameters for generic type "Group"  [type-arg]
sprites_patched.py:174: error: Function is missing a return type annotation  [no-untyped-def]
sprites_patched.py:174: error: Missing type parameters for generic type "Group"  [type-arg]
sprites_patched.py:211: error: Function is missing a return type annotation  [no-untyped-def]
sprites_patched.py:211: note: Use "-> None" if function does not return a value
sprites_patched.py:224: error: Call to untyped function "main" in typed context  [no-untyped-call]
Found 13 errors in 1 file (checked 1 source file)

Nejvíce nových problémů spočívá v tom, že Group je generickým typem a tedy očekává typový parametr. Tyto informace je nutné doplnit ručně, například do této podoby:

def createSprites() -> Tuple[pygame.sprite.Group[BlockySprite], pygame.sprite.Group[BlockySprite], BlockySprite]:
    # Objekt sdružující všechny sprity
    all_sprites: pygame.sprite.Group[BlockySprite] = pygame.sprite.Group()

atd. na dalších řádcích.

15. Problematické chování nástroje MonkeyType

Již v předchozí kapitole jsme mohli vidět některé problémy nástroje MonkeyType. Ty většinou vyžadují ruční zásah do analyzovaného a automaticky modifikovaného zdrojového kódu. Problémů je však ve skutečnosti ještě více. Na dva problémy, na něž jsme narazili v praxi, se pokusím upozornit v navazujících dvou kapitolách.

16. n-tice s mnoha prvky

V některých projektech se využívají n-tice jako forma neměnitelného seznamu. To je pochopitelně zcela legální způsob použití, ovšem typový systém Pythonu pracuje s n-ticemi odlišně než se seznamy. U seznamů se totiž očekává, že všechny prvky budou stejného typu (například int, takže píšeme list[int]), zatímco u n-tic se naopak očekává, že každý prvek může být odlišného typu. To ovšem u dlouhých n-tic způsobuje neúměrně dlouhý zápis typu.

Podívejme se nyní na jednoduchý příklad funkce, která volá funkci print_tuple a předává ji n-tici se 100 prvky typu int:

def print_tuple(x):
    print(x)
 
def use_tuple():
    x = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    print_tuple(x)

Nástroj MonkeyType typ n-tice zjistí přesně a zapíše její typ formou „nekonečného“ řádku:

from typing import Tuple
 
def print_tuple(x: Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int]) -> None:
    print(x)
 
def use_tuple() -> None:
    x = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
         1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    print_tuple(x)

V praxi je výhodnější zápis ručně zkrátit na pouhé (což již MonkeyType neumí):

Tuple[int, ...]

17. Pád v případě použití typu Callable na starších verzích Pythonu

Zkusme si nechat analyzovat tento triviální kód, v němž je použita funkce vyššího řádu foo. To je funkce, do které v argumentu předáváme jinou funkci:

#!/usr/bin/env python
 
def foo(x, y, function):
    return function(x, y)
 
 
def main():
    print(foo(1, 2, lambda x, y: x+y))

Výsledkem by mělo být odvození typu zhruba ve tvaru:

Callable[[int, int], int]

Ovšem ve starších verzích Pythonu (které se bohužel stále používají na produkci) dojde k pádu, tedy k nezachycené výjimce, a zdrojové kódy se neupraví (možná by lepší bylo přeskočit problematický kód a pokračovat dále):

linux_sprava_tip

$ monkeytype apply callable
 
Traceback (most recent call last):
  File "/home/ptisnovs/.local/bin/monkeytype", line 8, in <module>
    sys.exit(entry_point_main())
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/monkeytype/cli.py", line 473, in entry_point_main
    sys.exit(main(sys.argv[1:], sys.stdout, sys.stderr))
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/monkeytype/cli.py", line 458, in main
    handler(args, stdout, stderr)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/monkeytype/cli.py", line 209, in apply_stub_handler
    stub = get_stub(args, stdout, stderr)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/monkeytype/cli.py", line 119, in get_stub
    traces.append(thunk.to_trace())
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/monkeytype/encoding.py", line 203, in to_trace
    arg_types = arg_types_from_json(self.arg_types)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/monkeytype/encoding.py", line 151, in arg_types_from_json
    return {name: type_from_dict(type_dict) for name, type_dict in arg_types.items()}
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/monkeytype/encoding.py", line 151, in <dictcomp>
    return {name: type_from_dict(type_dict) for name, type_dict in arg_types.items()}
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/monkeytype/encoding.py", line 126, in type_from_dict
    typ = typ[elem_types]  # type: ignore[index]
  File "/usr/lib/python3.8/typing.py", line 806, in __getitem__
    raise TypeError("Callable must be used as "
TypeError: Callable must be used as Callable[[arg, ...], result].

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

Všechny Pythonovské skripty, které jsme si v dnešním článku ukázali, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady. Pro analýzu kódů je pochopitelně nutné mít nainstalovaný i nástroj monkeytype:

# Příklad Stručný popis Adresa příkladu
1 adder modul obsahující dvojici funkcí bez uvedení typových informací https://github.com/tisnik/most-popular-python-libs/blob/master/monkeytype/adder
2 test_adder1.py zavolání funkcí z modulu adder s parametry jednoho typu https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/test_adder1.py
3 test_adder2.py zavolání funkcí z modulu adder s parametry více typů https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/test_adder2.py
       
4 run_ackermann.py skript pro spuštění výpočtu Ackermannovy funkce https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/run_ackermann.py
5 ackermann.py výpočet Ackermannovy funkce bez uvedení typových informací https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/ackermann.py
6 ackermann_patched.py výpočet Ackermannovy funkce s automaticky přidanými typovými informacemi https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/ackermann_patched.py
       
7 run_sprites.py skript pro spuštění projektu založeného na knihovně Pygame https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/run_sprites.py
8 sprites.py projekt založený na knihovně Pygame bez uvedení typových informací https://github.com/tisnik/most-popular-python-libs/blob/master/monkeytype/sprites.py
9 sprites_patched.py projekt založený na knihovně Pygame s automaticky připojenými typovými informacemi https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/sprites_patched.py
       
10 run_callable.py skript volající funkci vyššího řádku https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/run_callable.py
11 callable.py funkce vyššího řádu https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/callable.py
       
12 run_tuple.py skript volající funkci akceptující n-tici https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/run_tuple.py
13 tuple.py původní tvar funkce, která akceptuje n-tici https://github.com/tisnik/most-popular-python-libs/blob/master/monkeytype/tuple.py
14 tuple_patched.py funkce, která akceptuje n-tici s přidanými typovými informacemi https://github.com/tisnik/most-popular-python-libs/blob/master/monkeyty­pe/tuple_patched.py

19. Odkazy na další články o typových anotacích v Pythonu

V této kapitole jsou uvedeny odkazy na články, které již na Rootu vyšly a ve kterých jsme se zabývali problematikou typového systému programovacího jazyka Python:

  1. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy/
  2. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (2.část)
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy-2-cast/
  3. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (3)
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy-3/
  4. Novinky v typovém systému přidané do Pythonu 3.12
    https://www.root.cz/clanky/novinky-v-typovem-systemu-pridane-do-pythonu-3–12/

20. Odkazy na Internetu

  1. What’s New In Python 3.12 (official)
    https://docs.python.org/3/what­snew/3.12.html
  2. What’s New In Python 3.12
    https://dev.to/mahiuddindev/python-312–4n43
  3. PEP 698 – Override Decorator for Static Typing
    https://peps.python.org/pep-0698/
  4. typing.override
    https://docs.python.org/3/li­brary/typing.html#typing.o­verride
  5. Type Hinting
    https://realpython.com/lessons/type-hinting/
  6. mypy homepage
    https://www.mypy-lang.org/
  7. mypy documentation
    https://mypy.readthedocs.i­o/en/stable/
  8. Mypy na PyPi Optional static typing for Python
    https://pypi.org/project/mypy/
  9. 5 Reasons Why You Should Use Type Hints In Python
    https://www.youtube.com/wat­ch?v=dgBCEB2jVU0
  10. Python Typing – Type Hints & Annotations
    https://www.youtube.com/watch?v=QORvB-_mbZ0
  11. What Problems Can TypeScript Solve?
    https://www.typescriptlang.org/why-create-typescript
  12. How to find code that is missing type annotations?
    https://stackoverflow.com/qu­estions/59898490/how-to-find-code-that-is-missing-type-annotations
  13. Do type annotations in Python enforce static type checking?
    https://stackoverflow.com/qu­estions/54734029/do-type-annotations-in-python-enforce-static-type-checking
  14. Understanding type annotation in Python
    https://blog.logrocket.com/un­derstanding-type-annotation-python/
  15. Static type checking with Mypy — Perfect Python
    https://www.youtube.com/wat­ch?v=9gNnhNxra3E
  16. Static Type Checker for Python
    https://github.com/microsoft/pyright
  17. Differences Between Pyright and Mypy
    https://github.com/microsof­t/pyright/blob/main/docs/my­py-comparison.md
  18. 4 Python type checkers to keep your code clean
    https://www.infoworld.com/ar­ticle/3575079/4-python-type-checkers-to-keep-your-code-clean.html
  19. Pyre: A performant type-checker for Python 3
    https://pyre-check.org/
  20. „Typing the Untyped: Soundness in Gradual Type Systems“ by Ben Weissmann
    https://www.youtube.com/wat­ch?v=uJHD2×yv7×o
  21. Covariance and contravariance (computer science)
    https://en.wikipedia.org/wi­ki/Covariance_and_contrava­riance_(computer_science)
  22. Functional Programming: Type Systems
    https://www.youtube.com/wat­ch?v=hy1wjkcIBCU
  23. A Type System From Scratch – Robert Widmann
    https://www.youtube.com/wat­ch?v=IbjoA5×VUq0
  24. „Type Systems – The Good, Bad and Ugly“ by Paul Snively and Amanda Laucher
    https://www.youtube.com/wat­ch?v=SWTWkYbcWU0
  25. Type Systems: Covariance, Contravariance, Bivariance, and Invariance explained
    https://medium.com/@thejameskyle/type-systems-covariance-contravariance-bivariance-and-invariance-explained-35f43d1110f8
  26. Statická vs. dynamická typová kontrola
    https://www.root.cz/clanky/staticka-dynamicka-typova-kontrola/
  27. Typový systém
    https://cs.wikipedia.org/wi­ki/Typov%C3%BD_syst%C3%A9m
  28. Comparison of programming languages by type system
    https://en.wikipedia.org/wi­ki/Comparison_of_programmin­g_languages_by_type_system
  29. Flow
    https://flow.org/
  30. TypeScript
    https://www.typescriptlang.org/
  31. Sorbet
    https://sorbet.org/
  32. Pyright
    https://github.com/microsoft/pyright
  33. Mypy: Type hints cheat sheet
    https://mypy.readthedocs.i­o/en/stable/cheat_sheet_py3­.html
  34. PEP 484 – Type Hints
    https://peps.python.org/pep-0484/
  35. What is the use of stub files (.pyi ) in python?
    https://stackoverflow.com/qu­estions/59051631/what-is-the-use-of-stub-files-pyi-in-python
  36. PEP 561 – Distributing and Packaging Type Information
    https://peps.python.org/pep-0561/
  37. What does „i“ represent in Python .pyi extension?
    https://stackoverflow.com/qu­estions/41734836/what-does-i-represent-in-python-pyi-extension
Neutrální ikona do widgetu na odběr článků ze seriálů

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.