Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (3)

29. 6. 2023
Doba čtení: 37 minut

Sdílet

 Autor: Python
Ukážeme si striktní režim Mypy i to, jak se pracuje s generickými datovými typy. Popíšeme si výpis typů zvolených symbolů či všech lokálních proměnných v průběhu statické typové analýzy a zmíníme se o neměnitelných hodnotách.

Obsah

1. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (3)

2. Duplikace u specifikací datových typů

3. Definice nových datových typů

4. Upravený kód demonstračního příkladu s definicí vlastního datového typu

5. Striktní kontrola typů nástrojem Mypy

6. Použití typově bezpečného generického datového typu

7. Seznam či sekvence spritů?

8. Zpětná kompatibilita se staršími verzemi Pythonu

9. Upravený kód demonstračního příkladu, který již projde striktní typovou kontrolou

10. Selektivní zjištění typu proměnné či třídy nástrojem Mypy

11. Selektivní zjištění typu funkce nástrojem Mypy

12. Typová inference

13. Explicitní definice typu versus typová inference

14. Zjištění typů všech lokálních proměnných

15. Podpora neměnitelných hodnot (immutable)

16. Příloha 1: základní dostupné datové typy

17. Příloha 2: dostupné generické typy

18. Příloha 3: rozdíly mezi Pythonem < 3.9 a novějšími verzemi

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

20. Odkazy na Internetu

1. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (3)

Ve třetím článku o statických typových kontrolách v Pythonu založených na nástroji Mypy navážeme na předchozí článek, v němž jsme si mj. ukázali, jakým způsobem je možné postupně do existujících zdrojových kódů psaných v Pythonu přidávat typové informace. Připomeňme si, že poslední zdrojový kód, který jsme si minule ukázali, byl doplněn o většinu typových informací, byly v něm opraveny nalezené chyby a výsledek vypadal následovně:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from typing import Tuple, List
 
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: Tuple[int, int, int] = (0, 0, 0)
RED: Tuple[int, int, int] = (255, 0, 0)
GRAY: Tuple[int, int, int] = (128, 128, 128)
YELLOW: Tuple[int, int, int] = (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: int, y: int):
        # 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) -> pygame.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[pygame.sprite.Group, pygame.sprite.Group, BlockySprite]:
    # Objekt sdružující všechny sprity
    all_sprites: pygame.sprite.Group = pygame.sprite.Group()
 
    # Objekt sdružující všechny sprity kromě hráče
    all_sprites_but_player: pygame.sprite.Group = 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: pygame.sprite.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: pygame.Surface, background_color: Tuple[int, int, int], sprite_group: pygame.sprite.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: pygame.sprite.Group, hit_list: List[pygame.sprite.Sprite]) -> 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: pygame.sprite.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: pygame.Surface, clock: pygame.time.Clock,
        all_sprites: pygame.sprite.Group,
        all_sprites_but_player: pygame.sprite.Group, player: BlockySprite) -> None:
    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() -> None:
    # 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

2. Duplikace u specifikací datových typů

V předchozím zdrojovém kódu je možné najít hned několik míst, u nichž specifikujeme shodný datový typ, resp. přesněji řečeno shodný typ z hlediska typového systému Pythonu – například jiný typový systém by tato místa považoval za specifikaci zcela unikátních a nekompatibilních typů:

BLACK: Tuple[int, int, int] = (0, 0, 0)
RED: Tuple[int, int, int] = (255, 0, 0)
GRAY: Tuple[int, int, int] = (128, 128, 128)
YELLOW: Tuple[int, int, int] = (255, 255, 0)
 
def __init__(self, color: Tuple[int, int, int], size: int, x: int, y: int):
 
def draw_scene(display: pygame.Surface, background_color: Tuple[int, int, int], sprite_group: pygame.sprite.Group) -> None:

Je možné nějakým způsobem tyto opakující se části kódu sjednotit? V jiných programovacích jazycích se statickým typovým systémem se tato operace provádí jednoduše – definicí nového datového typu. V Mypy tomu je podobně, i když způsob definice nového datového typu nám může připadat poněkud neobvyklý.

3. Definice nových datových typů

Pro definici nového datového typu se v Pythonu (Mypy) nepoužívá speciální syntaxe (ta prozatím neexistuje). Namísto toho se volá funkce NewType:

NewType(name, tp)
    NewType creates simple unique types with almost zero
    runtime overhead. NewType(name, tp) is considered a subtype of tp
    by static type checkers. At runtime, NewType(name, tp) returns
    a dummy function that simply returns its argument. Usage::
 
        UserId = NewType('UserId', int)
 
        def name_by_id(user_id: UserId) -> str:
            ...
 
        UserId('user')          # Fails type check
 
        name_by_id(42)          # Fails type check
        name_by_id(UserId(42))  # OK
 
        num = UserId(5) + 1     # type: int

Prvním parametrem této funkce je tedy jméno nově definovaného typu (použitého pro Mypy), druhým parametrem pak nadtyp, z něhož je nový datový typ odvozen. Vrácenou hodnotou je funkce, která pouze vrací svůj argument:

>>> real = NewType("real", float)
 
>>> real(3.14)
3.14
 
>>> type(real(3.14))
<class 'float'>
 
>>> type(real("foo"))
<class 'str'>

V našem konkrétním případě si vytvoříme nový datový typ, který se bude jmenovat RGB:

# Definice nových datových typů
RGB = NewType("RGB", Tuple[int, int, int])

Tento nový typ použijeme na následujících řádcích:

RGB = NewType("RGB", Tuple[int, int, int])
BLACK: RGB = RGB((0, 0, 0))
RED: RGB = RGB((255, 0, 0))
GRAY: RGB = RGB((128, 128, 128))
YELLOW: RGB = RGB((255, 255, 0))
 
def __init__(self, color: RGB, size: int, x: int, y: int):
 
def draw_scene(display: pygame.Surface, background_color: RGB, sprite_group: pygame.sprite.Group) -> None:

4. Upravený kód demonstračního příkladu s definicí vlastního datového typu

Upravený kód předchozího demonstračního příkladu, nyní ovšem s definicí a použitím nového datového typu, lze získat na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites5.py. Tento kód vypadá následovně:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from typing import NewType, Tuple, List
 
import pygame
import sys
 
# Nutno importovat kvůli konstantám QUIT atd.
from pygame.locals import *
 
# Velikost okna aplikace
WIDTH = 320
HEIGHT = 240
 
# Definice nových datových typů
RGB = NewType("RGB", Tuple[int, int, int])
 
# Konstanty s n-ticemi představujícími základní barvy
BLACK: RGB = RGB((0, 0, 0))
RED: RGB = RGB((255, 0, 0))
GRAY: RGB = RGB((128, 128, 128))
YELLOW: RGB = RGB((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: RGB, size: int, x: int, y: int):
        # 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) -> pygame.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[pygame.sprite.Group, pygame.sprite.Group, BlockySprite]:
    # Objekt sdružující všechny sprity
    all_sprites: pygame.sprite.Group = pygame.sprite.Group()
 
    # Objekt sdružující všechny sprity kromě hráče
    all_sprites_but_player: pygame.sprite.Group = 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: pygame.sprite.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: pygame.Surface, background_color: RGB, sprite_group: pygame.sprite.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: pygame.sprite.Group, hit_list: List[pygame.sprite.Sprite]) -> 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: pygame.sprite.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: pygame.Surface, clock: pygame.time.Clock,
        all_sprites: pygame.sprite.Group,
        all_sprites_but_player: pygame.sprite.Group, player: BlockySprite) -> None:
    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() -> None:
    # 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

5. Striktní kontrola typů nástrojem Mypy

Nyní je již zdrojový kód zdánlivě typově zcela bezpečný. Pokusme se tedy otestovat, zda je tomu tak i ve skutečnosti. Zavoláme nástroj mypy s parametrem –strict:

$ mypy --strict sprites5.py

V kódu bylo nalezeno celkem devět chyb, které se týkají stejného datového typu Group, resp. přesněji řečeno pygame.sprite.Group:

sprites5.py:69: error: Missing type parameters for generic type "Group"  [type-arg]
sprites5.py:71: error: Missing type parameters for generic type "Group"  [type-arg]
sprites5.py:74: error: Missing type parameters for generic type "Group"  [type-arg]
sprites5.py:111: error: Missing type parameters for generic type "Group"  [type-arg]
sprites5.py:132: error: Missing type parameters for generic type "Group"  [type-arg]
sprites5.py:142: error: Missing type parameters for generic type "Group"  [type-arg]
sprites5.py:152: error: Missing type parameters for generic type "Group"  [type-arg]
sprites5.py:164: error: Missing type parameters for generic type "Group"  [type-arg]
sprites5.py:165: error: Missing type parameters for generic type "Group"  [type-arg]
Found 9 errors in 1 file (checked 1 source file)

Chyby byly nalezeny z toho důvodu, že pygame.sprite.Group je kontejnerem, který může obsahovat sprity. Jde tedy o generický datový typ, u něhož by bylo vhodné specifikovat typy prvků. Rychlá, ale nedokonalá oprava spočívá v tom, že jako hodnoty prvků použijeme typ Any:

pygame.sprite.Group[Any]

To však není typově bezpečné (vlastně jen obcházíme typový systém a přitom se tváříme, jak dobře ho využíváme). Pokud se zamyslíme nad logikou programu, zjistíme, že skupiny spritů budou vždy obsahovat instance třídy BlockySprite a můžeme tedy psát:

pygame.sprite.Group[BlockySprite]

6. Použití typově bezpečného generického datového typu

Úpravu zdrojového kódu příkladu je nutné provést pouze na několika místech, která jsou zvýrazněna pod tímto odstavcem:

def createSprites() -> Tuple[pygame.sprite.Group[BlockySprite], pygame.sprite.Group[BlockySprite], BlockySprite]:
    all_sprites: pygame.sprite.Group[BlockySprite] = pygame.sprite.Group()
    all_sprites_but_player: pygame.sprite.Group[BlockySprite] = pygame.sprite.Group()
def move_sprites(sprite_group: pygame.sprite.Group[BlockySprite], playground_width: int, playground_height:int) -> None:
def draw_scene(display: pygame.Surface, background_color: RGB, sprite_group: pygame.sprite.Group[BlockySprite]) -> None:
def change_colors(sprite_group: pygame.sprite.Group[BlockySprite], hit_list: List[pygame.sprite.Sprite]) -> None:
def check_collisions(player: BlockySprite, sprite_group: pygame.sprite.Group[BlockySprite]) -> None:
        all_sprites: pygame.sprite.Group[BlockySprite],
        all_sprites_but_player: pygame.sprite.Group[BlockySprite], player: BlockySprite) -> None:

7. Seznam či sekvence spritů?

V programovém kódu navíc provedeme ještě dvě úpravy. V prvním kroku nahradíme seznam spritů (List[pygame.sprite.Sprite]) za sekvenci spritů, tedy za Sequence[pygame.sprite.Sprite], čímž efektivně zamezíme modifikaci sekvence spritů (tu stejně nechceme dělat):

from typing import NewType, Tuple, List, Sequence
def change_colors(sprite_group: pygame.sprite.Group[BlockySprite], hit_list: Sequence[pygame.sprite.Sprite]) -> None:
Poznámka: jedná se o doporučeníhodný přístup – nahradit měnitelný seznam za neměnitelnou sekvenci ve všech místech kódu, kde to dává význam.

8. Zpětná kompatibilita se staršími verzemi Pythonu

Ve druhém kroku jako první import uvedeme:

from __future__ import annotations

Tento programový řádek zamezí runtime chybám po spuštění programu ve starších verzích Pythonu. Pokud bude řádek chybět a použijeme například Python 3.8, běh programu skončí s touto chybou:

pygame 2.4.0 (SDL 2.26.4, Python 3.8.10)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
  File "sprites6.py", line 69, in
    def createSprites() -> Tuple[pygame.sprite.Group[BlockySprite], pygame.sprite.Group[BlockySprite], BlockySprite]:
TypeError: 'type' object is not subscriptable
 
shell returned 1

9. Upravený kód demonstračního příkladu, který již projde striktní typovou kontrolou

Upravený kód předchozího demonstračního příkladu, nyní ovšem upraveného do takové podoby, že projde striktní typovou kontrolou, je možné získat na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites6.py. Tento kód vypadá následovně:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from __future__ import annotations
from typing import NewType, Tuple, List, Sequence
 
import pygame
import sys
 
# Nutno importovat kvůli konstantám QUIT atd.
from pygame.locals import *
 
# Velikost okna aplikace
WIDTH = 320
HEIGHT = 240
 
# Definice nových datových typů
RGB = NewType("RGB", Tuple[int, int, int])
 
# Konstanty s n-ticemi představujícími základní barvy
BLACK: RGB = RGB((0, 0, 0))
RED: RGB = RGB((255, 0, 0))
GRAY: RGB = RGB((128, 128, 128))
YELLOW: RGB = RGB((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: RGB, size: int, x: int, y: int):
        # 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) -> pygame.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[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()
 
    # Objekt sdružující všechny sprity kromě hráče
    all_sprites_but_player: pygame.sprite.Group[BlockySprite] = 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: pygame.sprite.Group[BlockySprite], 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: pygame.Surface, background_color: RGB, sprite_group: pygame.sprite.Group[BlockySprite]) -> 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: pygame.sprite.Group[BlockySprite], hit_list: Sequence[pygame.sprite.Sprite]) -> 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: pygame.sprite.Group[BlockySprite]) -> 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: pygame.Surface, clock: pygame.time.Clock,
        all_sprites: pygame.sprite.Group[BlockySprite],
        all_sprites_but_player: pygame.sprite.Group[BlockySprite], player: BlockySprite) -> None:
    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() -> None:
    # 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

10. Selektivní zjištění typu proměnné či třídy nástrojem Mypy

V mnoha případech nastává situace, kdy není (mj. i díky dále zmíněné typové inferenci) zcela zřejmé, jakého typu je nějaká proměnná či třída. Nástroj Mypy pro tyto účely podporuje použití (pseudo)funkce nazvané reveal_type, která dokáže vypsat typ objektu. Má to ovšem dva háčky: jedná se o kód, který je zavolán v průběhu statické typové kontroly nástrojem Mypy a současně se nejedná o skutečnou funkci (proto ji ani neimportujeme). Při spuštění programu v interpretru Pythonu dojde k chybě, protože se jedná o neznámý symbol (tuto pseudofunkci tedy musíme z produkčního kódu odstranit).

Podívejme se nyní na jednoduchý příklad, v němž reveal_type použijeme pro zjištění typu libovolné hodnoty:

x = 42
reveal_type(x)
 
y = "foo"
reveal_type(y)
 
z = (1, "abc", 3.14, True)
reveal_type(z)
 
class X():
    pass
 
w = X()
reveal_type(w)

Nástroj Mypy vypíše následující informace s přesnými informacemi o typech tak, jak je lze zjistit statickou analýzou:

$ mypy reveal_type1.py
 
reveal_type1.py:2: note: Revealed type is "builtins.int"
reveal_type1.py:5: note: Revealed type is "builtins.str"
reveal_type1.py:8: note: Revealed type is "Tuple[builtins.int, builtins.str, builtins.float, builtins.bool]"
reveal_type1.py:14: note: Revealed type is "reveal_type1.X"
Success: no issues found in 1 source file

Naproti tomu pokud volání reveal_type neodstraníme, nebude možné tento kód běžnými prostředky spustit:

$ python3 reveal_type1.py
 
Traceback (most recent call last):
  File "reveal_type1.py", line 2, in
    reveal_type(x)
NameError: name 'reveal_type' is not defined

11. Selektivní zjištění typu funkce nástrojem Mypy

Nejvíce problémů se v souvislosti s datovými typy řeší u funkcí a metod (a to mj. i díky kontravarianci atd.). Podívejme se tedy na to, jak lze reveal_type použít pro typ funkce:

def add(a:int, b:int) -> int:
    return a+b
 
reveal_type(add)
reveal_type(add(1, 2))

Zjištění typu funkce a typu návratové hodnoty této funkce, a to pochopitelně bez jejího volání – jedná se o statickou typovou kontrolu:

$ mypy reveal_type2.py 
 
reveal_type2.py:4: note: Revealed type is "def (a: builtins.int, b: builtins.int) -> builtins.int"
reveal_type2.py:5: note: Revealed type is "builtins.int"

Tentýž příklad, ovšem nyní pro funkci bez explicitního uvedení typů:

def add(a, b):
    return a+b
 
reveal_type(add)
reveal_type(add(1, 2))

Výsledek ukazuje, že se nepoužila typová inference, ale všude, kde je to možné, byl dosazen univerzální datový typ Any:

$ mypy reveal_typeX.py 
 
reveal_type2.py:4: note: Revealed type is "def (a: Any, b: Any) -> Any"
reveal_type2.py:5: note: Revealed type is "Any"

A konečně si vyzkoušejme poněkud složitější funkci, resp. přesněji řečeno funkci se složitějšími typy a návratovými hodnotami:

from typing import List, Set
 
def add(a:List[Set[int]], b:List[Set[int]]) -> List[Set[int]]:
    return a+b
 
 
reveal_type(add)

Výsledek:

func.py:7: note: Revealed type is "def (a: builtins.list[builtins.set[builtins.int]], b: builtins.list[builtins.set[builtins.int]]) -> builtins.list[builtins.set[builtins.int]]"

12. Typová inference

Díky existenci pseudofunkce reveal_type si můžeme ověřit, jakým způsobem Mypy automaticky odvozuje datové typy proměnných. Jedná se o technologii nazvanou typová inference. Nejprve vytvoříme slovník se třemi dvojicemi klíč:hodnota, přičemž klíči budou vždy řetězce a hodnotami vždy celá čísla:

d = {}
 
d["foo"] = 1
d["bar"] = 3
d["baz"] = 10
 
reveal_type(d)

Pseudofunkcí reveal_type si necháme vypsat typ, který Mypy odvodil:

$ mypy reveal_type3.py
 
reveal_type3.py:7: note: Revealed type is "builtins.dict[builtins.str, builtins.int]"
Success: no issues found in 1 source file

Druhý příklad bude z pohledu Mypy složitější, protože klíči slovníků nyní budou jak řetězce, tak i celá čísla a i hodnoty budou různého typu:

d = {}
 
reveal_type(d)
 
d["foo"] = 1
d["bar"] = 3.14
 
reveal_type(d)
 
d[10] = 10
d[42] = "answer"
 
reveal_type(d)

Opět si necháme zobrazit, jaký typ slovníku Mypy v tomto případě odvodil:

$ mypy reveal_type4.py
 
reveal_type4.py:1: error: Need type annotation for "d" (hint: "d: Dict[<type>, <type>] = ...")  [var-annotated]
reveal_type4.py:3: note: Revealed type is "builtins.dict[Any, Any]"
reveal_type4.py:6: error: Incompatible types in assignment (expression has type "float", target has type "int")  [assignment]
reveal_type4.py:8: note: Revealed type is "builtins.dict[builtins.str, builtins.int]"
reveal_type4.py:10: error: Invalid index type "int" for "Dict[str, int]"; expected type "str"  [index]
reveal_type4.py:11: error: Invalid index type "int" for "Dict[str, int]"; expected type "str"  [index]
reveal_type4.py:11: error: Incompatible types in assignment (expression has type "str", target has type "int")  [assignment]
reveal_type4.py:13: note: Revealed type is "builtins.dict[builtins.str, builtins.int]"
Found 5 errors in 1 file (checked 1 source file)
Poznámka: pravděpodobně by v praxi bylo lepší využít typ Union, protože po prvním odvození typu (řádek 3 na výstupu) už se pochopitelně typ slovníku nemůže měnit.

13. Explicitní definice typu versus typová inference

Samozřejmě nám nic nebrání (spíše naopak) explicitně zapsat typ slovníku, tj. typ klíčů i typ hodnot. Například můžeme specifikovat, že typ klíčů bude vždy řetězec a typ hodnot buď celé číslo, číslo s plovoucí řádovou čárkou nebo řetězec:

from typing import Dict, Union
 
d:Dict[str, Union[int, float, str]] = {}
 
reveal_type(d)
 
d["foo"] = 1
d["bar"] = 3.14
d["baz"] = "*"
 
reveal_type(d)
 
d[10] = 10
d[42] = "answer"
 
reveal_type(d)

A takto bude vypadat výsledek statické typové kontroly nástrojem Mypy. Podle očekávání budou detekovány chyby na posledních dvou přiřazeních do slovníku (řádky 13 a 14):

$ mypy reveal_type5.py
 
reveal_type5.py:5: note: Revealed type is "builtins.dict[builtins.str, Union[builtins.int, builtins.float, builtins.str]]"
reveal_type5.py:11: note: Revealed type is "builtins.dict[builtins.str, Union[builtins.int, builtins.float, builtins.str]]"
reveal_type5.py:13: error: Invalid index type "int" for "Dict[str, Union[int, float, str]]"; expected type "str"  [index]
reveal_type5.py:14: error: Invalid index type "int" for "Dict[str, Union[int, float, str]]"; expected type "str"  [index]
reveal_type5.py:16: note: Revealed type is "builtins.dict[builtins.str, Union[builtins.int, builtins.float, builtins.str]]"
Found 2 errors in 1 file (checked 1 source file)

14. Zjištění typů všech lokálních proměnných

Kromě výše popsané pseudofunkce reveal_type můžeme při vývoji použít další pseudofunkci nazvanou releal_locals. Jak název této pseudofunkce napovídá, zobrazí se v místě jejího použití při statické typové kontrole typy všech lokálních proměnných, resp. přesněji řečeno proměnných z daného bloku. Ostatně si tuto funkcionalitu nástroje Mypy můžeme ukázat na několika demonstračních příkladech. Začneme proměnnými globálními v rámci daného balíčku:

x = 42
 
y = "foo"
 
z = (1, "abc", 3.14, True)
 
class X():
    pass
 
 
w = X()
reveal_locals()

Výsledek vyprodukovaný nástrojem Mypy:

$ mypy reveal_locals1.py
 
reveal_locals1.py:12: note: Revealed local types are:
reveal_locals1.py:12: note:     w: reveal_locals1.X
reveal_locals1.py:12: note:     x: builtins.int
reveal_locals1.py:12: note:     y: builtins.str
reveal_locals1.py:12: note:     z: Tuple[builtins.int, builtins.str, builtins.float, builtins.bool]
Success: no issues found in 1 source file

Zobrazení typů parametrů a lokálních proměnných ve funkci:

def add(a:int, b:int) -> int:
    reveal_locals()
    return a+b
 
reveal_type(add)
reveal_type(add(1, 2))

Nyní bude výsledek následující:

$ mypy reveal_locals2.py
 
reveal_locals2.py:2: note: Revealed local types are:
reveal_locals2.py:2: note:     a: builtins.int
reveal_locals2.py:2: note:     b: builtins.int
reveal_locals2.py:5: note: Revealed type is "def (a: builtins.int, b: builtins.int) -> builtins.int"
reveal_locals2.py:6: note: Revealed type is "builtins.int"
Success: no issues found in 1 source file

A konečně si ukažme ještě nepatrně složitější příklad s funkcí vyššího řádu, konkrétně s funkcí, která jako svůj parametr akceptuje jinou funkci:

from typing import Callable
 
 
def printIsPositive(condition:Callable[[int], bool]) -> None:
    reveal_locals()
    if condition(5):
        print("Positive")
    else:
        print("Negative")
 
 
def positiveFloat(x:float) -> bool:
    return x > 0.0
 
 
def positiveInt(x:int) -> bool:
    return x > 0
 
 
printIsPositive(positiveFloat)
printIsPositive(positiveInt)

Výsledky budou v tomto případě vypadat následovně:

$ mypy reveal_locals3.py
 
reveal_locals3.py:5: note: Revealed local types are:
reveal_locals3.py:5: note:     condition: def (builtins.int) -> builtins.bool
Success: no issues found in 1 source file

15. Podpora neměnitelných hodnot (immutable)

Již minule jsme se, i když jen okrajově, zmínili o podpoře neměnitelných (immutable) hodnot, přesněji řečeno typů, u nichž je specifikováno, že se jejich hodnoty nebudou měnit. Konkrétně je možné použít kvalifikátor Final, o němž se podrobněji zmíníme příště, nebo v případě seznamů lze seznamy nahradit za typ Sequence a slovníky za typ Mapping. Jak Sequence tak i Mapping při statické typové kontrole představují immutable hodnoty, ovšem při běhu (runtime) se jedná o běžné seznamy nebo slovníky!

Ostatně si to můžeme velmi snadno ověřit. Začneme seznamy a typem Sequence:

from typing import List, Sequence
 
l1: List[bool] = [True, False]
 
l1.append(True)
l1.append(False)
 
print(l1)
 
l2: Sequence[bool] = [True, False]
 
l2.append(True)
l2.append(False)
 
print(l2)

Výsledek po spuštění v interpretru Pythonu neukáže žádný podstatný rozdíl:

$ python3 list_sequence.py
 
[True, False, True, False]
[True, False, True, False]

Výsledek statické typové kontroly ovšem již rozdíly pochopitelně ukazuje:

$ mypy list_sequence.py
 
list_sequence.py:12: error: "Sequence[bool]" has no attribute "append"  [attr-defined]
list_sequence.py:13: error: "Sequence[bool]" has no attribute "append"  [attr-defined]
Found 2 errors in 1 file (checked 1 source file)

Prakticky totéž platí pro slovníky a typ Mapping:

from typing import Dict, Mapping
 
d1:Dict[str, float] = {}
 
d1["foo"] = 1
d1["bar"] = 3.14
d1["baz"] = 0.0
 
print(d1)
 
d2:Mapping[str, float] = {}
 
d2["foo"] = 1
d2["bar"] = 3.14
d2["baz"] = 0.0
 
print(d2)

Výsledek po spuštění v interpretru Pythonu opět neukáže žádný podstatný rozdíl:

$ python3 dict_mapping.py
 
{'foo': 1, 'bar': 3.14, 'baz': 0.0}
{'foo': 1, 'bar': 3.14, 'baz': 0.0}

Výsledek statické typové kontroly ovšem již rozdíly pochopitelně ukazuje:

$ mypy dict_mapping.py
 
dict_mapping.py:13: error: Unsupported target for indexed assignment ("Mapping[str, float]")  [index]
dict_mapping.py:14: error: Unsupported target for indexed assignment ("Mapping[str, float]")  [index]
dict_mapping.py:15: error: Unsupported target for indexed assignment ("Mapping[str, float]")  [index]
Found 3 errors in 1 file (checked 1 source file)

16. Příloha 1: základní dostupné datové typy

V Mypy je definováno sedm základních datových typů, z nichž se mohou odvozovat další datové typy, popř. lze tyto typy zkombinovat s generickými typy vypsanými v navazující kapitole:

# Typ Stručný popis Podtyp z
1 int celá čísla  
2 float čísla s plovoucí řádovou čárkou  
3 bool pravdivostní hodnoty int
4 str sekvence Unicode znaků  
5 bytes sekvence bajtů  
6 object libovolný objekt object (bázová třída)
7 Any libovolný typ  

17. Příloha 2: dostupné generické typy

Základní datové typy popsané v předchozí kapitole je možné zkombinovat se standardními generickými datovými typy, které jsou, společně s příklady, vypsány v následující tabulce. Většinu těchto typů již známe z předešlých článků:

# Generický typ Stručný popis
1 list[str] seznam hodnot typu řetězec
2 list[Any] seznam hodnot libovolného typu
3 tuple[int, int] dvojice hodnot typu celé číslo
4 tuple[int, str] dvojice hodnot, první hodnotou bude celé číslo, druhou hodnotou řetězec
5 tuple[()] prázdná n-tice
6 tuple[int, …] n-tice s libovolným množstvím hodnot typu int
7 dict[str, int] slovník s klíči typu řetězec a hodnotami typu celé číslo
8 dict[str, Any] slovník s klíči typu řetězec a hodnotami libovolného typu
9 Iterable[int] objekt, který je „iterovatelný“ (lze jím procházet s využitím for-each)
10 Sequence[int] obdoba seznamů, ovšem neměnitelná (immutable)
11 Mapping[str, int] obdoba slovníků, ovšem neměnitelná (immutable)
Poznámka: v této tabulce byly použity identifikátory zavedené v Pythonu 3.9 a pochopitelně podporované i v Pythonu 3.10 a 3.11. Na jednu stranu se používají názvy list, tuple a dict, které není zapotřebí importovat, na stranu druhou zde můžeme vidět různé jmenné nekonzistence, například mezi názvem list a Sequence.

18. Příloha 3: rozdíly mezi Pythonem < 3.9 a novějšími verzemi

Již několikrát jsme se setkali s rozdílným způsobem zápisu typů v Pythonu 3.9 a jakékoli vyšší verze na jedné straně a staršími verzemi Pythonu 3 na straně druhé. Tyto rozdíly jsou zvýrazněny v následující tabulce (přičemž platí, že typy začínající velkým písmenem je nutné importovat z balíčku typing):

bitcoin_skoleni

Původní zápis typu Nový zápis typu
List[str] list[str]
Tuple[int, int] tuple[int, int]
Tuple[int, …] tuple[int, …]
Dict[str, int] dict[str, int]
Poznámka: všechny ostatní typy se zapisují stejně v jakékoli verzi Pythonu 3.x (konkrétně od verze 3.7 výše).

Další rozdíl spočívá v zápisu „složeného“ typu Union a Optional:

Původní zápis typu Nový zápis typu
Union[int, str] int | str
Optional[int, str] int | None
Poznámka: záleží pochopitelně na vás, kterému způsobu zápisu dáváte přednost. Starší způsob je stále podporován.

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

Všechny Pythonovské skripty, které jsme si v dnešním článku (i v obou předchozích článcích) ukázali, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady (pro jejich spuštění je pochopitelně nutné mít nainstalován balíček mypy společně s Pythonem alespoň 3.7):

# Příklad Stručný popis Adresa příkladu
1 adder1.py funkce add bez typových anotací https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder1.py
2 adder2.py funkce add s typovými anotacemi https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder2.py
3 adder3.py funkce add volaná s hodnotami TrueFalse https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder3.py
4 adder4.py funkce add akceptující hodnoty typu bool https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder4.py
5 adder5.py zobrazení typových informací pro funkci bez typových anotací https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder5.py
6 adder6.py zobrazení typových informací pro funkci s typovými anotacemi https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/adder6.py
       
5 exec_problem1.py funkce add s typovými anotacemi https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/exec_pro­blem1.py
6 exec_problem2.py korektní detekce volání funkce add s nekompatibilními hodnotami https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/exec_pro­blem2.py
7 exec_problem3.py příkaz použitý v exec není statickým analyzátorem zachycen https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/exec_pro­blem3.py
       
8 list_type1.py typ seznam, s inicializací (bez prvků), pro Python 3.10 https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type1.py
9 list_type2.py typ seznam, s inicializací (bez prvků), pro starší verze Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type2.py
10 list_type3.py typ seznam, s inicializací (s prvky), pro starší verze Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type3.py
11 list_type4.py typ seznam, kontrola použití prvků s nekorektními typy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type4.py
12 list_type5.py typ seznam, kontrola použití prvků s korektními typy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type5.py
13 list_type6.py typ seznam, kontrola použití prvků s korektními typy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_type6.py
       
14 tuple_type1.py typ n-tice (nekorektní specifikace typu) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/tuple_type1.py
15 tuple_type2.py typ n-tice (korektní specifikace typu) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/tuple_type2.py
16 tuple_type3.py typ n-tice, v níž má každý prvek odlišný typ https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/tuple_type3.py
17 tuple_type4.py typ n-tice, v níž má každý prvek odlišný typ https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/tuple_type4.py
       
18 json_check.py delší kód v Pythonu bez typových anotací https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/json_check.py
       
19 Variance1.java variance v Javě – korektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance1.java
20 Variance2.java variance v Javě – nekorektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance2.java
       
21 Variance1.py variance v Pythonu – korektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance1.py
22 Variance2.py variance v Pythonu – nekorektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance2.py
23 Variance3.py variance v Pythonu – nekorektní příklad použití https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance3.py
24 Variance4.py použití typu Sequence namísto List https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/Variance4.py
       
25 view_pyc.py jednoduchá prohlížečka souborů .pyc https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/view_pyc.py
       
26 callable1.py funkce s typovými informacemi https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/callable1.py
27 callable2.py variance funkcí https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/callable2.py
28 callable3.py variance funkcí (nekorektní příklad) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/callable3.py
29 callable4.py korektní řešení problému z kódu callable3.py https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/callable4.py
       
30 dict_type1.py slovník bez specifikace informací o typech (homogenní struktura) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type1.py
31 dict_type2.py slovník bez specifikace informací o typech (heterogenní struktura) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type2.py
32 dict_type3.py typově silný slovník (heterogenní struktura) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type3.py
33 dict_type4.py typově silný slovník (homogenní struktura) https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type4.py
34 dict_type5.py použití typu Union https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type5.py
35 dict_type6.py použití typu Union https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type6.py
36 dict_type7.py použití typu Optional https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type7.py
37 dict_type8.py použití typu Optional https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_type8.py
       
38 sprites1.py původní kód před refaktoringem a bez typových informací https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites1.py
39 sprites2.py refaktoring kódu sprites1.py https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites2.py
40 sprites3.py přidání typových informací do kódu sprites2.py https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites3.py
41 sprites4.py oprava chyb nalezených v kódu sprites3.py https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites4.py
42 sprites5.py doplnění generických typů pro volbu –strict https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites5.py
43 sprites6.py definice a použití nového datového typu https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites6.py
       
44 reveal_type1.py selektivní zjištění typu proměnné či třídy nástrojem Mypy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type1.py
45 reveal_type2.py selektivní zjištění typu funkce nástrojem Mypy https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type2.py
46 reveal_type3.py typová inference https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type3.py
47 reveal_type4.py typová inference https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type4.py
48 reveal_type5.py explicitní definice typu versus typová inference https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reveal_type5.py
       
49 reveal_locals1.py zjištění typů všech lokálních proměnných https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reve­al_locals1.py
50 reveal_locals2.py zjištění typů všech lokálních proměnných https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reve­al_locals2.py
51 reveal_locals3.py zjištění typů všech lokálních proměnných https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/reve­al_locals3.py
       
52 list_sequence.py měnitelné seznamy vs. neměnitelné sekvence https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/list_sequence.py
53 dict_mapping.py měnitelné slovníky vs. neměnitelné mapování https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/dict_mapping.py

20. Odkazy na Internetu

  1. mypy homepage
    https://www.mypy-lang.org/
  2. mypy documentation
    https://mypy.readthedocs.i­o/en/stable/
  3. Mypy na PyPi Optional static typing for Python
    https://pypi.org/project/mypy/
  4. 5 Reasons Why You Should Use Type Hints In Python
    https://www.youtube.com/wat­ch?v=dgBCEB2jVU0
  5. Python Typing – Type Hints & Annotations
    https://www.youtube.com/watch?v=QORvB-_mbZ0
  6. What Problems Can TypeScript Solve?
    https://www.typescriptlang.org/why-create-typescript
  7. How to find code that is missing type annotations?
    https://stackoverflow.com/qu­estions/59898490/how-to-find-code-that-is-missing-type-annotations
  8. Do type annotations in Python enforce static type checking?
    https://stackoverflow.com/qu­estions/54734029/do-type-annotations-in-python-enforce-static-type-checking
  9. Understanding type annotation in Python
    https://blog.logrocket.com/un­derstanding-type-annotation-python/
  10. Static type checking with Mypy — Perfect Python
    https://www.youtube.com/wat­ch?v=9gNnhNxra3E
  11. Static Type Checker for Python
    https://github.com/microsoft/pyright
  12. Differences Between Pyright and Mypy
    https://github.com/microsof­t/pyright/blob/main/docs/my­py-comparison.md
  13. 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
  14. Pyre: A performant type-checker for Python 3
    https://pyre-check.org/
  15. „Typing the Untyped: Soundness in Gradual Type Systems“ by Ben Weissmann
    https://www.youtube.com/wat­ch?v=uJHD2×yv7×o
  16. Covariance and contravariance (computer science)
    https://en.wikipedia.org/wi­ki/Covariance_and_contrava­riance_(computer_science)
  17. Functional Programming: Type Systems
    https://www.youtube.com/wat­ch?v=hy1wjkcIBCU
  18. A Type System From Scratch – Robert Widmann
    https://www.youtube.com/wat­ch?v=IbjoA5×VUq0
  19. „Type Systems – The Good, Bad and Ugly“ by Paul Snively and Amanda Laucher
    https://www.youtube.com/wat­ch?v=SWTWkYbcWU0
  20. Type Systems: Covariance, Contravariance, Bivariance, and Invariance explained
    https://medium.com/@thejameskyle/type-systems-covariance-contravariance-bivariance-and-invariance-explained-35f43d1110f8
  21. Statická vs. dynamická typová kontrola
    https://www.root.cz/clanky/staticka-dynamicka-typova-kontrola/
  22. Typový systém
    https://cs.wikipedia.org/wi­ki/Typov%C3%BD_syst%C3%A9m
  23. Comparison of programming languages by type system
    https://en.wikipedia.org/wi­ki/Comparison_of_programmin­g_languages_by_type_system
  24. Flow
    https://flow.org/
  25. TypeScript
    https://www.typescriptlang.org/
  26. Sorbet
    https://sorbet.org/
  27. Pyright
    https://github.com/microsoft/pyright
  28. Mypy: Type hints cheat sheet
    https://mypy.readthedocs.i­o/en/stable/cheat_sheet_py3­.html
  29. PEP 484 – Type Hints
    https://peps.python.org/pep-0484/

Autor článku

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